Complete guide for integrating Pons Network into your DApps. Build seamless cross-chain experiences with decentralized execution.
- Installation & Setup
- Understanding the Architecture
- Understanding Pons Fees
- Basic Examples
- Advanced Examples
- Frontend Integration
- Node.js Integration
- Running Your Own Operator
npm install @pons-network/pons.js viemimport { PonsClient, Chain } from '@pons-network/pons.js';
// Initialize Pons client
const pons = await PonsClient.create({
from: Chain.SEPOLIA, // Source chain
to: Chain.ARC_TESTNET, // Destination chain
sourceRpcUrl: process.env.SEPOLIA_RPC_URL,
destinationRpcUrl: process.env.ARC_RPC_URL,
});
// Get user's Smart Account address (deterministic across all chains!)
const smartAccount = await pons.calculateSmartAccountAddress(userAddress, 0n);
console.log(`Smart Account: ${smartAccount}`);Pons Network is a decentralized cross-chain execution layer.
┌─────────────────────────────────────────────────────────────────────────────┐
│ PONS NETWORK FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ SOURCE CHAIN PONS NETWORK DESTINATION CHAIN │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ │ │ │ │ │ │
│ │ 1. User signs │ │ 3. Message │ │ 5. Message │ │
│ │ action │ ──────► │ relayed │ ──────► │ indexed │ │
│ │ │ │ │ │ │ │
│ │ 2. Message │ │ 4. Attestation│ │ 6. Action │ │
│ │ sent │ │ verified │ │ executed │ │
│ │ │ │ │ │ │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
STEP 1-2: User signs action + sends message on source chain
STEP 3-4: Pons Network relays and verifies the message
STEP 5: Indexer indexes the message on destination chain
STEP 6: Resolver executes the user's signed action
| Component | Decentralization |
|---|---|
| Message Publishing | Anyone can publish transfer messages |
| Indexers | Permissionless - anyone can run an indexer |
| Resolvers | Permissionless - anyone can run a resolver |
| Smart Accounts | Non-custodial - only owner can authorize actions |
Anyone can join Pons Network as an operator and earn income:
- Indexers earn fees for indexing messages on destination chain
- Resolvers earn fees for executing actions
See Running Your Own Operator below.
Fees are dynamic - just like Ethereum gas! Pay more for faster execution, or save money with lower fees.
┌─────────────────────────────────────────────────────────────────────────────┐
│ DYNAMIC FEE MODEL │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ⚡ FAST - Pay above market rate │
│ → Operators prioritize your transaction │
│ → Fastest execution │
│ │
│ 🔄 STANDARD - Pay market rate │
│ → Normal execution speed │
│ → Balanced cost/speed │
│ │
│ 🐢 ECONOMY - Pay below market rate │
│ → Slower execution │
│ → Cheapest option │
│ │
│ Just like Ethereum gas - you choose speed vs cost! │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
User sends: 15.000000 USDC
│
├── Network Fee (~0.01%): Cross-chain relay
│
└── Expected Amount: Arrives at Smart Account
│
├── Protocol Fee: Pons treasury
├── Indexer Fee: Indexer operator (DYNAMIC)
├── Resolver Fee: Resolver operator (DYNAMIC)
│
└── Amount for Action: Your action
import { calculateFeesSync } from '@pons-network/pons.js';
import { parseUnits, formatUnits } from 'viem';
// Standard fees (market rate)
const standardFees = calculateFeesSync(parseUnits('15', 6));
console.log(`Standard: ${formatUnits(standardFees.amountForAction, 6)} USDC for action`);
// Fast execution (2x fees)
const fastFees = calculateFeesSync(parseUnits('15', 6), {
indexerFee: parseUnits('0.2', 6), // Higher indexer fee
resolverFee: parseUnits('0.3', 6), // Higher resolver fee
});
console.log(`Fast: ${formatUnits(fastFees.amountForAction, 6)} USDC for action`);
// Economy (0.5x fees - slower but cheaper)
const economyFees = calculateFeesSync(parseUnits('15', 6), {
indexerFee: parseUnits('0.05', 6), // Lower indexer fee
resolverFee: parseUnits('0.08', 6), // Lower resolver fee
});
console.log(`Economy: ${formatUnits(economyFees.amountForAction, 6)} USDC for action`);const fees = calculateFeesSync(parseUnits('15', 6));
// fees.amountForAction = how much available for your actionconst fees = calculateBurnForAction(parseUnits('10', 6));
// fees.burnAmount = how much user needs to sendSend USDC cross-chain to your Smart Account.
import { PonsClient, Chain, calculateFeesSync } from '@pons-network/pons.js';
import { parseUnits, formatUnits } from 'viem';
async function simpleBridge(walletClient: WalletClient, amount: string) {
const pons = await PonsClient.create({
from: Chain.SEPOLIA,
to: Chain.ARC_TESTNET,
sourceRpcUrl: process.env.SEPOLIA_RPC_URL!,
destinationRpcUrl: process.env.ARC_RPC_URL!,
});
const fees = calculateFeesSync(parseUnits(amount, 6));
console.log(`🌉 Cross-Chain Bridge`);
console.log(` Send: ${formatUnits(fees.burnAmount, 6)} USDC`);
console.log(` Receive: ${formatUnits(fees.amountForAction, 6)} USDC`);
// Send message on source chain
const result = await pons.execute({
amount: fees.burnAmount,
action: {
target: '0x0000000000000000000000000000000000000000',
callData: '0x',
value: 0n,
feeConfig: {
paymentToken: USDC_ADDRESS,
indexerFee: fees.indexerFee,
resolverFee: fees.resolverFee,
},
permit2Setup: [],
funding: {
ethNeeded: 0n,
tokensNeeded: [],
tokenAmounts: [],
maxReimbursement: 0n,
},
},
}, walletClient);
console.log(`✅ Message sent!`);
console.log(` TX: ${result.txHash}`);
console.log(` Smart Account: ${result.smartAccountAddress}`);
console.log(` Waiting for indexer and resolver...`);
// Track the decentralized execution
const tracker = pons.trackTransfer(
result.txHash,
result.smartAccountAddress,
result.nonce
);
tracker.on('statusChange', (status) => {
console.log(`📡 Status: ${status}`);
});
return result;
}Swap USDC on source chain for tokens on destination chain.
async function crossChainSwap(
walletClient: WalletClient,
usdcAmount: string,
minOutputAmount: bigint,
speedOption: 'fast' | 'standard' | 'economy' = 'standard'
) {
const pons = await PonsClient.create({
from: Chain.SEPOLIA,
to: Chain.ARC_TESTNET,
sourceRpcUrl: process.env.SEPOLIA_RPC_URL!,
destinationRpcUrl: process.env.ARC_RPC_URL!,
});
const smartAccount = await pons.calculateSmartAccountAddress(
walletClient.account.address,
0n
);
// Dynamic fees based on speed preference
const feeOptions = {
fast: { indexerFee: parseUnits('0.2', 6), resolverFee: parseUnits('0.3', 6) },
standard: { indexerFee: parseUnits('0.1', 6), resolverFee: parseUnits('0.15', 6) },
economy: { indexerFee: parseUnits('0.05', 6), resolverFee: parseUnits('0.08', 6) },
};
const fees = calculateFeesSync(parseUnits(usdcAmount, 6), feeOptions[speedOption]);
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800);
console.log(`🔄 Cross-Chain Swap (${speedOption} mode)`);
console.log(` Send: ${formatUnits(fees.burnAmount, 6)} USDC`);
console.log(` Swap: ${formatUnits(fees.amountForAction, 6)} USDC → WETH`);
const swapCalldata = encodeFunctionData({
abi: UNISWAP_ABI,
functionName: 'exactInputSingle',
args: [{
tokenIn: USDC_ADDRESS,
tokenOut: WETH_ADDRESS,
fee: 3000,
recipient: smartAccount,
deadline,
amountIn: fees.amountForAction,
amountOutMinimum: minOutputAmount,
sqrtPriceLimitX96: 0n,
}],
});
const result = await pons.execute({
amount: fees.burnAmount,
action: {
target: UNISWAP_ROUTER,
callData: swapCalldata,
value: 0n,
feeConfig: {
paymentToken: USDC_ADDRESS,
indexerFee: fees.indexerFee,
resolverFee: fees.resolverFee,
},
permit2Setup: [],
funding: {
ethNeeded: 0n,
tokensNeeded: [],
tokenAmounts: [],
maxReimbursement: fees.amountForAction,
},
},
}, walletClient);
console.log(`✅ Message sent! Decentralized operators will complete the swap.`);
return result;
}
// Usage with different speed options
await crossChainSwap(wallet, '15', minOutput, 'fast'); // Pay more, faster
await crossChainSwap(wallet, '15', minOutput, 'standard'); // Market rate
await crossChainSwap(wallet, '15', minOutput, 'economy'); // Pay less, slowerBuy an NFT on destination chain using USDC from source chain.
async function buyNFT(
walletClient: WalletClient,
nftContract: Address,
tokenId: bigint,
nftPriceEth: number,
ethPriceUsdc: number
) {
const pons = await PonsClient.create({
from: Chain.SEPOLIA,
to: Chain.ARC_TESTNET,
sourceRpcUrl: process.env.SEPOLIA_RPC_URL!,
destinationRpcUrl: process.env.ARC_RPC_URL!,
});
const nftPriceUsdc = nftPriceEth * ethPriceUsdc;
const ethNeeded = parseEther(nftPriceEth.toString());
const fees = calculateBurnForAction(parseUnits(nftPriceUsdc.toFixed(6), 6));
console.log(`🎨 Cross-Chain NFT Purchase`);
console.log(` NFT: ${nftPriceEth} ETH (~${nftPriceUsdc} USDC)`);
console.log(` User sends: ${formatUnits(fees.burnAmount, 6)} USDC`);
const mintCalldata = encodeFunctionData({
abi: NFT_ABI,
functionName: 'mint',
args: [tokenId],
});
const result = await pons.execute({
amount: fees.burnAmount,
action: {
target: nftContract,
callData: mintCalldata,
value: ethNeeded,
feeConfig: {
paymentToken: USDC_ADDRESS,
indexerFee: fees.indexerFee,
resolverFee: fees.resolverFee,
},
permit2Setup: [],
funding: {
ethNeeded: ethNeeded,
tokensNeeded: [],
tokenAmounts: [],
maxReimbursement: fees.amountForAction,
},
},
}, walletClient);
return result;
}Execute game actions cross-chain with fee optimization.
const ITEM_PRICES = {
sword: parseUnits('5', 6),
shield: parseUnits('3', 6),
potion: parseUnits('1', 6),
};
async function buyGameItem(
walletClient: WalletClient,
itemId: bigint,
itemPrice: bigint,
prioritize: boolean = false
) {
const pons = await PonsClient.create({
from: Chain.SEPOLIA,
to: Chain.ARC_TESTNET,
sourceRpcUrl: process.env.SEPOLIA_RPC_URL!,
destinationRpcUrl: process.env.ARC_RPC_URL!,
});
// Dynamic fees - pay more if you want the item faster!
const feeConfig = prioritize
? { indexerFee: parseUnits('0.2', 6), resolverFee: parseUnits('0.3', 6) }
: { indexerFee: parseUnits('0.1', 6), resolverFee: parseUnits('0.15', 6) };
const fees = calculateBurnForAction(itemPrice, feeConfig);
console.log(`🎮 Cross-Chain Game Purchase ${prioritize ? '(PRIORITY)' : ''}`);
console.log(` Item: ${formatUnits(itemPrice, 6)} USDC`);
console.log(` User sends: ${formatUnits(fees.burnAmount, 6)} USDC`);
const buyCalldata = encodeFunctionData({
abi: GAME_ABI,
functionName: 'buyItem',
args: [itemId],
});
const result = await pons.execute({
amount: fees.burnAmount,
action: {
target: GAME_CONTRACT,
callData: buyCalldata,
value: 0n,
feeConfig: {
paymentToken: USDC_ADDRESS,
indexerFee: fees.indexerFee,
resolverFee: fees.resolverFee,
},
permit2Setup: [],
funding: {
ethNeeded: 0n,
tokensNeeded: [USDC_ADDRESS],
tokenAmounts: [itemPrice],
maxReimbursement: fees.amountForAction,
},
},
}, walletClient);
return result;
}Execute multiple actions in a single cross-chain transaction.
async function batchSwapAndStake(walletClient: WalletClient, amount: string) {
const pons = await PonsClient.create({
from: Chain.SEPOLIA,
to: Chain.ARC_TESTNET,
sourceRpcUrl: process.env.SEPOLIA_RPC_URL!,
destinationRpcUrl: process.env.ARC_RPC_URL!,
});
const fees = calculateFeesSync(parseUnits(amount, 6));
console.log(`⚡ Cross-Chain Batch: Swap + Stake`);
console.log(` User sends: ${formatUnits(fees.burnAmount, 6)} USDC`);
const action = new ActionBuilder()
.addCall(USDC_ADDRESS, approveSwapCalldata)
.addCall(UNISWAP_ROUTER, swapCalldata)
.addCall(WETH_ADDRESS, approveStakeCalldata)
.addCall(STAKING_CONTRACT, stakeCalldata)
.withFees(USDC_ADDRESS, fees.indexerFee, fees.resolverFee)
.build(BigInt(Date.now()), BigInt(Math.floor(Date.now() / 1000) + 3600), fees.expectedAmount);
const result = await pons.execute({
amount: fees.burnAmount,
action: { /* ... */ },
}, walletClient);
return result;
}import { useWalletClient, useAccount } from 'wagmi';
import { PonsClient, Chain, calculateFeesSync, TransferStatus } from '@pons-network/pons.js';
import { useState } from 'react';
function usePons() {
const { data: walletClient } = useWalletClient();
const { address } = useAccount();
const [speedMode, setSpeedMode] = useState<'fast' | 'standard' | 'economy'>('standard');
const bridge = async (amount: string) => {
if (!walletClient) throw new Error('Not initialized');
const pons = await PonsClient.create({
from: Chain.SEPOLIA,
to: Chain.ARC_TESTNET,
sourceRpcUrl: process.env.NEXT_PUBLIC_SEPOLIA_RPC!,
destinationRpcUrl: process.env.NEXT_PUBLIC_ARC_RPC!,
});
// Dynamic fees based on user preference
const feeOptions = {
fast: { indexerFee: parseUnits('0.2', 6), resolverFee: parseUnits('0.3', 6) },
standard: { indexerFee: parseUnits('0.1', 6), resolverFee: parseUnits('0.15', 6) },
economy: { indexerFee: parseUnits('0.05', 6), resolverFee: parseUnits('0.08', 6) },
};
const fees = calculateFeesSync(parseUnits(amount, 6), feeOptions[speedMode]);
return await pons.execute({
amount: fees.burnAmount,
action: { /* ... */ },
}, walletClient);
};
return { bridge, speedMode, setSpeedMode };
}
// UI Component
function SpeedSelector({ value, onChange }) {
return (
<div>
<label>Execution Speed:</label>
<select value={value} onChange={(e) => onChange(e.target.value)}>
<option value="fast">⚡ Fast (higher fees)</option>
<option value="standard">🔄 Standard</option>
<option value="economy">🐢 Economy (lower fees)</option>
</select>
</div>
);
}import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { PonsClient, Chain, calculateFeesSync, TransferStatus } from '@pons-network/pons.js';
async function main() {
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Address);
const walletClient = createWalletClient({
account,
transport: http(process.env.SEPOLIA_RPC_URL),
});
const pons = await PonsClient.create({
from: Chain.SEPOLIA,
to: Chain.ARC_TESTNET,
sourceRpcUrl: process.env.SEPOLIA_RPC_URL!,
destinationRpcUrl: process.env.ARC_RPC_URL!,
});
const fees = calculateFeesSync(parseUnits('100', 6));
console.log(`Sending message on source chain...`);
const result = await pons.execute({
amount: fees.burnAmount,
action: { /* ... */ },
}, walletClient);
console.log(`TX: ${result.txHash}`);
console.log(`Waiting for message to be indexed on destination...`);
const tracker = pons.trackTransfer(
result.txHash,
result.smartAccountAddress,
result.nonce
);
tracker.on('statusChange', (status) => {
console.log(`Status: ${status}`);
if (status === TransferStatus.EXECUTED) {
console.log('✅ Action executed!');
pons.stop();
process.exit(0);
}
});
}
main().catch(console.error);Pons Network is permissionless - anyone can run an indexer or resolver and earn fees!
- Earn fees - Dynamic fees based on market demand
- Support decentralization - More operators = more resilient network
- No permission needed - Just run the software and start earning
Indexers monitor messages and index them on the destination chain.
# Clone the resolver
git clone https://github.com/pons-network/resolver
cd resolver
# Configure environment
cp env.example .env
# Edit .env with your RPC URLs and private key
# Run as indexer
docker-compose --profile indexer up -dResolvers execute user actions after messages are indexed.
# Run as resolver (executor)
docker-compose --profile executor up -d# Run as full resolver (indexer + resolver)
docker-compose --profile both up -dOperators compete in a free market:
- Users who pay higher fees get prioritized
- Operators choose which transactions to process based on profitability
- Market equilibrium finds fair prices based on supply/demand
Track your cross-chain transfers through these stages:
| Status | Description |
|---|---|
PENDING |
Waiting for source chain confirmation |
SENT |
Message sent on source chain |
ATTESTED |
Attestation verified |
INDEXING |
Indexer processing |
INDEXED |
Message indexed on destination |
EXECUTING |
Resolver executing action |
EXECUTED |
✅ Complete! |
FAILED |
❌ Action failed |
| Traditional Approach | Pons Network |
|---|---|
| Centralized bridges | Decentralized operators |
| Fixed fees | Dynamic fees (like ETH gas) |
| Single points of failure | Permissionless & resilient |
| Switch networks | Stay on your chain |
| Multiple transactions | One signature |
| Need gas on each chain | Fees in USDC only |
Pons Network provides a decentralized execution layer, giving your users a seamless multi-chain experience.