Skip to main content
@geodesics-protocol/sdk is the typed TypeScript client for the Geodesics API. One signed intent in, the asset out on the chain you want, across EVM chains and Solana. Settlement is typically 5-15 seconds, including cross-chain.
  • Any wallet works: a raw key from any stack, an embedded wallet, or a Virtuals ACP agent wallet. The signer is just callbacks; nothing about your wallet setup is assumed.
  • The agent holds only the token it wants to swap. Gas and fees come out of the input, so the agent never needs ETH or any gas token.
  • Self-custodial: the server builds every operation, your agent signs one hash with the signer it already has, and funds never leave the agent’s own wallet.
  • Chain onboarding is automatic: a signer with signAuthorization onboards the wallet inside its first swap from each chain, gasless; a Virtuals-style signer with sendTransaction runs the one-time activation instead, same as the CLI.
  • Zero runtime dependencies, ESM, fully typed and runtime-validated responses.

Install

Requires Node.js 20+ and a Geodesics API key.

One call

swap() runs the whole flow, from quote through build, sign, and submit to settlement, and resolves with the terminal status. For venue-style routing, price with quote() and compare against your other providers, then execute the winner with swap().

Any wallet: bring a raw key

The simplest signer is a plain private key, from geodesics init, your KMS, or any wallet stack that can export one. With viem (npm i @geodesics-protocol/sdk viem), the whole integration is:
signAuthorization receives the fields to sign, chainId, the delegation target address, and the wallet’s current account nonce, straight from the server; it never needs an RPC of its own. The delegation is a one-time EIP-7702 authorization to an audited smart-account implementation, verifiable on-chain, and Geodesics never replaces a delegation a wallet already has. For Solana-origin swaps a raw keypair signs the same way: provide signSolanaTransaction, deserialize the base64 transaction, sign with the keypair, re-serialize. output on quotes and results is the estimated amount, formatted in the destination token; the settled amount can differ slightly with the fill. Never reuse a previous output as an exact input amount for a follow-up swap: a sell-all that overdraws by dust fails on-chain. Use your own balance read, or the CLI’s --max. When the origin wallet holds enough $GEO, quotes carry a feeDiscount object with the earned tier, the discountPercent, and the undiscounted baseFeeBps; feeBps already reflects the discounted fee.

Withdraw

withdraw() moves the chain’s canonical USD stable, USDC or its per-chain equivalent such as usdg on Robinhood Chain, from the agent wallet to another wallet, gasless, through the same pipeline as swap() and with the same signer and result shape. The token is resolved from the chain, so there is no token parameter; to move any other asset, swap it to USDC first with swap(). Same-chain it is a plain transfer, and to must differ from walletAddress. To deliver on another chain, pass destinationChain; the destination chain’s canonical stable is delivered, and slippageBps is optional and only matters cross-chain. The recipient is treated as an external wallet and is never onboarded.

Step-by-step methods

Need more control than swap()? The same wire is available step by step: Everything is fully typed; run your own retry, batching, or signing pipeline on top. The wire itself is documented on the REST API page and in the API Reference.

Virtuals ACP agents: plug in the signer you already have

Existing Virtuals agents using acp-trade already have everything Geodesics needs: the same wallet, the same swapping signer, and the same signature flow. signMessage provides the one signature each swap needs, and sendTransaction lets the SDK handle chain onboarding. Install the SDK next to the ACP toolkit your agent already uses:
A complete, runnable example for a Virtuals agent:
For swaps FROM Solana, provide signSolanaTransaction with the adapter’s Solana signer instead: it receives the unsigned base64 transaction and returns it signed.

Chain onboarding: first swap from or into a new chain

A wallet must be onboarded once per EVM chain before it can originate swaps there. Which path runs is decided by the capabilities your signer provides, never by configuration: With signAuthorization (the agent’s own wallet):
  • First swap FROM a chain: the SDK fetches the signing fields from the API, collects the wallet’s one-time authorization, and the swap that carries it onboards the wallet, gasless, in the same settlement. Progress reports { stage: 'authorizing' }. If the authorization goes stale between signing and execution because the account’s nonce moved, the server rejects it with INVALID_AUTHORIZATION and the SDK automatically refreshes the state, re-collects one signature, and retries once.
  • Delivery INTO any chain needs no preparation. Whatever lands there can always swap back out, because the wallet onboards itself whenever it first swaps out of that chain.
  • A wallet that is already delegated to another provider on a chain cannot be onboarded there: Geodesics never replaces an existing delegation, and the API refuses with UNSUPPORTED_DELEGATION. Use a different wallet on that chain.
