npm.io
0.2.1 • Published yesterday

@obolo/sdk

Licence
MIT
Version
0.2.1
Deps
0
Vulns
0
Weekly
0

@obolo/sdk

SDK for discovering and paying for agent-payable APIs on Obolo. Install it, connect a wallet, and your agent can autonomously discover, pay for, and consume any API on Obolo.

Installation

npm install @obolo/sdk viem

Quickstart

import { Obolo } from '@obolo/sdk';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

// Create a wallet from a private key
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
});

// Initialize the Obolo client
const gf = new Obolo({
  walletClient,
  maxPriceUsdc: 0.10,  // Safety cap: never pay more than $0.10 per call
});

// Discover available APIs
const endpoints = await gf.discover('crypto prices');
console.log(endpoints.map(e => `${e.name}: $${e.price_usdc}/call`));

// Call an API — payment handled automatically
const response = await gf.call('provider/crypto-prices', {
  params: { ids: 'bitcoin', vs_currencies: 'usd' },
});

console.log(response.data);           // { bitcoin: { usd: 65000 } }
console.log(response.payment.txHash); // 0x...
console.log(response.payment.amountUsdc); // 0.01
console.log(response.timing.totalMs); // 3421

API Reference

new Obolo(config)
Option Type Default Description
walletClient WalletClient required viem WalletClient with an account attached
proxyBaseUrl string https://proxy.obolo.io Override proxy base URL
catalogUrl string https://www.obolo.io/api/marketplace/catalog Override catalog URL
autoApprove boolean true Auto-approve exact USDC spend before each payment
maxPriceUsdc number 1.00 Reject endpoints priced above this (safety cap)
gf.discover(query?)

Returns all active endpoints from the catalog, optionally filtered by query (name, description, category, or provider).

gf.call(slug, options?)

Pays for and calls an endpoint. Returns { data, payment, timing, status, rawBody }.

Options: method, params, body, headers.

gf.getPrice(slug)

Returns price info without paying.

gf.getReputation(slug)

Returns provider reputation and trust score (0–1).

gf.getBalance()

Returns the wallet's USDC balance on Base as a formatted string.

Error Handling

import {
  PriceExceededError,
  InsufficientBalanceError,
  PaymentFailedError,
  EndpointNotFoundError,
} from '@obolo/sdk';

try {
  const response = await gf.call('provider/expensive-api');
} catch (err) {
  if (err instanceof PriceExceededError) {
    console.error(`Too expensive: ${err.price} USDC (cap: ${err.maxPrice})`);
  } else if (err instanceof InsufficientBalanceError) {
    console.error(`Need ${err.required} USDC, have ${err.available}`);
  } else if (err instanceof PaymentFailedError) {
    console.error('Transaction rejected:', err.message);
  }
}

Payment rails — one call, any chain

gf.call() is EVM-only. Payment rails give every non-EVM chain the same one-liner: payAndReplay() probes the endpoint's 402, picks the option for your chain, enforces a price cap, sends the transfer, signs the chain's wallet-proof, and replays the request — retrying automatically while the proxy's node catches up to your tx.

import { payAndReplay, tonRail, solanaRail, aptosRail, stellarRail, evmRail } from '@obolo/sdk';

// One rail per chain — pick the one that matches the wallet you hold. Each is an optional install:
const rail = tonRail({ mnemonic: process.env.TON_MNEMONIC!.split(' ') }); // npm i @ton/ton @ton/crypto

const { data, payment } = await payAndReplay({
  rail,
  slug: 'acme/weather',
  method: 'GET',
  maxPriceUsd: 0.10,          // enforced BEFORE any funds move
});
console.log(payment.txHash, data);

Every rail satisfies the same PaymentRail interface, so payAndReplay is identical across chains — only the rail factory differs:

Chain Factory Config Optional install
Solana solanaRail { secretKey } (64-byte) @solana/web3.js @solana/spl-token tweetnacl
Aptos aptosRail { privateKeyHex } @aptos-labs/ts-sdk
Stellar stellarRail { secret } (S…) @stellar/stellar-sdk
EVM evmRail { chainId, walletClient, publicClient } viem
TON † tonRail { mnemonic, version?, workchain? } @ton/ton @ton/crypto

TON is the newest rail. The tonRail code and the proxy's TON verifier are live on the production proxy, but TON has not yet been exercised with a live mainnet payment — do a testnet dry-run first (see below). Solana, Aptos, Stellar, and EVM rails have been settling against the live proxy in production.

The chain SDKs are optional peer dependencies — the core SDK is ~46 KB and pulls in nothing unless you build a rail that needs it. Each rail sends the exact recipient/amount/token from the proxy's authoritative 402 (never a hardcoded mint), and signs the wallet-proof with the same key that paid, so the proxy's fail-closed payer-binding always matches. Bring your own rail by implementing PaymentRail (chainId, pay(option), signProof(...)) if you'd rather send funds your own way.

Before enabling a rail against mainnet funds, do a testnet dry-run. The transfer construction is grounded against each chain's official SDK + the proxy verifier and adversarially reviewed, but on-chain submission is the one thing unit tests can't exercise.

Solana payments (low-level)

