npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@geodesics-protocol/sdk

v0.8.3

Published

Geodesics REST client: gasless cross-chain swaps for agents.

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.

  • 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

npm i @geodesics-protocol/sdk

Requires 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. The other callbacks are optional
  // capabilities: signAuthorization (own wallet, gasless first-swap onboarding), sendTransaction
  // (Virtuals-style activation), signSolanaTransaction (Solana origin). Both wallet kinds below.
  {
    signMessage: (geoOpHash) => agentSigner.signMessage(geoOpHash),
  },
);
// 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().

Any wallet: bring a raw key

The simplest signer is a plain private key, from any stack that can export one. With viem (npm i @geodesics-protocol/sdk viem), the whole integration is:

import { createGeodesicsClient } from '@geodesics-protocol/sdk';
import { privateKeyToAccount } from 'viem/accounts';

const geodesics = createGeodesicsClient({
  baseUrl: 'https://api.geodesics.ai',
  apiKey: process.env.GEODESICS_API_KEY ?? '',
});
const account = privateKeyToAccount(process.env.AGENT_SIGNER_PRIVATE_KEY);

const result = await geodesics.swap(
  {
    originChain: 8453,
    destinationChain: 8453,
    inputToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
    outputToken: '0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b', // VIRTUAL on Base
    amount: '3000000', // 3 USDC
    walletAddress: account.address,
  },
  {
    signMessage: (geoOpHash) => account.signMessage({ message: { raw: geoOpHash } }),
    // The wallet's first swap from a chain carries this one-time EIP-7702 authorization and
    // onboards the wallet inside the same swap, gasless. Every later swap skips it.
    signAuthorization: async ({ chainId, address, nonce }) => {
      const signed = await account.signAuthorization({ contractAddress: address, chainId, nonce });
      return { chainId, address, nonce, yParity: signed.yParity === 1 ? 1 : 0, r: signed.r, s: signed.s };
    },
  },
);

signAuthorization receives the fields to sign (chainId, the delegation target address, 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.

withdraw() moves the chain's canonical USD stable (USDC, or its per-chain equivalent such as Robinhood Chain's USDG) to another wallet, gasless, through the same pipeline:

const receipt = await geodesics.withdraw(
  { chain: 8453, amount: '25000000', walletAddress, to: recipientAddress },
  signer,
);

The token is resolved from the chain, never passed. Same-chain the recipient must differ from the wallet and a small transfer fee comes out of the amount (the fee is what makes the transfer gasless); destinationChain bridges to that chain's canonical stable instead. On Solana the wallet's own SOL pays the network fee.

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; getDelegation() reports a wallet's per-chain onboarding state including the fields a new wallet signs over, and onboard() runs the onboarding standalone, at signup instead of first swap. Submits are idempotent per quote, with the deduplication key derived server-side: resubmitting the same geoOpToken and signature returns the existing swap instead of executing twice, so retrying on a timeout or crash is always safe. 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/infra

A 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 onboarded once per EVM chain before it can originate swaps there. Which path runs is decided by the capabilities your signer provides:

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 before execution (the account's nonce moved), the server rejects it with INVALID_AUTHORIZATION and the SDK automatically refreshes, 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 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.

With sendTransaction (Virtuals-style wallet infrastructure):

  • 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 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() (its response carries 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). To onboard at signup instead of first swap, onboard(chainId, walletAddress, signer) activates right away when the signer can send transactions, and reports { delegated: false, onboarding: 'first-swap' } for an authorization-capable signer: 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: delivery to a Solana wallet with zero SOL works, and a wallet holding only SPL tokens swaps back 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, and that top-up can itself be a Geodesics swap into Solana.

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:

  • GeodesicsApiError with status, code, and a plain-language message. Notable codes: NEEDS_DELEGATION and INVALID_AUTHORIZATION (see Chain onboarding above; the latter is retried once automatically), UNSUPPORTED_DELEGATION (the wallet is delegated to another provider on that chain, never replaced), NEEDS_SOL_TOPUP (a same-chain Solana swap needs ~0.005 SOL for network fees), and NEEDS_LARGER_SIZE (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).

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.