With sendTransaction (Virtuals-style wallet infrastructure):
  • First swap FROM a chain: swap() fetches the prepared activation call from the API, sends it, waits for the delegation to land, and continues with a fresh quote. The call is a tiny self-transfer of the chain’s stable, paid from that balance. Progress reports { stage: 'activating' }.
  • First swap INTO a chain: the destination is onboarded before the main swap so the delivered assets can always swap back out. If the swap itself delivers the chain’s stable, activation simply runs after settlement. If the wallet already holds the stable there, it activates up front. Otherwise swap() pipes ~1 unit of the origin chain’s stable over, activates, and returns the remainder; keep ~1 unit spare on the origin for this. It adds about a minute, once per chain. Progress reports { stage: 'onboarding' }; anything non-fatal that could not run lands in result.warnings. Opt out with swap(request, signer, { onboardDestination: false }).
With neither capability, the first swap from a new chain throws NEEDS_DELEGATION. You can run the onboarding yourself via getDelegation(), whose response carries everything both paths use: the delegationTarget and current accountNonce a new wallet signs over, the ready-to-send activationCall, and the chain’s carrier stable and balance for planning your own flow. Onboarding at signup instead of first swap: onboard(chainId, walletAddress, signer) runs the sendTransaction activation right away when the signer can send, and reports { delegated: false, onboarding: 'first-swap' } for an authorization-capable signer, because the authorization must bind the account nonce at swap time; that wallet onboards automatically, gasless, whenever its first swap happens. Solana needs no activation, and cross-chain swaps in and out of Solana are gasless: a wallet holding only SPL tokens and zero SOL can swap out with one signature. The exception is a same-chain Solana swap, which needs ~0.005 SOL for network fees and fails fast with NEEDS_SOL_TOPUP at quote time when the wallet lacks it; swapping ~1.5 USDC into SOL covers it.

Slippage

slippageBps on the quote/swap request caps price movement in basis points; 300 means 3%. Omit it and the server picks a per-route default: tight for stables, wider for volatile tokens. A swap that cannot fill within tolerance comes back refunded with the input returned to the origin wallet and a refundHint explaining the usual fix: retry with a higher slippageBps.

Errors

Failures throw typed errors instead of loose strings:
  • GeodesicsApiError with status, code, and a plain-language message. Notable codes: NEEDS_DELEGATION and INVALID_AUTHORIZATION, covered under Chain onboarding above (the latter is retried once automatically); UNSUPPORTED_DELEGATION, when the wallet is already delegated to another provider on that chain, which Geodesics never replaces; NEEDS_SOL_TOPUP, when a same-chain Solana swap needs ~0.005 SOL for network fees; and NEEDS_LARGER_SIZE, when the amount is too small for that route.
  • GeodesicsTimeoutError with swapId and lastStatus when polling hits its deadline; the swap usually still settles, so check status(swapId).
The terminal statuses settled, failed, and refunded are results, not exceptions: swap() resolves and callers branch on result.status. The full code list lives in Errors.

Apps with end-user wallets

Building an app where every user has a wallet, embedded, server-side, or KMS-backed, rather than one agent wallet? The integration shape is the same SDK with the trust split in the right places:
  • Your backend holds the API key and makes every Geodesics call: quote, build, submit, status. The key never ships to a browser or app client.
  • The user’s wallet contributes only signatures. Your backend hands the geoOpHash to the client; the user’s wallet signs it (EIP-191 over the raw 32 bytes) and, once per chain, the one-time onboarding authorization; the backend passes both back through swap()’s signer callbacks or the step-by-step methods. The sealed geoQuoteToken / geoOpToken are opaque and safe to hold server-side; nothing in them needs the client.
  • Onboard at signup if you prefer: call onboard() per user when they join, so the first swap is indistinguishable from every later one. With signature-only wallets it reports onboarding: 'first-swap' and the first swap handles it, gasless, automatically.
  • Nothing assumes one wallet per API key: any number of users’ wallets can swap behind one key, and the wallet that spends is always the one whose key signed.
One honest boundary: injected browser-extension wallets (MetaMask and friends) reserve delegation for their own systems and cannot be onboarded; the supported story is any embedded or server-side wallet stack.

Supported chains

Base, Ethereum, Arbitrum, Optimism, Polygon, BNB Chain, Robinhood Chain, and Solana, both as origin and destination. Requests take numeric chain ids; the SDK exports them as CHAIN_IDS. The full id and token-address reference is in Supported chains.

Prefer a ready-made agent skill?

The Agent skill sets an agent up from one prompt and drives the CLI: a geodesics command with token/chain aliases, automatic first-swap activation, and a packaged SKILL.md for agent runtimes. This SDK is the same engine as a library.