Skip to main content

Morph SDK

Official JavaScript/TypeScript SDK for interacting with the Morph Network

Table of Contents


Introduction

Morph SDK is the official JavaScript/TypeScript toolkit for interacting with the Morph Network. Its core capability is support for Alt Fee Transactions—a Morph-specific transaction type that lets users pay gas with ERC-20 tokens instead of ETH.

Why choose the Morph SDK?

Compared with using base libraries (Viem/Ethers) directly, the SDK offers:

FeatureBase libs onlyMorph SDK
Alt Fee Transaction support❌ Manual serialize/deserialize needed✅ Built-in, just add feeTokenID + feeLimit
Tx type 0x7f❌ Unsupported without forking/patching✅ Fully supported
Morph chain config❌ Manual chainId/RPC setup✅ Mainnet & Testnet presets
Token Registry access❌ Manage ABI yourself✅ ABI + addresses exported
Type safety⚠️ No Morph-specific types✅ Full TypeScript typings
Backward compatibility✅ Normal txs keep working

SDK architecture

┌─────────────────────────────────────────────────────────┐
│ Your Application │
└─────────────────────────────────────────────────────────┘

┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ @morph- │ │ @morph- │ │ @morph- │
│ network/ │ │ network/ │ │ network/ │
│ viem │ │ ethers │ │ ethers5 │
└─────────────┘ └─────────────┘ └──────────────┘
│ │ │
└─────────────────────┼─────────────────────┘


┌─────────────────┐
│ @morph-network/ │
│ chain │
└─────────────────┘

┌─────────────────────┴─────────────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Morph │ │ Morph │
│ Mainnet │ │ Hoodi │
│ (2818) │ │ Testnet │
│ │ │ (2910) │
└─────────────┘ └─────────────┘

Alt Fee Transaction basics

Alt Fee Transaction (type 0x7f) lets users pay gas with registered ERC-20 tokens (e.g., USDT/USDC) instead of ETH.

Key fields:

  • feeTokenID: Token ID in the Token Registry
  • feeLimit: Max token amount the user is willing to pay

Installation

Prerequisites

  • Node.js >= 18
  • pnpm / npm / yarn

Pick the adapter for your stack

If you use...Package
Viem@morph-network/viem
Ethers v6@morph-network/ethers
Ethers v5@morph-network/ethers5
# Viem adapter
pnpm add viem @morph-network/viem

# Ethers v6 adapter
pnpm add ethers @morph-network/ethers

# Ethers v5 adapter
pnpm add ethers@^5.8.0 @morph-network/ethers5

With npm

# Viem adapter
npm install viem @morph-network/viem

# Ethers v6 adapter
npm install ethers @morph-network/ethers

# Ethers v5 adapter
npm install ethers@^5.8.0 @morph-network/ethers5

With yarn

# Viem adapter
yarn add viem @morph-network/viem

# Ethers v6 adapter
yarn add ethers @morph-network/ethers

# Ethers v5 adapter
yarn add ethers@^5.8.0 @morph-network/ethers5

Quick Start

Viem quick start

Send an Alt Fee Transaction with the Viem adapter:

import { createPublicClient, createWalletClient, http, parseEther } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { morphHoodiTestnet } from "@morph-network/viem";

// 1. Create account
const account = privateKeyToAccount("0x...");

// 2. Public Client (read)
const publicClient = createPublicClient({
chain: morphHoodiTestnet,
transport: http(),
});

// 3. Wallet Client (sign + send)
const walletClient = createWalletClient({
account,
chain: morphHoodiTestnet,
transport: http(),
});

// 4. Send Alt Fee Transaction
async function sendAltFeeTransaction() {
const nonce = await publicClient.getTransactionCount({
address: account.address,
});

const hash = await walletClient.sendTransaction({
account,
to: "0x...",
value: parseEther("0.01"),
nonce,
gas: 100000n,
maxFeePerGas: 15000000n,
maxPriorityFeePerGas: 14000000n,
// Alt Fee fields
feeTokenID: 6, // Token Registry ID
feeLimit: 252637086960555000n, // Max token payment
});

console.log("Transaction hash:", hash);
}

