@tapforce/pod-bridge-sdk
v2.2.1
Published
SDK for interacting with Bridges between pod and other chains
Readme
Pod Bridge SDK
TypeScript SDK for bridging tokens between POD and EVM chains (e.g., ETH Mainnet).
Architecture
ETH -> Pod: Deposit on ETH, AUTO-CLAIM on Pod (no claim TX needed)
Pod -> ETH: Withdraw on Pod, claim on ETH with proof from pod_getBridgeClaimProof RPCKey points:
- ERC20 and native token bridging supported
- Token addresses differ between chains (e.g., USDC on ETH maps to native
0xEeee...EEeEon Pod) - Use ETH-side decimals for amounts (e.g.,
1e6for 1 USDC, not1e18) - Pod transactions require EIP-1559 (type 2) gas params, not legacy
gasPrice
Features
- Bridge Deposits: Deposit ERC20 or native tokens to the bridge (with optional CLOB integration)
- Claim with Proof: Claim on ETH using
pod_getBridgeClaimProofRPC - Track Bridge Requests: Query deposits sent/received by any address
- Claim Status Tracking: Monitor claim status and finality
- Transaction Building: Generate unsigned transactions ready for signing
Installation
npm install @tapforce/pod-bridge-sdk
# or
yarn add @tapforce/pod-bridge-sdk
# or
pnpm add @tapforce/pod-bridge-sdkQuick Start
import {
SourceChainToPodActionClient,
PodToSourceChainActionClient,
PodBridgeTrackerClient,
getBridgeClaimProof,
SOURCE_CHAIN_BRIDGE_ABI,
POD_BRIDGE_ABI,
} from '@tapforce/pod-bridge-sdk';Configuration
For Action Clients (creating transactions)
import { PodBridgeActionsClientConfig } from '@tapforce/pod-bridge-sdk';
const actionConfig: PodBridgeActionsClientConfig = {
sourceChain: {
contractAddress: '0x...' // BridgeDepositWithdraw on ETH
},
pod: {
contractAddress: '0x0000000000000000000000000000000000B41D9E' // BridgeMintBurn on Pod
}
};For Tracker Client (querying events)
import { PodBridgeConfig } from '@tapforce/pod-bridge-sdk';
import { ethers } from 'ethers';
const trackerConfig: PodBridgeConfig = {
sourceChain: {
provider: new ethers.JsonRpcProvider('https://eth-mainnet.rpc.example'),
contractAddress: '0x...',
deploymentBlock: 12345678 // Optional: avoids indexing empty blocks
},
pod: {
provider: new ethers.JsonRpcProvider('https://rpc.pod.network'),
contractAddress: '0x0000000000000000000000000000000000B41D9E',
deploymentBlock: 0
}
};Usage
ETH -> Pod (Auto-claim)
Deposits on ETH are automatically claimed on Pod after block finalization.
const client = new SourceChainToPodActionClient(actionConfig);
// Simple deposit (bridge only)
const depositTx = client.deposit({
token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on ETH
amount: ethers.parseUnits('1', 6), // 1 USDC (use ETH-side decimals)
destinationWalletAddress: '0x...',
from: '0x...',
// permit: '0x...' // Optional: ERC20 permit bytes
});
// CLOB deposit — bridge and deposit to orderbook in one TX:
// const clobTx = client.deposit({
// token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT on ETH
// amount: ethers.parseUnits('1', 6),
// destinationWalletAddress: '0x...',
// callContract: '0x000000000000000000000000000000000000C10B', // CLOB orderbook
// reserveBalance: ethers.parseUnits('0.5', 6), // Keep 0.5, forward 0.5 to CLOB
// from: '0x...',
// });
// Don't forget to approve the bridge contract first for ERC20 tokens
const tx = await signer.sendTransaction(depositTx);
const receipt = await tx.wait();
// No claim needed - balance auto-credited on Pod after finalizationPod -> ETH (Claim with proof)
Withdrawals on Pod require claiming on ETH with a proof from pod_getBridgeClaimProof.
import {
PodToSourceChainActionClient,
getBridgeClaimProof,
ClaimProofData,
} from '@tapforce/pod-bridge-sdk';
const client = new PodToSourceChainActionClient(actionConfig);
// Step 1: Withdraw on Pod (specify target chain ID)
const withdrawTx = client.withdraw({
token: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // Native token on Pod
amount: ethers.parseUnits('1', 6), // Target chain units (1 USDC = 1e6)
destinationWalletAddress: '0x...',
chainId: 1, // Target chain ID (e.g. 1 for ETH mainnet)
from: '0x...',
});
// Pod requires EIP-1559 gas params (all zeros for free transactions)
const tx = await podSigner.sendTransaction({
...withdrawTx,
maxFeePerGas: 0n,
maxPriorityFeePerGas: 0n,
gasLimit: 0n,
});
// Step 2: Get claim proof from Pod RPC
const { proof, auxTxSuffix } = await getBridgeClaimProof(podProvider, tx.hash);
// Step 3: Claim on ETH
const claimData: ClaimProofData = {
ethTokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on ETH
amount: ethers.parseUnits('1', 6), // Must match withdrawn amount
to: '0x...',
proof,
auxTxSuffix,
};
const claimTx = client.claim({ claimData, from: '0x...' });
const claim = await ethSigner.sendTransaction(claimTx);Tracking Deposits
const tracker = new PodBridgeTrackerClient(trackerConfig);
// Get all deposits for an address
const deposits = await tracker.getAllDepositsFor('0x...');
for (const deposit of deposits) {
console.log('Request ID:', deposit.requestId);
console.log('Direction:', deposit.deposit.chain === 'sourceChain' ? 'ETH -> Pod' : 'Pod -> ETH');
console.log('Token:', deposit.deposit.token);
console.log('Amount:', deposit.deposit.amount);
console.log('Timestamp:', new Date(deposit.deposit.timestamp * 1000).toISOString());
console.log('Status:', deposit.isClaimed ? 'Claimed' : (deposit.isClaimable ? 'Claimable' : 'Pending'));
}
// Get deposits sent/received by address
const sent = await tracker.getDepositsSentBy('0x...');
const received = await tracker.getDepositsReceivedBy('0x...');
// Check if deposit can be claimed
const canClaim = await tracker.canBeClaimed(deposit);
// Batch check processed status
const statuses = await tracker.areRequestsProcessed(deposits);API Reference
SourceChainToPodActionClient
For ETH -> Pod deposits (auto-claim on Pod).
deposit(args: {
token: string;
amount: string | bigint;
destinationWalletAddress: string;
callContract?: string; // Contract to call on Pod after deposit (default: address(0))
reserveBalance?: string | bigint; // Amount to reserve when using callContract (default: 0)
from?: string;
permit?: string; // Optional ERC20 permit bytes (default: '0x')
}): UnsignedTransactionPodToSourceChainActionClient
For Pod -> ETH withdrawals and claims.
// Withdraw tokens on Pod
withdraw(args: {
token: string; // Use 0xEeee...EEeE for native token
amount: string | bigint; // Target chain units (e.g. 1e6 for USDC)
destinationWalletAddress: string;
chainId: number | bigint; // Target chain ID for claiming
from?: string;
}): UnsignedTransaction
// Claim on ETH with proof
claim(args: {
claimData: ClaimProofData;
from?: string;
}): UnsignedTransactiongetBridgeClaimProof
Helper to call pod_getBridgeClaimProof RPC and format the result.
import { getBridgeClaimProof } from '@tapforce/pod-bridge-sdk';
const { proof, committeeEpoch, auxTxSuffix } = await getBridgeClaimProof(
podProvider, // ethers.JsonRpcProvider connected to Pod RPC
depositTxHash // TX hash of the deposit on Pod
);PodBridgeTrackerClient
getDepositsSentBy(address: string, fromBlock?: number): Promise<BridgeRequest[]>
getDepositsReceivedBy(address: string, fromBlock?: number): Promise<BridgeRequest[]>
getAllDepositsFor(address: string, fromBlock?: number): Promise<BridgeRequestWithType[]>
canBeClaimed(deposit: BridgeRequest): Promise<boolean>
areRequestsProcessed(deposits: BridgeRequest[]): Promise<boolean[]>Types
ClaimProofData
interface ClaimProofData {
ethTokenAddress: string; // Token address on ETH (different from Pod)
amount: string | bigint; // Must match deposited amount
to: string; // Recipient address
proof: string; // Hex bytes from pod_getBridgeClaimProof
auxTxSuffix: string; // Hex bytes from pod_getBridgeClaimProof
}BridgeRequest
interface BridgeRequest {
requestId: string;
deposit: {
chain: 'sourceChain' | 'pod';
txHash: string;
depositor: string;
destination: string;
token: string;
amount: string;
callContract?: string; // Contract called after deposit (address(0) for simple bridge) — ETH only
reserveBalance?: string; // Amount reserved when using callContract ('0' for simple bridge) — ETH only
targetChainId?: number; // Target chain ID for claiming (Pod→ETH direction)
chainId: number;
blockNumber: number;
timestamp: number; // Unix timestamp (seconds)
};
claim?: {
chain: 'sourceChain' | 'pod';
txHash: string;
claimer: string;
chainId: number;
blockNumber: number;
timestamp: number;
};
isClaimed: boolean;
isClaimable: boolean;
}Events
The bridge contracts emit different events per chain:
// ETH (Source Chain)
event Deposit(uint256 indexed id, address indexed from, address indexed to, address token, uint256 amount, address callContract, uint256 reserveBalance);
event Claim(bytes32 indexed txHash, address token, address mirrorToken, uint256 amount, address indexed to);
// Pod
event Withdraw(bytes32 indexed id, address indexed from, address indexed to, address token, uint256 amount, uint256 chainId);ABIs
The SDK exports separate ABIs for each chain:
SOURCE_CHAIN_BRIDGE_ABI- ETH bridge (6-param deposit with callContract/reserveBalance/permit, claim with proof)POD_BRIDGE_ABI- Pod bridge (withdraw with chainId, Withdraw events with bytes32 id)BRIDGE_ABI- Alias forSOURCE_CHAIN_BRIDGE_ABI
Pod-specific Notes
- Pod system contract address:
0x0000000000000000000000000000000000B41D9E - Pod transactions are free: use
maxFeePerGas: 0, maxPriorityFeePerGas: 0, gasLimit: 0 - Must use EIP-1559 (type 2) transactions, not legacy
gasPrice - Pod does NOT need
valueset for native token deposits - the system contract handles balance internally - Pod returns
blockNumberas a Unix timestamp in receipts
Development
npm run build # Build
npm run clean # CleanDependencies
- ethers: ^6.15.0
License
ISC
