TypeScript SDK for creating and signing Signet orders compatible with the Rust signet-types crate.
pnpm add @signet-sh/sdk viemimport { UnsignedOrder, orderHash, MAINNET } from "@signet-sh/sdk";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
// Create a wallet client
const account = privateKeyToAccount("0x...");
const client = createWalletClient({
account,
chain: mainnet,
transport: http(),
});
// Build and sign an order
const signedOrder = await UnsignedOrder.new()
.withInput("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 1000000n) // 1 USDC
.withOutput(
"0x0000000000000000000000000000000000000000", // Native token
500000000000000000n, // 0.5 ETH
account.address,
1 // Mainnet
)
.withDeadline(BigInt(Math.floor(Date.now() / 1000) + 3600)) // 1 hour
.withChain({
chainId: MAINNET.rollupChainId,
orderContract: MAINNET.rollupOrders,
})
.sign(client);
// Compute the order hash
const hash = orderHash(signedOrder);import { orderHash } from "@signet-sh/sdk";
import type { SignedOrder } from "@signet-sh/sdk";
const order: SignedOrder = {
permit: {
permit: {
permitted: [{ token: "0x...", amount: 1000000n }],
nonce: 12345n,
deadline: 1700000000n,
},
owner: "0x...",
signature: "0x...",
},
outputs: [
{
token: "0x...",
amount: 500000n,
recipient: "0x...",
chainId: 1,
},
],
};
const hash = orderHash(order);After signing an order, submit it to the Signet transaction pool for fillers to discover and execute:
import { createTxCacheClient } from "@signet-sh/sdk/client";
// Create a client pointing to the Signet tx-cache
const txCache = createTxCacheClient("https://tx.signet.sh");
// Submit a signed order
const { id } = await txCache.submitOrder(signedOrder);
console.log(`Order submitted with ID: ${id}`);The SDK exports ABIs and constants for on-chain operations. Use viem directly for contract interactions:
import { PERMIT2_ADDRESS } from "@signet-sh/sdk";
import { erc20Abi, maxUint256 } from "viem";
// Approve Permit2 to spend your tokens (one-time per token)
await walletClient.writeContract({
address: tokenAddress,
abi: erc20Abi,
functionName: "approve",
args: [PERMIT2_ADDRESS, maxUint256],
});
// Check current allowance
const allowance = await publicClient.readContract({
address: tokenAddress,
abi: erc20Abi,
functionName: "allowance",
args: [ownerAddress, PERMIT2_ADDRESS],
});For tokens like USDT that require resetting allowance to zero before setting a new value, use ensurePermit2Approval:
import { ensurePermit2Approval } from "@signet-sh/sdk";
// Handles USDT-style tokens automatically
const { approved, txHash } = await ensurePermit2Approval(
walletClient,
publicClient,
{
token: usdtAddress,
owner: account.address,
amount: 1000000n,
}
);import { passageAbi, MAINNET } from "@signet-sh/sdk";
// Bridge native ETH to Signet
await walletClient.writeContract({
address: MAINNET.hostPassage,
abi: passageAbi,
functionName: "enter",
args: [recipientAddress],
value: 1000000000000000000n, // 1 ETH
});
// Bridge ERC20 tokens to Signet (requires prior token approval to Passage)
await walletClient.writeContract({
address: MAINNET.hostPassage,
abi: passageAbi,
functionName: "enterToken",
args: [MAINNET.rollupChainId, recipientAddress, tokenAddress, amount],
});import { wethAbi, getTokenAddress, MAINNET } from "@signet-sh/sdk";
const wethAddress = getTokenAddress("WETH", MAINNET.hostChainId, MAINNET)!;
// Wrap ETH → WETH
await walletClient.writeContract({
address: wethAddress,
abi: wethAbi,
functionName: "deposit",
value: 1000000000000000000n, // 1 ETH
});
// Unwrap WETH → ETH
await walletClient.writeContract({
address: wethAddress,
abi: wethAbi,
functionName: "withdraw",
args: [1000000000000000000n], // 1 WETH
});import { rollupOrdersAbi, MAINNET } from "@signet-sh/sdk";
const { witnessHash, witnessTypeString } = await publicClient.readContract({
address: MAINNET.rollupOrders,
abi: rollupOrdersAbi,
functionName: "outputWitness",
args: [outputs],
});import { MAINNET, PARMIGIANA } from "@signet-sh/sdk";
// Mainnet configuration
console.log(MAINNET.hostChainId); // 1n
console.log(MAINNET.rollupChainId); // 519n
// Parmigiana testnet
console.log(PARMIGIANA.hostChainId); // 3151908n
console.log(PARMIGIANA.rollupChainId); // 88888n// Import specific modules for smaller bundle sizes
import { MAINNET, PARMIGIANA } from "@signet-sh/sdk/constants";
import { UnsignedOrder } from "@signet-sh/sdk/signing";
import type { SignedOrder } from "@signet-sh/sdk/types";
import { rollupOrdersAbi, passageAbi, wethAbi } from "@signet-sh/sdk/abi";
import { createTxCacheClient } from "@signet-sh/sdk/client";
import { ensurePermit2Approval } from "@signet-sh/sdk/permit2";
⚠️ SECURITY WARNING: Bundles are private information and MUST be kept safe.Never log, share, or transmit bundles over unencrypted connections. Leaked bundles expose your trading strategy and can be front-run.
Bundles are atomic transaction packages for block builders:
import {
SignetEthBundleBuilder,
createTxCacheClient,
type Hex,
} from "@signet-sh/sdk";
// Build a bundle from signed transactions
const signedTx: Hex = "0x02f8..."; // Your signed transaction
const bundle = SignetEthBundleBuilder.new()
.withTx(signedTx)
.withBlockNumber(12345678n)
.build();
// Submit to tx-cache
const txCache = createTxCacheClient("https://tx.signet.sh");
const response = await txCache.submitBundle(bundle);
console.log("Bundle ID:", response.id);Bundle with host transactions and timing constraints:
const bundle = SignetEthBundleBuilder.new()
.withTxs([tx1, tx2]) // Rollup transactions
.withHostTx(hostTx) // Host chain transaction
.withBlockNumber(12345678n)
.withMinTimestamp(Math.floor(Date.now() / 1000))
.withMaxTimestamp(Math.floor(Date.now() / 1000) + 120)
.build();Simulate a bundle before submission:
import { SignetCallBundleBuilder, serializeCallBundle } from "@signet-sh/sdk";
const callBundle = SignetCallBundleBuilder.new()
.withTx(signedTx)
.withBlockNumber(12345679n)
.withStateBlockNumber("latest")
.build();
const serialized = serializeCallBundle(callBundle);
// Use with signet_callBundle RPC methodThe SDK exports ABIs and token utilities. Use viem directly for RPC operations.
import { createPublicClient, http, getContract } from "viem";
import {
passageAbi,
PARMIGIANA,
getTokenAddress,
resolveTokenSymbol,
} from "@signet-sh/sdk";
const client = createPublicClient({ chain, transport: http(rpcUrl) });
const passage = getContract({
address: PARMIGIANA.hostPassage,
abi: passageAbi,
client,
});
// Get Enter events (native ETH entries)
const enterLogs = await passage.getEvents.Enter({
rollupChainId: PARMIGIANA.rollupChainId,
rollupRecipient: userAddress,
});
// Get EnterToken events (ERC20 entries)
const enterTokenLogs = await passage.getEvents.EnterToken({
rollupChainId: PARMIGIANA.rollupChainId,
rollupRecipient: userAddress,
});
// Resolve token symbols from logs
for (const log of enterTokenLogs) {
const symbol = resolveTokenSymbol(
log.args.token,
PARMIGIANA.hostChainId,
PARMIGIANA
);
console.log(`Entered ${log.args.amount} of ${symbol}`);
}import { createPublicClient, http, erc20Abi, formatUnits } from "viem";
import { PARMIGIANA, getTokenAddress, getTokenDecimals } from "@signet-sh/sdk";
const client = createPublicClient({ chain, transport: http(rpcUrl) });
// Fetch native ETH balance
const ethBalance = await client.getBalance({ address: userAddress });
// Fetch ERC20 balance
const wethAddress = getTokenAddress("WETH", PARMIGIANA.hostChainId, PARMIGIANA);
const wethBalance = await client.readContract({
address: wethAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [userAddress],
});
// Format with correct decimals (uses testnet override if available)
const decimals = getTokenDecimals("WETH", PARMIGIANA);
const formatted = formatUnits(wethBalance, decimals);import { getTokenAddress, getTokenDecimals } from "@signet-sh/sdk";
const tokens = ["WETH", "WBTC", "USDC"] as const;
const balances = await Promise.all(
tokens.map(async (symbol) => {
const address = getTokenAddress(symbol, chainId, config);
if (!address) return { symbol, balance: 0n };
const balance = await client.readContract({
address,
abi: erc20Abi,
functionName: "balanceOf",
args: [userAddress],
});
return { symbol, balance, decimals: getTokenDecimals(symbol, config) };
})
);SignedOrder- A signed order ready for submissionSignedFill- A signed fill for filling ordersSignetEthBundle- Bundle forsignet_sendBundleSignetCallBundle- Bundle forsignet_callBundle(simulation)Permit2Batch- Permit2 batch transfer dataOutput- Order output specificationTokenPermissions- Token permission for Permit2Flow- Entry mechanism type:"passage"or"orders"TokenSymbol- Supported token symbols
orderHash(order)- Compute the order hashorderHashPreImage(order)- Get the pre-image used for hashingnormalizeSignature(sig)- Normalize ECDSA signature S-valueserializeEthBundle(bundle)- Serialize bundle for JSON-RPCserializeCallBundle(bundle)- Serialize call bundle for JSON-RPCcreateTxCacheClient(url)- Create a tx-cache client for bundle submissionensurePermit2Approval(walletClient, publicClient, params)- Smart Permit2 approval with USDT handlinggetTokenDecimals(symbol, config?)- Get token decimals with chain-specific overridesneedsWethWrap(symbol, direction, flow)- Check if ETH needs wrapping for operation
UnsignedOrder- Builder for creating unsigned ordersUnsignedFill- Builder for creating unsigned fillsSignetEthBundleBuilder- Builder for creating eth bundlesSignetCallBundleBuilder- Builder for creating call bundles
createTxCacheClient(baseUrl)- Create a client for submitting orders to the transaction poolsubmitOrder(order)- Submit a signed order
PERMIT2_ADDRESS- Canonical Permit2 contract addressMAINNET- Mainnet chain configurationPARMIGIANA- Parmigiana testnet configuration
passageAbi- Passage bridge contract ABIwethAbi- WETH contract ABIrollupOrdersAbi- Rollup orders contract ABIpermit2Abi- Permit2 contract ABI
This SDK produces signed orders that are byte-for-byte compatible with the Rust signet-types crate. The order hash computation matches exactly, ensuring interoperability between TypeScript and Rust implementations.
pnpm install # Install dependencies
pnpm build # Build the package
pnpm test # Run tests in watch mode
pnpm test:run # Run tests once
pnpm lint # Run ESLint
pnpm format # Format with Prettier
pnpm typecheck # Type checkMIT OR Apache-2.0