sendAltFeeTransaction();

Ethers v6 quick start

Send an Alt Fee Transaction with the Ethers v6 adapter:

import { BrowserProvider, parseEther } from "ethers";
import { MorphSigner, MORPH_HOODI_TESTNET } from "@morph-network/ethers";

async function main() {
// 1. Provider (e.g., MetaMask)
const provider = new BrowserProvider(window.ethereum);

// 2. Wrap signer
const browserSigner = await provider.getSigner();
const signer = MorphSigner.from(browserSigner);

// 3. Send Alt Fee Transaction
const tx = await signer.sendTransaction({
to: "0x...",
value: parseEther("0.01"),
chainId: MORPH_HOODI_TESTNET.chainId,
gasLimit: 100000n,
maxFeePerGas: 15000000n,
maxPriorityFeePerGas: 14000000n,
// Alt Fee fields
feeTokenID: 6,
feeLimit: 252637086960555000n,
});

console.log("Transaction response:", tx);
}

Ethers v5 quick start

import { ethers, providers } from "ethers";
import { MorphSigner, MORPH_HOODI_TESTNET } from "@morph-network/ethers5";

async function main() {
// 1. Provider
const provider = new providers.Web3Provider(window.ethereum);

// 2. Wrap signer
const web3Signer = provider.getSigner();
const signer = MorphSigner.from(web3Signer);

// 3. Send Alt Fee Transaction
const tx = await signer.sendTransaction({
to: "0x...",
value: ethers.utils.parseEther("0.01"),
chainId: MORPH_HOODI_TESTNET.chainId,
gasLimit: ethers.BigNumber.from(100000),
maxFeePerGas: ethers.BigNumber.from(15000000),
maxPriorityFeePerGas: ethers.BigNumber.from(14000000),
// Alt Fee fields
feeTokenID: 6,
feeLimit: ethers.BigNumber.from("252637086960555000"),
});

console.log("Transaction response:", tx);
}

Query supported tokens

import { createPublicClient, http } from "viem";
import { readContract } from "viem/actions";
import {
morphHoodiTestnet,
TOKEN_REGISTRY_PROXY_ADDRESS,
tokenRegistryAbi,
} from "@morph-network/viem";

const publicClient = createPublicClient({
chain: morphHoodiTestnet,
transport: http(),
});

// Get supported tokens
async function getSupportedTokens() {
const tokens = await readContract(publicClient, {
address: TOKEN_REGISTRY_PROXY_ADDRESS,
abi: tokenRegistryAbi,
functionName: "getSupportedTokenList",
});

console.log("Supported tokens:", tokens);
// => [{ tokenID: 6, tokenAddress: "0x..." }, ...]
}

// Get specific token info
async function getTokenInfo(tokenId: number) {
const info = await readContract(publicClient, {
address: TOKEN_REGISTRY_PROXY_ADDRESS,
abi: tokenRegistryAbi,
functionName: "getTokenInfo",
args: [tokenId],
});

console.log("Token info:", info);
}

API Reference

@morph-network/viem

Viem adapter with Morph-specific features.

Chain definitions

morphMainnet

Mainnet chain object, usable directly in createPublicClient / createWalletClient.

import { morphMainnet } from "@morph-network/viem";

// Properties
morphMainnet.id // 2818
morphMainnet.name // "Morph Mainnet"
morphMainnet.nativeCurrency // { name: "Ethereum", symbol: "ETH", decimals: 18 }
morphMainnet.rpcUrls.default.http // ["https://rpc.morph.network"]

Usage:

import { createPublicClient, http } from "viem";
import { morphMainnet } from "@morph-network/viem";

const client = createPublicClient({
chain: morphMainnet,
transport: http(),
});
morphHoodiTestnet

