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 /v2 endpoints for Agent-led execution.
  • Public Mode: Targets /api endpoints 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.

Chain Constant USDC Decimals Status Notes
Solana pay.ChainSolanaMainnet ("solana-mainnet-beta") 6 Live
Base pay.ChainBase ("base") 6 Live
Ethereum pay.ChainEthereum ("ethereum") 6 Live
Polygon pay.ChainPolygon ("polygon") 6 Live
HyperEVM pay.ChainHyperEVM ("hyperevm") 6 Live
Arbitrum pay.ChainArbitrum ("arbitrum") 6 Live
BSC pay.ChainBSC ("bsc") 18 Live Binance-Peg USDC
Monad pay.ChainMonad ("monad") 6 Live
SKALE Base pay.ChainSKALEBase ("skale-base", payer-only) 6 Live Token is USDC.e (Bridged USDC). EIP-712 domain name "Bridged USDC (SKALE Bridge)".
MegaETH pay.ChainMegaETH ("megaeth", payer-only) 18 Live Native USDm (MegaUSD)
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.Decimals from the intent response rather than hardcoding 6. See Chain-Specific Notes for details.

Intent Status Constants

Use status constants instead of raw strings when checking intent status:

Constant Value Terminal
pay.StatusAwaitingPayment "AWAITING_PAYMENT" No
pay.StatusPending "PENDING" No
pay.StatusSourceSettled "SOURCE_SETTLED" No
pay.StatusTargetSettling "TARGET_SETTLING" No
pay.StatusTargetSettled "TARGET_SETTLED" Yes
pay.StatusVerificationFailed "VERIFICATION_FAILED" Yes
pay.StatusPartialSettlement "PARTIAL_SETTLEMENT" Yes
pay.StatusExpired "EXPIRED" Yes
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:

Field Go type Required Description
Chain string Yes Source chain
FromToken string Yes Input token contract address
ToToken string Yes Output token contract address
FromAmount uint64 Yes Input amount in smallest unit (wei)
SlippageBps uint16 No Slippage in basis points (default 50)
ToChain string No Destination chain for cross-chain swaps

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: 300 or 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

Constant Value Terminal
pay.SwapJobStatusPending "PENDING" No
pay.SwapJobStatusDone "DONE" Yes
pay.SwapJobStatusFailed "FAILED" Yes
pay.SwapJobStatusCanceled "CANCELED" Yes

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.

View on GitHub