Swap
The Swap API lets agents convert any supported token into USDC (or another stablecoin) as part of a payment flow. Same-chain swaps use Jupiter (Solana) or LI.FI (EVM); cross-chain swaps route through LI.FI's bridging layer. After the swap transaction is signed and broadcast, register the source transaction hash with POST /api/swap/intents to have Cross402 track and settle the receiving leg.
ExecuteSwap
Execute a token swap on behalf of the authenticated agent's Privy-hosted wallet. No private key or transaction signing is required — the backend handles quoting, ERC-20 approval, and broadcasting via Privy.
POST /v2/swap/execute
Authentication required. Use your API key in the Authorization: Bearer <base64(apiKey:secretKey)> header.
This endpoint is EVM-only. The agent must have an active Privy-hosted wallet (wallet_address and a Privy wallet ID set on the account).
Request Body
Response (200)
{
"tx_hash": "0xabc123...",
"chain": "base",
"from_token": "0x4200000000000000000000000000000000000006",
"to_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"from_amount": "1000000000000000000",
"estimated_output": "2498123456"
}
SDK Examples
JavaScript / TypeScript
import { PayClient } from "@cross402/usdc/server";
const client = new PayClient({
baseUrl: "https://api-pay.agent.tech",
auth: { apiKey: "your-api-key", secretKey: "your-secret-key" },
});
const result = await client.executeSwap({
chain: "base",
fromToken: "0x4200000000000000000000000000000000000006", // WETH
toToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
fromAmount: 1_000_000_000_000_000_000, // 1 WETH in wei
});
console.log("tx:", result.txHash);
console.log("estimated output:", result.estimatedOutput);
Go
import pay "github.com/cross402/usdc-sdk-go"
client, _ := pay.NewClient(
"https://api-pay.agent.tech",
pay.WithBearerAuth("your-api-key", "your-secret-key"),
)
resp, err := client.ExecuteSwap(ctx, &pay.ExecuteSwapRequest{
Chain: "base",
FromToken: "0x4200000000000000000000000000000000000006",
ToToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
FromAmount: 1_000_000_000_000_000_000,
})
if err != nil {
log.Fatal(err)
}
fmt.Println("tx:", resp.TxHash)
fmt.Println("estimated output:", resp.EstimatedOutput)
Error Reference
GetSwapQuote
Fetches a price quote for a token swap.
GET /api/swap/quote
Exactly one of from_amount or to_amount must be set. from_amount requests an ExactIn quote (you spend a fixed input); to_amount requests an ExactOut quote (you receive a fixed output).
When user_address is supplied the response includes a ready-to-sign swap_transaction. Omit it to get a quote only.
Query Parameters
Legacy alias:
amountis accepted as a deprecated alias forfrom_amount. Do not mixamountwithfrom_amountorto_amountin the same request.
Response
{
"quote": {
"input_token": "So11111111111111111111111111111111111111112",
"output_token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"input_amount": "1000000000",
"output_amount": "25000000",
"min_output_amount": "24875000",
"price_impact_pct": "0.05"
},
"swap_transaction": {
"transaction": "<base64 or hex>",
"expires_at": 1748649600,
"last_valid_block_height": 12345678
}
}
quote fields
swap_transaction fields (only when user_address is provided)
SDK Examples
JavaScript / TypeScript
import { PublicPayClient } from "@cross402/usdc";
const client = new PublicPayClient({ baseUrl: "https://api-pay.agent.tech" });
// ExactIn: swap 1 SOL → USDC on Solana (quote only)
const { quote } = await client.getSwapQuote({
chain: "solana",
inputToken: "So11111111111111111111111111111111111111112",
outputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
fromAmount: 1_000_000_000,
});
console.log("expected out:", quote.outputAmount, "min:", quote.minOutputAmount);
// ExactIn with transaction (ready to sign)
const result = await client.getSwapQuote({
chain: "base",
inputToken: "0x4200000000000000000000000000000000000006", // WETH
outputToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
fromAmount: 1_000_000_000_000_000_000n,
userAddress: "0xYourEVMWallet",
});
// result.swapTransaction contains { transaction, to, value, gasLimit, expiresAt }
// Cross-chain: Base → Solana
const crossQuote = await client.getSwapQuote({
chain: "base",
inputToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
outputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
fromAmount: 10_000_000, // 10 USDC
toChain: "solana",
userAddress: "0xYourEVMWallet",
toUserAddress: "YourSolanaWalletPublicKey",
});
Go
import pay "github.com/cross402/usdc-sdk-go"
client := pay.New(pay.WithBaseURL("https://api-pay.agent.tech"))
// ExactIn: swap 1 SOL → USDC on Solana (quote only)
resp, err := client.GetSwapQuote(ctx, &pay.SwapQuoteParams{
Chain: "solana",
InputToken: "So11111111111111111111111111111111111111112",
OutputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
FromAmount: 1_000_000_000,
})
fmt.Println("min out:", resp.Quote.MinOutputAmount)
// ExactIn with transaction
resp, err := client.GetSwapQuote(ctx, &pay.SwapQuoteParams{
Chain: "base",
InputToken: "0x4200000000000000000000000000000000000006",
OutputToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
FromAmount: 1_000_000_000_000_000_000,
UserAddress: "0xYourEVMWallet",
})
// resp.SwapTransaction != nil — sign and broadcast it
GetSwapApproval
Checks whether the user's wallet has sufficient ERC-20 allowance for the swap and, if not, returns the approval transaction to sign first.
GET /api/swap/approval
EVM only. Solana does not require token approvals.
Query Parameters
Response
{
"request_id": "abc123",
"needs_approval": true,
"gas_fee": "0.001",
"cancel_gas_fee": "0.0005",
"approval": {
"to": "0xTokenContract",
"from": "0xYourWallet",
"data": "0x...",
"value": "0x0",
"chain_id": 8453,
"gas_limit": "0x186A0"
},
"cancel": { ... }
}
When needs_approval is false, the approval field is absent — proceed directly to GetSwapQuote with user_address.
GetSwapStatus
Polls the status of a submitted cross-chain swap. Backed by LI.FI's /v1/status endpoint.
GET /api/swap/status
Returns 404 when LI.FI has no record yet. Retry after source-chain settlement (typically 30 s+).
Query Parameters
Response
{
"status": "DONE",
"substatus": "COMPLETED",
"message": "Transfer completed",
"tool": "lifi",
"source_tx_hash": "0xabc...",
"dest_tx_hash": "0xdef...",
"received_amount": "9980000",
"explorer_link": "https://..."
}
Possible status values: PENDING, DONE, FAILED, INVALID, NOT_FOUND.
RegisterSwapIntent
After the user signs and broadcasts the swap transaction, call this endpoint to register it as a Cross402 payment intent. The backend monitors the source chain, tracks cross-chain settlement, and makes the intent queryable via the standard intent endpoints.
POST /api/swap/intents
Request Body
All fields are required.
Response (201)
{
"intent_id": "int_abc123",
"status": "PENDING"
}
Use intent_id with GET /v2/intents/{intent_id} (authenticated) or GET /api/intents/{intent_id} (public) to poll settlement. Terminal statuses are DONE and FAILED; CANCELED means the job was dropped.
SDK Examples
JavaScript / TypeScript
const { intentId, status } = await client.registerSwapIntent({
sourceTxHash: "0xabc...",
fromChain: "base",
toChain: "solana",
fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
toToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
payerAddress: "0xYourEVMWallet",
recipientAddress: "YourSolanaWalletPublicKey",
sendingTokenAmount: "10000000",
});
console.log(intentId, status); // "PENDING"
Go
resp, err := client.RegisterSwapIntent(ctx, &pay.RegisterSwapIntentRequest{
SourceTxHash: "0xabc...",
FromChain: "base",
ToChain: "solana",
FromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
ToToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
PayerAddress: "0xYourEVMWallet",
RecipientAddress: "YourSolanaWalletPublicKey",
SendingTokenAmount: "10000000",
})
fmt.Println(resp.IntentID, resp.Status) // "PENDING"
Discovery Endpoints
These three endpoints are LI.FI passthroughs that return raw JSON. Use them to enumerate tokens, chains, and routes before building a swap UI.
GetSwapTokens
GET /api/swap/tokens
Returns LI.FI's /v1/tokens response. Useful for building a token picker.
JS/TS:
const tokens = await client.getSwapTokens({ chains: "8453,137" });
Go:
raw, err := client.GetSwapTokens(ctx, "8453,137", "")
// raw is []byte of the LiFi JSON response
GetSwapChains
GET /api/swap/chains
Returns LI.FI's /v1/chains response. Useful for building a chain picker.
JS/TS:
const chains = await client.getSwapChains();
Go:
raw, err := client.GetSwapChains(ctx, "")
GetSwapConnections
GET /api/swap/connections
Returns LI.FI's /v1/connections response. Shows which token→token routes are available between two chains.
JS/TS:
const connections = await client.getSwapConnections({
fromChain: "8453",
toChain: "137",
});
Go:
raw, err := client.GetSwapConnections(ctx, "8453", "137", "", "")