Hoodi Testnet chain object.

import { morphHoodiTestnet } from "@morph-network/viem";

// Properties
morphHoodiTestnet.id // 2910
morphHoodiTestnet.name // "Morph Hoodi Testnet"
morphHoodiTestnet.nativeCurrency // { name: "Ethereum", symbol: "ETH", decimals: 18 }
morphHoodiTestnet.rpcUrls.default.http // ["https://rpc-hoodi.morphl2.io"]

Usage:

import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { morphHoodiTestnet } from "@morph-network/viem";

const client = createWalletClient({
account: privateKeyToAccount("0x..."),
chain: morphHoodiTestnet,
transport: http(),
});

Serialization helpers

serializeTransaction(tx, signature?)

Serialize EIP-1559 or Alt Fee transactions.

Parameters:

ParamTypeDescription
txAltFeeTransactionTransaction object
signatureSignature (optional)Signature

Returns: Hex - Serialized transaction hex

Example:

import { serializeTransaction } from "@morph-network/viem";

const serialized = serializeTransaction({
chainId: 2910,
to: "0x...",
value: 1000000000000000n,
gas: 100000n,
maxFeePerGas: 15000000n,
maxPriorityFeePerGas: 14000000n,
nonce: 0,
feeTokenID: 6,
feeLimit: 252637086960555000n,
});

console.log(serialized); // "0x7f..."
serializeAltFeeTransaction(tx, signature?)

Serialize Alt Fee transactions (type 0x7f).

Parameters:

ParamTypeDescription
txAltFeeTransactionAlt Fee transaction
signatureSignature (optional)Signature

Returns: AltFeeTransactionSerialized

Example:

import { serializeAltFeeTransaction } from "@morph-network/viem";

const serialized = serializeAltFeeTransaction({
chainId: 2910,
to: "0x92be4a7c135314932b481c4c5a9e391644b91fe5",
value: 10000000000000000n,
gas: 1000000n,
maxFeePerGas: 15316544n,
maxPriorityFeePerGas: 14116544n,
nonce: 5,
feeTokenID: 6,
feeLimit: 252637086960555000n,
accessList: [],
});

// With signature
const signedSerialized = serializeAltFeeTransaction(tx, {
r: "0x...",
s: "0x...",
v: 27n,
yParity: 0,
});

Parsing helpers

parseTransaction(serializedTx)

Parse a serialized transaction and auto-detect Alt Fee vs standard.

Parameters:

ParamTypeDescription
serializedTxHexSerialized transaction hex

Returns: Parsed transaction object

Example:

import { parseTransaction } from "@morph-network/viem";

const tx = parseTransaction("0x7f...");

console.log(tx);
// {
// type: "altFee",
// chainId: 2910,
// to: "0x...",
// value: 10000000000000000n,
// feeTokenID: 6,
// feeLimit: 252637086960555000n,
// ...
// }
parseAltFeeTransaction(serializedTx)

Parse Alt Fee transactions.

Parameters:

ParamTypeDescription
serializedTxHexSerialized Alt Fee transaction

Returns: AltFeeTransactionSerializable

Example:

import { parseAltFeeTransaction } from "@morph-network/viem";

const tx = parseAltFeeTransaction("0x7f...");

console.log(tx.feeTokenID); // 6
console.log(tx.feeLimit); // 252637086960555000n
console.log(tx.type); // "altFee"

Address recovery

recoverAddress(parameters)

Recover the signer address from a serialized transaction (Alt Fee supported).

Parameters:

ParamTypeDescription
parameters.serializedTransactionHexSerialized transaction
parameters.signatureSignature (optional)Signature (if not embedded)

Returns: Promise<Address>

Example:

import { recoverAddress, parseTransaction } from "@morph-network/viem";

const serializedTransaction = "0x7f..."; // Signed transaction

// Recover signer
const from = await recoverAddress({ serializedTransaction });
console.log("Signer address:", from);

