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 privateKey = "<YOUR_PRIVATE_KEY>";
const account = privateKeyToAccount(privateKey);

// 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.001"),
nonce,
gas: 100000n,
maxFeePerGas: 15000000n,
maxPriorityFeePerGas: 14000000n,
// Alt Fee fields
feeTokenID: 4, // Token Registry ID for USDT
feeLimit: 252637086960555000n, // Max token payment
});

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

sendAltFeeTransaction();
Interactive Demo (Morph Hoodi Testnet Only)
All operations are performed locally on this page. Your private key is not stored.
This demo sends a transaction on Morph Hoodi Testnet. You still need a small testnet balance for gas.

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.001"),
chainId: MORPH_HOODI_TESTNET.chainId,
gasLimit: 100000n,
maxFeePerGas: 15000000n,
maxPriorityFeePerGas: 14000000n,
// Alt Fee fields
feeTokenID: 4,
feeLimit: 252637086960555000n,
});

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

Ethers v5 quick start

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

async function main() {
// 1. Provider
const provider = new providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []);

// 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: utils.parseEther("0.001"),
chainId: MORPH_HOODI_TESTNET.chainId,
gasLimit: utils.hexlify(100000),
maxFeePerGas: utils.parseUnits("0.015", "gwei"),
maxPriorityFeePerGas: utils.parseUnits("0.014", "gwei"),
// Alt Fee fields
feeTokenID: 4,
feeLimit: utils.parseUnits("1", 6), // 1 USDT/USDC
});

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

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);
}
Query Supported Tokens
This query uses Morph Hoodi Testnet RPC.

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.morph.network"]

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(),
});
Chain Config Quick View
NameMorph Hoodi Testnet
Chain ID2910
RPChttps://rpc-hoodi.morph.network
Explorerhttps://explorer-hoodi.morph.network
Native CurrencyEthereum (ETH)

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: 4,
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: "0x...",
value: 10000000000000000n,
gas: 1000000n,
maxFeePerGas: 15316544n,
maxPriorityFeePerGas: 14116544n,
nonce: 5,
feeTokenID: 4,
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: 4,
// 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

numericCore(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 { numericCore } from "@morph-network/viem";

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

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

// In a transaction
const tx = {
feeTokenID: numericCore(6).hex(),
feeLimit: numericCore(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: 4,
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, parseEther } 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.001"),
feeTokenID: 4,
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)
feeLimitbigint | HexNo*Token fee cap (required for Alt Fee). Accepts bigint or hex string.

Example:

import { parseEther } from "ethers";

const signedTx = await signer.signTransaction({
to: "0x...",
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:

import { parseEther } from "ethers";

const txResponse = await signer.sendTransaction({
to: "0x...",
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:

import { parseEther } from "ethers";

const gasEstimate = await signer.estimateGas({
to: "0x...",
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:

import { parseEther } from "ethers";

const populatedTx = await signer.populateTransaction({
to: "0x...",
value: parseEther("0.001"),
feeTokenID: 3,
feeLimit: 1000000n, // Required for Alt Fee transactions
});

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 { utils } from "ethers";

const signedTx = await signer.signTransaction({
to: "0x...",
chainId: 2910,
value: utils.parseEther("0.001"),
gasLimit: utils.hexlify(1000000),
maxFeePerGas: utils.hexlify(15316544),
maxPriorityFeePerGas: utils.hexlify(14116544),
feeTokenID: 2,
feeLimit: "0x0381653dfd7f0f8", // hex string or bigint
});
import { utils } from "ethers";

const txResponse = await signer.sendTransaction({
to: "0x...",
chainId: 2910,
value: utils.parseEther("0.001"),
gasLimit: utils.hexlify(1000000),
maxFeePerGas: utils.hexlify(15316544),
maxPriorityFeePerGas: utils.hexlify(14116544),
feeTokenID: 3,
feeLimit: utils.parseUnits("1", 6), // Required! 1 USDT/USDC
});
import { utils } from "ethers";

const gasEstimate = await signer.estimateGas({
to: "0x...",
chainId: 2910,
value: utils.parseEther("0.001"),
feeTokenID: 3,
feeLimit: utils.parseUnits("1", 6),
});

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 { MORPH_MAINNET, MORPH_HOODI_TESTNET } from "@morph-network/chain";

console.log(MORPH_MAINNET.chainId); // 2818
console.log(MORPH_HOODI_TESTNET.rpcUrl); // https://rpc-hoodi.morphl2.io

Note: The viem-style chain objects (morphMainnet, morphHoodiTestnet) are only exported from @morph-network/viem, not from @morph-network/chain.