@geodesics-protocol/sdk
v0.3.2
Published
Geodesics REST client: gasless cross-chain swaps for agents.
Maintainers
Readme
@geodesics-protocol/sdk
Typed TypeScript client for the Geodesics API: gasless, self-custodial cross-chain swaps for agents. 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.
- 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: give the signer a
sendTransactionandswap()activates a chain the wallet has never swapped from, and onboards a first-time destination so delivered assets can always swap back out, same as the CLI. - Zero runtime dependencies, ESM, fully typed and runtime-validated responses.
Install
npm i @geodesics-protocol/sdkRequires Node.js 20+ and a Geodesics API key (message us to get one).
One call
import { createGeodesicsClient } from '@geodesics-protocol/sdk';
const geodesics = createGeodesicsClient({
baseUrl: 'https://api.geodesics.ai',
apiKey: process.env.GEODESICS_API_KEY ?? '',
});
const result = await geodesics.swap(
{
originChain: 8453, // Base
destinationChain: 42161, // Arbitrum
inputToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
outputToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
amount: '5000000', // 5 USDC, in base units (6 dp)
walletAddress: agentWalletAddress,
slippageBps: 300, // optional; defaults per route (tight for stables, wider for volatile)
},
// One EIP-191 signature over the raw 32-byte hash per swap (for the Virtuals ACP signer,
// see the walkthrough below). Solana origin: provide signSolanaTransaction instead.
// sendTransaction is optional and unlocks automatic chain onboarding (see below).
{
signMessage: (geoOpHash) => agentSigner.signMessage(geoOpHash),
sendTransaction: (chainId, call) => agentSigner.sendTransaction(chainId, call),
},
);
// result: { swapId, status: 'settled', originTxHash, deliveryTxHash, output, feeBps }swap() runs the whole flow (quote, build, sign, submit, poll 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().
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.
Need more control than swap()? The same wire is available step by step: quote() prices the
route and returns a sealed geoQuoteToken; buildGeoOp() (or buildSolanaTx() for Solana
origin) turns it into a geoOpHash to sign plus a sealed geoOpToken; submit() /
submitSolana() takes the token and your raw signature and returns a swapId; status() and
waitForSettlement() follow it to a terminal state, and getDelegation() reports a wallet's
activation state per chain. Everything is fully typed; run your own retry, batching, or signing
pipeline on top.
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 signs each swap
(one signature per swap), and sendTransaction lets the SDK handle chain onboarding. Install
the SDK next to the ACP toolkit your agent already uses:
npm i @geodesics-protocol/sdk @virtuals-protocol/acp-node-v2 @account-kit/infraA complete, runnable example for a Virtuals agent:
import { base } from '@account-kit/infra';
import { createGeodesicsClient } from '@geodesics-protocol/sdk';
import { PrivyAlchemyEvmProviderAdapter } from '@virtuals-protocol/acp-node-v2';
const geodesics = createGeodesicsClient({
baseUrl: 'https://api.geodesics.ai',
apiKey: process.env.GEODESICS_API_KEY ?? '',
});
// The agent's ACP wallet + swapping signer key (the base64 value starting "MIG" shown once at
// signer creation). Privy signs remotely; no key material lives in your process.
const walletAddress = process.env.AGENT_WALLET_ADDRESS ?? '';
const agent = await PrivyAlchemyEvmProviderAdapter.create({
walletAddress,
walletId: process.env.AGENT_WALLET_ID ?? '',
signerPrivateKey: (process.env.AGENT_SIGNER_PRIVATE_KEY ?? '').replace(/^0x/, ''),
chains: [base],
});
const result = await geodesics.swap(
{
originChain: 8453,
destinationChain: 8453,
inputToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
outputToken: '0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b', // VIRTUAL on Base
amount: '3000000', // 3 USDC
walletAddress,
},
{
// sign on the origin chain id (8453 here); the hash is the same EIP-191 flow ACP trades use
signMessage: (geoOpHash) => agent.signMessage(8453, { raw: geoOpHash }),
sendTransaction: (chainId, call) => agent.sendTransaction(chainId, call),
},
);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 activated once per EVM chain before it can originate swaps there. When the
signer provides sendTransaction, swap() handles both directions transparently:
- First swap FROM a chain:
swap()fetches the prepared activation call from the API, sends it (a tiny self-transfer of the chain's stable, paid from that balance), waits for the delegation to land, and continues with a fresh quote. 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; adds about a minute, once per chain). Progress reports{ stage: 'onboarding' }; anything non-fatal that could not run lands inresult.warnings. Opt out withswap(request, signer, { onboardDestination: false }).
Without sendTransaction, the first swap from a new chain throws NEEDS_DELEGATION; you can
run the activation yourself via getDelegation() (its response carries the ready-to-send
activationCall plus the chain's carrier stable and balance when planning your own flow).
Solana needs no activation. Delivery to a Solana wallet with zero SOL works, but swapping back
OUT of Solana needs ~0.01 SOL for network fees. When a quote's delivery recipient is below that,
the server flags it and the SDK either passes the notice through in result.warnings (default)
or, with swap(request, signer, { topUpSol: true }), pipes ~1.5 units of the origin's stable
into SOL first and continues. Solana-origin swaps without SOL fail fast with NEEDS_SOL_TOPUP
at quote time; the same top-up fixes those.
Slippage
slippageBps on the quote/swap request caps price movement in basis points (300 = 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:
GeodesicsApiErrorwithstatus,code, and a plain-languagemessage. Notable codes:NEEDS_DELEGATION(see Chain onboarding above),NEEDS_SOL_TOPUP(a Solana-origin swap needs ~0.01 SOL for network fees), andNEEDS_LARGER_SIZE(the amount is too small for that route).GeodesicsTimeoutErrorwithswapIdandlastStatuswhen polling hits its deadline; the swap usually still settles, so checkstatus(swapId).
Terminal wire statuses (settled, failed, refunded) are results, not exceptions: swap()
resolves and callers branch on result.status.
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:
| Chain | id | |---|---| | Base | 8453 | | Ethereum | 1 | | Arbitrum | 42161 | | Optimism | 10 | | Polygon | 137 | | BNB Chain | 56 | | Robinhood Chain | 4663 | | Solana | 792703809 (this API's numeric id for Solana) |
Prefer a ready-made agent skill?
npm i -g @geodesics-protocol/cli gives agents 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.
Questions, keys, or anything broken: message us directly or join our Telegram community. We iterate fast.