// Verify flow
const parsedTx = parseTransaction(serializedTransaction);
const recoveredFrom = await recoverAddress({ serializedTransaction });

console.log("Parsed tx:", parsedTx);
console.log("Recovered from:", recoveredFrom);

Utility

numeric(value)

Convert between number, bigint, and hex.

Parameters:

ParamTypeDescription
valuenumber | bigint | HexInput value

Return helpers:

MethodReturnsDescription
.bigint()bigintTo bigint
.number()numberTo number
.hex()HexRLP-minified hex
.uint(size)HexFixed-width uint
.uint16()Hexuint16
.uint64()Hexuint64

Example:

import { numeric } from "@morph-network/viem";

// Convert types
numeric(0).hex(); // "0x"
numeric(1).hex(); // "0x1"
numeric(255).hex(); // "0xff"
numeric(256n).hex(); // "0x100"
numeric("0x10").hex(); // "0x10"

// Other conversions
numeric("0xff").number(); // 255
numeric(100).bigint(); // 100n
numeric(1000).uint16(); // "0x03e8"

// In a transaction
const tx = {
feeTokenID: numeric(6).hex(),
feeLimit: numeric(252637086960555000n).hex(),
};

Types

AltFeeTransaction

Alt Fee transaction type.

import type { AltFeeTransaction } from "@morph-network/viem";

const tx: AltFeeTransaction = {
chainId: 2910,
to: "0x...",
value: 1000000000000000n,
gas: 100000n,
maxFeePerGas: 15000000n,
maxPriorityFeePerGas: 14000000n,
nonce: 0,
data: "0x",
accessList: [],
// Alt Fee fields
feeTokenID: 6,
feeLimit: 252637086960555000n,
};
AltFeeTransactionRequest

Alt Fee transaction request type for signTransaction / sendTransaction.

import type { AltFeeTransactionRequest } from "@morph-network/viem";
NumericLike
import type { NumericLike } from "@morph-network/viem";

// All valid NumericLike
const a: NumericLike = 6;
const b: NumericLike = 6n;
const c: NumericLike = "0x06";

@morph-network/ethers

Ethers v6 adapter.

MorphSigner

Wraps an Ethers Signer to support Alt Fee Transactions.

MorphSigner.from(signer)

Factory to wrap an Ethers Signer with Morph Alt Fee support.

Parameters:

ParamTypeDescription
signerSignerEthers Signer instance

Returns: T & MorphSigner - Wrapped signer with Morph features

Example:

import { BrowserProvider } from "ethers";
import { MorphSigner } from "@morph-network/ethers";

const provider = new BrowserProvider(window.ethereum);
const browserSigner = await provider.getSigner();

// Wrap
const signer = MorphSigner.from(browserSigner);

// Alt Fee tx supported now
await signer.sendTransaction({
to: "0x...",
value: parseEther("0.01"),
feeTokenID: 6,
feeLimit: 252637086960555000n,
});
signer.signTransaction(tx)

Sign standard or Alt Fee transactions.

Parameters:

ParamTypeDescription
txTransactionRequestRequest object

Returns: Promise<string> - Signed tx hex

TransactionRequest fields:

FieldTypeRequiredDescription
tostringYesRecipient
valuebigintNoAmount
datastringNoCall data
chainIdnumberYesChain ID
gasLimitbigintYesGas limit
maxFeePerGasbigintYesMax fee
maxPriorityFeePerGasbigintYesMax priority fee
noncenumberNoTx nonce
feeTokenIDnumberNo*Token ID (required for Alt Fee)
feeLimitbigintNo*Token fee cap (required for Alt Fee)

Example:

const signedTx = await signer.signTransaction({
to: "0x78fE92CBB534C4d35A8cB0F3F2C58a5AA7DF2DA6",
chainId: 2910,
value: parseEther("0.001"),
gasLimit: 1000000n,
maxFeePerGas: 15316544n,
maxPriorityFeePerGas: 14116544n,
feeTokenID: 2,
feeLimit: 252637086960555000n,
});

