Go SDK
High-performance, zero-dependency SDK designed for backend services and microservice architectures.
Smart Routing
The Go SDK features a unified Client that automatically routes requests based on the authentication options provided during initialization (WithBearerAuth).
- Authenticated Mode: Targets
/v2endpoints for Agent-led execution. - Public Mode: Targets
/apiendpoints for public intent creation.
Multi-Chain Support
A payment intent declares a payer chain and a target chain. PayerChain is required; TargetChain is optional and defaults to "base". The merchant receives the stablecoin on the target chain.
Use chain constants from the SDK instead of hardcoded strings. Treat GET /api/chains as authoritative for the exact set exposed by your deployment.
resp, err := client.CreateIntent(ctx, &pay.CreateIntentRequest{
Email: "merchant@example.com",
Amount: "100.50",
PayerChain: pay.ChainBase,
TargetChain: pay.ChainEthereum, // optional; defaults to "base"
})
BSC / MegaETH decimals: BSC USDC and MegaETH's native USDm use 18 decimals. Always read
resp.Extra.Decimalsfrom the intent response rather than hardcoding6. See Chain-Specific Notes for details.
Intent Status Constants
Use status constants instead of raw strings when checking intent status:
intent, err := client.GetIntent(ctx, intentID)
switch intent.Status {
case pay.StatusTargetSettled:
// Payment complete — use intent.TargetPayment for receipt
log.Printf("paid: tx=%s url=%s", intent.TargetPayment.TxHash, intent.TargetPayment.ExplorerURL)
case pay.StatusExpired, pay.StatusVerificationFailed, pay.StatusPartialSettlement:
// Terminal failure
}
Agent Identity (v2)
Two helpers expose the v2 agent surface backed by GET /v2/me and GET /v2/intents/list. Both require WithBearerAuth.
// Identity of the agent owning the API key in use.
me, err := client.GetMe(ctx)
log.Printf("agent %s (%s) base=%s solana=%s",
me.AgentID, me.Name, me.WalletAddress, me.SolanaWalletAddress)
// Paginated list of intents owned by the calling agent.
// page is 1-indexed; pageSize must be in [1,100]. Pass 0/0 for server defaults (1, 20).
list, err := client.ListIntents(ctx, 1, 20)
for _, it := range list.Intents {
log.Printf("%s %s→%s status=%s", it.IntentID, it.PayerChain, it.TargetChain, it.Status)
}
IntentBase.AgentID is populated on every v2 intent response (CreateIntent, ExecuteIntent, GetIntent, ListIntents) and is empty for intents created via the public /api flow. GetIntent against /v2 enforces ownership and returns 404 payment intent not found when the intent belongs to a different agent or has no owner.
Swap
GetSwapQuote, RegisterSwapIntent, and the three discovery methods are available on every Client regardless of authentication mode. No API key is required.
ExecuteSwap (Agent Wallet)
When the agent has a Privy-hosted wallet, use ExecuteSwap to swap tokens without managing private keys. The SDK calls POST /v2/swap/execute; the backend handles quoting, ERC-20 approval, and broadcasting.
Requires WithBearerAuth.
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", // WETH
ToToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
FromAmount: 1_000_000_000_000_000_000, // 1 WETH in wei
// SlippageBps: 100, // optional, default 50
// ToChain: "polygon", // optional, cross-chain
})
fmt.Println("tx:", resp.TxHash, "estimated output:", resp.EstimatedOutput)
ExecuteSwapRequest fields:
Native ETH: To swap native ETH (not WETH), pass
FromToken: "0x0000000000000000000000000000000000000000"(zero address). The backend passes this directly to LiFi, which treats the zero address as the native chain token.
Slippage: For native-token swaps (ETH → USDC), the default 50 bps may be insufficient. Set
SlippageBps: 300or higher for more reliable execution.
ExecuteSwapResponse fields: TxHash, Chain, FromToken, ToToken, FromAmount (string), EstimatedOutput (string).
import pay "github.com/cross402/usdc-sdk-go"
client := pay.New(pay.WithBaseURL("https://api-pay.agent.tech"))
// ExactIn quote (no user address → 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 swap transaction (EVM)
resp, err := client.GetSwapQuote(ctx, &pay.SwapQuoteParams{
Chain: "base",
InputToken: "0x4200000000000000000000000000000000000006",
OutputToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
FromAmount: 1_000_000_000_000_000_000,
UserAddress: "0xYourWallet",
})
// resp.SwapTransaction != nil — { Transaction (hex), To, Value, GasLimit, ExpiresAt }
// Cross-chain: Base → Solana
resp, err := client.GetSwapQuote(ctx, &pay.SwapQuoteParams{
Chain: "base",
InputToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
OutputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
FromAmount: 10_000_000,
ToChain: "solana",
UserAddress: "0xYourEVMWallet",
ToUserAddress: "YourSolanaPublicKey",
})
After signing and broadcasting the swap transaction, register it:
reg, err := client.RegisterSwapIntent(ctx, &pay.RegisterSwapIntentRequest{
SourceTxHash: "0xabc...",
FromChain: "base",
ToChain: "base",
FromToken: "0x4200000000000000000000000000000000000006",
ToToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
PayerAddress: "0xYourWallet",
RecipientAddress: "0xRecipient",
SendingTokenAmount: "1000000000000000000",
})
fmt.Println(reg.IntentID, reg.Status) // "PENDING"
Discovery (LI.FI passthrough — returns json.RawMessage):
tokens, err := client.GetSwapTokens(ctx, "8453,137", "") // chains, chainTypes
chains, err := client.GetSwapChains(ctx, "EVM")
conns, err := client.GetSwapConnections(ctx, "8453", "137", "", "")
SwapJobStatus Values
SubmitProof is /api-only
SubmitProof belongs to the unauthenticated /api flow. When called on a client configured with WithBearerAuth, the SDK rejects the call locally with pay.ErrSubmitProofNotAllowed instead of letting it 404 against /v2/intents/{intent_id}. v2 callers should rely on ExecuteIntent to drive settlement.