If you'd rather settle the payment yourself, gf.call() handles the EVM x402 path end-to-end — it settles the USDC payment and attaches the EIP-191 wallet-proof for you. It is EVM-only by design. Solana wallets sign with ed25519, not EIP-191, so they use a separate, domain-separated proof: build the headers with signSolanaWalletProof and attach them to your own request.

import { signSolanaWalletProof } from '@obolo/sdk';

// A SolanaSignerLike: your base58 pubkey + a raw-message signMessage returning the
// raw 64-byte signature. Satisfied by an @solana/web3.js Keypair wrapper or a
// wallet-adapter signMessage. Your secret key never leaves your process.
const signer = {
  publicKey: myBase58Address,
  signMessage: (msg: Uint8Array) => nacl.sign.detached(msg, secretKey),
};

// After settling the SPL payment on-chain, sign a proof bound to that exact tx.
const headers = await signSolanaWalletProof(signer, {
  method: 'GET',
  pathname: '/v1/provider/crypto-prices', // the URL path you send to the proxy
  paymentHash: base58TxSignature,         // the X-Payment-Hash value for Solana
});

const res = await fetch('https://proxy.obolo.io/v1/provider/crypto-prices', {
  headers: {
    ...headers, // X-Solana-Wallet, X-Solana-Signature, X-Solana-Proof-Issued
    'X-Payment-Hash': base58TxSignature,
    'X-Payment-Chain': 'solana-mainnet',
  },
});
  • The challenge domain string is GateFlow-Solana-Wallet-Proof-v1 (distinct from the EVM GateFlow-Wallet-Proof-v1), and the base58 wallet is case-preserved — never lowercased.
  • ed25519 has no public-key recovery, so you send your public key and the proxy verifies the signature against it, then binds it to the on-chain SPL payer. This is fail-closed: a Solana payment with no valid proof, or a payer mismatch, is rejected. The proof is valid for 5 minutes.

Stellar payments

Stellar also signs with ed25519, so it uses its own domain-separated proof (GateFlow-Stellar-Wallet-Proof-v1). After settling the native-Circle USDC payment on Horizon, build the headers with signStellarWalletProof and attach them to your own request.

import { signStellarWalletProof } from '@obolo/sdk';

// A StellarSignerLike: your 'G…' strkey account + a raw-message signMessage returning
// the raw 64-byte ed25519 signature. Your secret key never leaves your process.
const signer = {
  address: myStellarGAddress,                    // the 'G…' strkey account
  signMessage: (msg: Uint8Array) => stellarKeypair.sign(msg),
};

// After settling the classic-asset USDC payment, sign a proof bound to that exact tx.
const headers = await signStellarWalletProof(signer, {
  method: 'GET',
  pathname: '/v1/provider/crypto-prices', // the URL path you send to the proxy
  paymentHash: stellarTxHash,             // the X-Payment-Hash value (Stellar tx hash, 64-hex)
});

const res = await fetch('https://proxy.obolo.io/v1/provider/crypto-prices', {
  headers: {
    ...headers, // X-Stellar-Wallet, X-Stellar-Signature, X-Stellar-Proof-Issued
    'X-Payment-Hash': stellarTxHash,
    'X-Payment-Chain': 'stellar-mainnet',
  },
});
  • The 'G…' strkey encodes the ed25519 pubkey, so you send your address and the proxy decodes it to verify the signature. The strkey is base32 and case-significant — never lowercase it. The signature is 0x-hex (unlike Solana's base58).
  • Fail-closed: the proxy binds the proven wallet to the on-chain Stellar payer, so a payment with no valid proof, or a payer mismatch, is rejected. The proof is valid for 5 minutes.

Aptos payments

Aptos likewise signs with ed25519 and uses its own domain-separated proof (GateFlow-Aptos-Wallet-Proof-v1). Aptos USDC is a native fungible asset. The Aptos twist: the account address is not the pubkey (it is sha3_256(pubkey ‖ 0x00)), so you send your ed25519 public key and the proxy verifies against it and derives the address.

import { signAptosWalletProof } from '@obolo/sdk';

// An AptosSignerLike: your ed25519 PUBLIC KEY (0x-hex, 32 bytes — NOT the account
// address) + a raw-message signMessage returning the raw 64-byte signature.
const signer = {
  publicKey: myAptosPublicKeyHex,                // 0x-hex ed25519 pubkey, not the address
  signMessage: (msg: Uint8Array) => aptosAccount.sign(msg),
};

// After settling the fungible-asset USDC payment, sign a proof bound to that exact tx.
const headers = await signAptosWalletProof(signer, {
  method: 'GET',
  pathname: '/v1/provider/crypto-prices',
  paymentHash: aptosTxHash,               // the X-Payment-Hash value (Aptos tx hash, 0x-hex)
});

const res = await fetch('https://proxy.obolo.io/v1/provider/crypto-prices', {
  headers: {
    ...headers, // X-Aptos-Wallet (the pubkey), X-Aptos-Signature, X-Aptos-Proof-Issued
    'X-Payment-Hash': aptosTxHash,
    'X-Payment-Chain': 'aptos-mainnet',
  },
});
  • X-Aptos-Wallet carries the ed25519 public key (0x-hex, lowercased in the challenge), and the signature is 0x-hex. The proxy derives the account address from the pubkey and binds it.
  • Fail-closed: an Aptos payment with no valid proof, or a payer mismatch, is rejected. The proof is valid for 5 minutes.

License

MIT

Keywords