console.log("Signed transaction:", signedTx);
signer.sendTransaction(tx)

Send standard or Alt Fee transactions.

Parameters: Same as signTransaction

Returns: Promise<TransactionResponse> - Tx response

Example:

const txResponse = await signer.sendTransaction({
to: "0x78fE92CBB534C4d35A8cB0F3F2C58a5AA7DF2DA6",
chainId: 2910,
value: parseEther("0.001"),
gasLimit: 1000000n,
maxFeePerGas: 15316544n,
maxPriorityFeePerGas: 14116544n,
feeTokenID: 3,
feeLimit: 252637086960555000n,
});

console.log("Transaction hash:", txResponse.hash);

// Wait for confirmation
const receipt = await txResponse.wait();
console.log("Confirmed in block:", receipt.blockNumber);
signer.estimateGas(tx)

Estimate gas, including Alt Fee support.

Parameters: Same as signTransaction

Returns: Promise<bigint> - Gas estimate

Example:

const gasEstimate = await signer.estimateGas({
to: "0x78fE92CBB534C4d35A8cB0F3F2C58a5AA7DF2DA6",
chainId: 2910,
value: parseEther("0.001"),
gasLimit: 1000000n,
maxFeePerGas: 15316544n,
maxPriorityFeePerGas: 14116544n,
feeTokenID: 3,
feeLimit: 252637086960555000n,
});

console.log("Estimated gas:", gasEstimate.toString());
signer.populateTransaction(tx)

Populate missing fields (nonce/gasLimit/etc.).

Parameters: Same as signTransaction

Returns: Promise<TransactionRequest> - Populated request

Example:

const populatedTx = await signer.populateTransaction({
to: "0x78fE92CBB534C4d35A8cB0F3F2C58a5AA7DF2DA6",
value: parseEther("0.001"),
feeTokenID: 3,
});

console.log("Populated tx:", populatedTx);
// Contains auto-filled nonce, gasLimit, chainId, etc.

@morph-network/ethers5

Ethers v5 adapter with the same API shape as @morph-network/ethers.

MorphSigner (Ethers v5)

import { providers } from "ethers";
import { MorphSigner } from "@morph-network/ethers5";

const provider = new providers.Web3Provider(window.ethereum);
const web3Signer = provider.getSigner();

// Wrap
const signer = MorphSigner.from(web3Signer);
import { ethers } from "ethers";

const signedTx = await signer.signTransaction({
to: "0x...",
chainId: 2910,
value: ethers.utils.parseEther("0.001"),
gasLimit: ethers.BigNumber.from(1000000),
maxFeePerGas: ethers.BigNumber.from(15316544),
maxPriorityFeePerGas: ethers.BigNumber.from(14116544),
feeTokenID: 2,
feeLimit: ethers.BigNumber.from("252637086960555000"),
});
const txResponse = await signer.sendTransaction({
to: "0x...",
chainId: 2910,
value: ethers.utils.parseEther("0.001"),
gasLimit: ethers.BigNumber.from(1000000),
maxFeePerGas: ethers.BigNumber.from(15316544),
maxPriorityFeePerGas: ethers.BigNumber.from(14116544),
feeTokenID: 3,
});
const gasEstimate = await signer.estimateGas({
to: "0x...",
chainId: 2910,
value: ethers.utils.parseEther("0.001"),
feeTokenID: 3,
feeLimit: ethers.BigNumber.from("252637086960555000"),
});

console.log("Estimated gas:", gasEstimate.toString());

@morph-network/chain

Chain definitions as a standalone package, so you can reuse Morph networks across libraries.

Example:

import { morphMainnet, morphHoodiTestnet } from "@morph-network/chain";

console.log(morphMainnet.id); // 2818
console.log(morphHoodiTestnet.rpcUrls.default.http[0]); // https://rpc-hoodi.morphl2.io