@leashmarket/sdk is the TypeScript surface for app developers — the language-level analog to the OpenAPI doc at api.leash.market/openapi.json. Use it to build apps that discover services, vet counterparties, ingest receipts, and subscribe to webhooks signed with the agent’s executive keypair. The SDK is browser/Bun/Deno-friendly: it uses fetch and globalThis.crypto.subtle with a node:crypto fallback. Zero runtime deps beyond Umi for ed25519 signing.

Try it without installing anything

The two read endpoints are public — you can verify the API is up with a single curl before pulling the SDK in:
# Merged search (Leash marketplace + Solana Foundation pay-skills registry).
curl 'https://api.leash.market/v1/discover?capability=ocr&limit=3'

# Scope to one catalogue with `source=leash|pay-skills`.
curl 'https://api.leash.market/v1/discover?capability=email&source=pay-skills'

curl 'https://api.leash.market/v1/agents/AGENT_MINT/reputation'

curl 'https://api.leash.market/v1/identity/verify?handle=payce-demo'
If those return JSON, the SDK calls below will work too.

Install

pnpm add @leashmarket/sdk
# or
npm install @leashmarket/sdk
The package is ESM-only ("type": "module"). In a Node CJS project, either switch your package.json to "type": "module", import dynamically (const { LeashClient } = await import('@leashmarket/sdk')), or transpile with your bundler.

Quickstart

import { LeashClient } from '@leashmarket/sdk';

const leash = new LeashClient({ baseUrl: 'https://api.leash.market' });

// 1. Unified search — public, no auth. Merges the Leash marketplace
//    with the Solana Foundation pay-skills registry; each item carries
//    a `source: 'leash' | 'pay-skills'` tag.
const services = await leash.discover({ capability: 'ocr', max_price_usdc: 0.1 });

// 2. Reputation lookup before paying — only meaningful for Leash
//    agents (pay-skills entries have no on-chain mint). Filter first.
const leashItem = services.items.find((s) => s.source === 'leash' && s.seller_agent_mint);
if (leashItem) {
  const rep = await leash.reputation({ agentMint: leashItem.seller_agent_mint! });
  if (rep.rating < 0.5) throw new Error('seller has too low a rating');
}

// 2a. Identity preflight before trusting a handle/domain/mint.
const identity = await leash.resolveIdentity({ handle: 'payce-demo' });
const verdict = await leash.verifyIdentity({ mint: identity.mint });
if (!verdict.verified) throw new Error('identity did not verify');

// Ask for an opinionated allow/warn/deny decision before paying or
// calling a capability.
const decision = await leash.verifyIdentityDecision({
  selector: { mint: identity.mint },
  intent: 'call_capability',
  capability: { slug: 'seller/quote-api', protocol: 'x402' },
  thresholds: { min_rating: 0.7, require_verified_domain: true },
});
if (decision.verdict === 'deny') throw new Error('identity decision denied');

// 2b. Expand a pay-skills item into its individual paid endpoints —
//     mirrors `pay skills endpoints <fqn>` from the pay.sh CLI. The
//     returned `endpoint.url` values are absolute and ready to pay
//     via `@leashmarket/buyer-kit`.
const paySkillsItem = services.items.find((s) => s.source === 'pay-skills');
if (paySkillsItem) {
  const provider = await leash.paySkillsProvider(paySkillsItem.slug); // e.g. 'agentmail/email'
  for (const ep of provider.endpoints) {
    console.log(`${ep.method} ${ep.url} (probe=${ep.probe_status})`);
  }
}

// 3. Record a client-minted agent — public, no auth (idempotent on
//    `mint`). Mint + delegate locally with `@leashmarket/mcp::mintAgentLocally`
//    or your own Umi setup, then hand the result here so the API can
//    write the platform row + receipts feed metadata.
const recorded = await leash.recordAgent({
  mint: 'BcN4ToBs8jE3dbYNhYqDJqGnKPjH3zRX8gsDUDH72JQp',
  executive_pubkey: '947dU4Nk8HsdkFcrVip5Zt9XLnfFF5iJSvepEArdr5Ma',
  name: 'my-experimental-bot',
  network: 'solana-devnet',
});
console.log('recorded', recorded.mint, 'treasury', recorded.treasury);
Provisioning (generating keypairs, minting MPL Core assets, setting USDC delegation) is not in the SDK — use @leashmarket/mcp::mintAgentLocally or the leash agent create CLI for that. The SDK is for “remote control” of agents that already exist; the MCP / CLI is the engine that creates them.

Receipts — list, single, history, daily

Once an agent is on-chain, every settled x402 call writes a ReceiptV1 row the API exposes through four matching helpers:
const leash = new LeashClient({
  baseUrl: 'https://api.leash.market',
  apiKey: process.env.LEASH_API_KEY!,
});
const agentMint = 'AjfeyP...';

// 1. Paginated feed (newest-first). Filter by direction with `kind`.
const feed = await leash.receipts({ agentMint, direction: 'earn', limit: 50 });

// 2. Single ReceiptV1 by hash — same blob the explorer renders at
//    /receipt/{hash}. Network is bound to the API key prefix; a hash
//    from the sibling cluster returns 404.
const r = await leash.getReceipt(
  'c3c50cb352a2624f783ca6a51bdb7fbcd3b67f04b4a42cd431444db05504181a',
);
console.log(r.raw); // → full canonical ReceiptV1

