@tapforce/pod-bridge-sdk
v2.0.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: Deposit 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
- 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);
// Create deposit transaction (with optional permit for gasless approval)
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
});
// 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)
Deposits 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: Deposit on Pod
const depositTx = client.deposit({
token: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // Native token on Pod
amount: ethers.parseUnits('1', 6), // Use ETH-side decimals (1 USDC = 1e6)
destinationWalletAddress: '0x...',
from: '0x...',
});
// Pod requires EIP-1559 gas params (all zeros for free transactions)
const tx = await podSigner.sendTransaction({
...depositTx,
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 deposited 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;
from?: string;
permit?: string; // Optional ERC20 permit bytes (default: '0x')
}): UnsignedTransactionPodToSourceChainActionClient
For Pod -> ETH deposits and claims.
// Deposit tokens on Pod
deposit(args: {
token: string; // Use 0xEeee...EEeE for native token
amount: string | bigint; // Use ETH-side decimals
destinationWalletAddress: string;
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;
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);
event Claim(bytes32 indexed txHash, address token, address mirrorToken, uint256 amount, address indexed to);
// Pod
event Deposit(bytes32 indexed id, address indexed from, address indexed to, address token, uint256 amount);ABIs
The SDK exports separate ABIs for each chain:
SOURCE_CHAIN_BRIDGE_ABI- ETH bridge (deposit with permit, claim with proof)POD_BRIDGE_ABI- Pod bridge (3-param deposit)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