// 3. Last N days plus running USD totals (stables summed at 1:1).
const week = await leash.transactionHistory({ agentMint, days: 7 });
console.log(week.total_received_usd, week.total_sent_usd, week.net_usd);

// 4. Same window, bucketed by UTC day (continuous timeline).
const daily = await leash.dailyTransactions({ agentMint, days: 14 });
for (const day of daily.daily) {
  console.log(day.date, '+', day.received_usd, '-', day.sent_usd, '=', day.net_usd);
}
The aggregate helpers paginate the underlying /v1/receipts/{agent} feed for you and stop early once a row falls outside the window. Stables (USDC, USDG, USDT) are summed as USD 1:1; non-stable receipts get counted but excluded from the USD math (non_usd_count).

Authenticated calls (X-Leash-Sig)

Agent-scoped endpoints verify an X-Leash-Sig header signed with the agent’s executive ed25519 keypair. Pass the keypair and mint to the constructor; the SDK stamps a fresh signature per request.
const leash = new LeashClient({
  agentMint: 'AjfeyP...',
  executiveSecretBase58: process.env.LEASH_EXECUTIVE_KEY!,
});

const createdKey = await leash.createAgentApiKey({ label: 'production worker' });
console.log('STORE THIS ONCE:', createdKey.plaintext);

const keys = await leash.listAgentApiKeys();
await leash.revokeAgentApiKey(keys.items[0].id);

const sub = await leash.createWebhook({
  url: 'https://my-app.example/leash-webhook',
  events: ['receipt.published', 'agent.treasury.withdraw'],
});
console.log('SAVE THIS SECRET:', sub.secret); // returned ONCE.
Agent-created API keys are stored with owner_wallet equal to the agent executive public key, agent_mint equal to the signed agent, and scopes: ["agent"]. Use them for legacy bearer-token surfaces such as payment-link CRUD and receipt reads when a runtime needs a LEASH_API_KEY. The signature envelope matches the API verifier byte-for-byte:
SHA-256(
  "${method}\n${pathWithQuery}\n${timestampIso}\n${sha256Hex(bodyBytes)}\n${agentMint}"
)
The SDK exports signRequest() and buildEnvelope() if you need to build the same headers manually for other transports (gRPC, fetch in a worker, etc). Payment links are hosted x402 paywalls — the API serves /x/{id} and the agent’s Asset Signer PDA receives funds. Authentication today is the legacy bearer-token API key; the SDK exposes typed CRUD so you don’t have to touch fetch directly.
const leash = new LeashClient({
  baseUrl: 'https://api.leash.market',
  apiKey: process.env.LEASH_API_KEY!,
});

const link = await leash.createPaymentLink({
  label: 'Tag a single image',
  owner_agent: 'AjfeyP...', // your agent mint
  method: 'POST',
  price: '$0.01',
  currency: 'USDC',
  response: {
    status: 200,
    mimeType: 'application/json',
    body: { ok: true },
  },
});

console.log('share this:', link.share_url); // -> https://api.leash.market/x/<id>

const monetized = await leash.createPaymentLink({
  label: 'JSONPlaceholder posts',
  owner_agent: 'AjfeyP...',
  method: 'GET',
  price: '$0.001',
  currency: 'USDC',
  response: {
    status: 200,
    mimeType: 'application/json',
    body: { ok: true, message: 'Payment accepted. Forwarding to upstream.' },
  },
  metadata: {
    upstream_url: 'https://jsonplaceholder.typicode.com/posts',
    provider_url: 'https://jsonplaceholder.typicode.com',
    pricing_type: 'fixed',
  },
});

console.log('paid upstream URL:', monetized.share_url);

const postAgent = await leash.createPaymentLink({
  label: 'Design agent',
  owner_agent: 'AjfeyP...',
  method: 'POST',
  price: '1 USDC',
  currency: 'USDC',
  response: {
    status: 200,
    mimeType: 'application/json',
    body: { ok: true, message: 'Payment accepted. Forwarding to design agent.' },
  },
  metadata: {
    upstream_url: 'https://api.example.com/design',
    provider_url: 'https://api.example.com',
    pricing_type: 'fixed',
    expected_request_body: {
      prompt: 'string',
      style: 'string',
      format: 'string',
    },
  },
});

console.log('POST paywall with expected body metadata:', postAgent.share_url);

const all = await leash.listPaymentLinks({ limit: 25 });
const one = await leash.getPaymentLink(link.id);
await leash.updatePaymentLink(link.id, { price: '$0.02' });
await leash.deletePaymentLink(link.id);
The SDK does not pay payment links — paying requires Solana signing, which @leashmarket/sdk deliberately avoids so it stays browser-friendly. To pay programmatically, use @leashmarket/buyer-kit (heavyweight, signs locally) or the @leashmarket/mcp host’s pay() tool.

Reputation cheat sheet

reputation.rating is normalised to [0, 1]:
rating = (1 - dispute_rate) * weight
weight = min(1, log10(settled_calls + 1) / 3)
  • A new agent with settled_calls: 0 has rating: 0 regardless of dispute rate. That’s intentional — you don’t have data yet.
  • An established agent with no disputes saturates at 1.0 around ~1000 settled calls.
  • dispute_rate = denied_calls / (settled_calls + denied_calls).

Errors

Network and non-2xx responses throw LeashError:
import { LeashError } from '@leashmarket/sdk';

try {
  await leash.discover();
} catch (err) {
  if (err instanceof LeashError) {
    console.log('status:', err.status, 'body:', err.body);
  }
}

Source