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

@linkexai/agent-sdk

v0.8.2

Published

Linkexai agent SDK — automatic USDC topup for AI agents

Readme

@linkexai/agent-sdk

Automatic USDC top-up for AI agents. The SDK is a small TypeScript library you import into your own agent program — it keeps your Linkexai account funded by paying x402 invoices from an agent wallet, then your normal OpenAI/Anthropic client spends that balance through the Linkexai gateway.

It is not a CLI or a daemon: nothing happens just by installing it. You call its methods from your code (or wire the auto-top-up wrapper once and forget it).

Install

npm install @linkexai/agent-sdk viem openai

Concepts

  • Owner wallet — your main wallet (holds USDC on Base). Used once to derive an agent key.
  • Agent key — a dedicated wallet you generate yourself and associate with the agent. It signs USDC payments. We only ever store its public address — never the key.
  • API key (sk-lxAgent-…) — identifies the account to the gateway; both the SDK and your model client use it.

Step 1 — Create the agent identity (once)

Bring your own wallet: generate a dedicated agent wallet yourself, then register it by proving you control it. The easiest path is the Developer Portal web UI — paste or connect the wallet, sign once, and it's associated with your account. To do it from code instead, sign an EIP-712 AgentOwnership message with the agent key over a single-use nonce and post it:

import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"
import { encryptAgentKey } from "@linkexai/agent-sdk"

// A wallet you generated yourself (here, a fresh one). Fund it with a small amount
// of USDC on Base — that balance is the most that can ever be at risk.
const privateKey = generatePrivateKey()
const agent = privateKeyToAccount(privateKey)

// 1. Get a single-use nonce bound to the agent address (requires your account session).
const nonceRes = await fetch("https://api.linkexai.com/api/user/login/siwe/nonce", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ wallet_addr: agent.address.toLowerCase() }),
})
const { data: { nonce } } = await nonceRes.json()

// 2. Sign the ownership proof with the AGENT key (proves you control the wallet).
const issuedAt = Math.floor(Date.now() / 1000)
const ownershipSig = await agent.signTypedData({
  domain: { name: "linkexai-agent-ownership", version: "1", chainId: 8453, verifyingContract: "0x0000000000000000000000000000000000000000" },
  types: { AgentOwnership: [{ name: "agent", type: "address" }, { name: "nonce", type: "string" }, { name: "issuedAt", type: "uint256" }] },
  primaryType: "AgentOwnership",
  message: { agent: agent.address, nonce, issuedAt: BigInt(issuedAt) },
})

// 3. Register server-side → returns the one-time API key. (Needs your account session.)
const res = await fetch("https://api.linkexai.com/api/user/self/agents", {
  method: "POST",
  headers: { "Content-Type": "application/json" /* + account session auth */ },
  body: JSON.stringify({
    label: "My OpenAI agent",
    agent_addr: agent.address,
    ownership_sig: ownershipSig,
    nonce,
    issued_at: issuedAt,
  }),
})
const { data } = await res.json()
console.log("API KEY (shown once):", data.api_key)

// Optional: store your agent key encrypted.
const backup = await encryptAgentKey(privateKey, process.env.BACKUP_PASSWORD!)

Store the API key as a secret and keep your agent private key safe — we never receive or store it.

No-code: run the auto-funder (Docker)

Don't want to write code? Run the standalone funder — a headless daemon that watches your agent's balance and tops it up when low. It runs in your own environment; the agent private key stays in the container and is never sent to Linkexai. It's fully decoupled from your agent (any framework, any language, local or hosted) — your agent just keeps calling with its API key.

# Pull the published image (or build locally: docker build -t linkex/funder sdk/typescript)
docker pull linkex/funder

# THRESHOLD_USD: top up when balance falls below this (USD)
# TOPUP_USD:     amount added each top-up (USD)
# POLL_SECONDS:  balance-check interval (min 5)
docker run --rm \
  -e LINKEXAI_API_KEY=sk-… \
  -e AGENT_PRIVATE_KEY=0x… \
  -e THRESHOLD_USD=1 \
  -e TOPUP_USD=5 \
  -e POLL_SECONDS=60 \
  linkex/funder

Keep the # notes on their own lines: a comment after a trailing \ (e.g. -e AGENT_PRIVATE_KEY=0x… # note \) swallows the line-continuation and silently truncates the command.

Or without Docker: LINKEXAI_API_KEY=… AGENT_PRIVATE_KEY=0x… npx -p @linkexai/agent-sdk linkexai-funder.

Pull failing with 403 Forbidden from a *.daocloud.io / dockerproxy.com URL? Your Docker daemon has a Docker Hub registry mirror that doesn't serve user-namespace repos. Remove the registry-mirrors entry from ~/.docker/daemon.json (or Docker Desktop → Settings → Docker Engine) and restart Docker, or pull on a network without the mirror.

Keep it running on a box that stays on (a small VPS, a server, a container host) so top-ups happen even overnight. For embedding the funder in code you already run, use the Funder engine or autoTopup mode below.

Funder environment variables (required: the API key + one funding credential; everything else optional):

| Var | Rail | Default | Notes | |---|---|---|---| | LINKEXAI_API_KEY | all | — | required — the agent's API key (sk-…) | | AGENT_PRIVATE_KEY | EVM | — | 0x + 64 hex; required on the EVM rail | | NETWORK | all | unset = EVM | CAIP-2 chain. solana:* selects the Solana rail; unset / eip155:* = EVM | | AGENT_SOLANA_SECRET_KEY | Solana | — | base58 or a 32/64-byte JSON array; required when NETWORK=solana:* | | AGENT_SOLANA_RPC_URL | Solana | public RPC | override the RPC used to build the transfer | | CIRCLE_WALLET_ADDRESS | Circle | — | auto (discover from the login session) or a 0x address; selects the key-less Circle rail | | CIRCLE_CLI_PATH | Circle | circle | path to the Circle CLI binary when it isn't on PATH | | SYMBOL | all | per-network | token symbol (e.g. USDC) | | LINKEXAI_BASE_URL | all | https://api.linkexai.com | gateway base URL | | THRESHOLD_USD | all | 1 | top up when the balance falls below this (USD) | | TOPUP_USD | all | 5 | amount added each top-up (USD) | | POLL_SECONDS | all | 60 | balance-check interval (min 5) | | FUNDER_STATE_FILE | all | ~/.linkexai-funder-state.json | where the double-spend cooldown persists across restarts; set none to disable |

Fund on Solana instead of an EVM chain by setting NETWORK=solana:* and AGENT_SOLANA_SECRET_KEY:

docker run --rm \
  -e LINKEXAI_API_KEY=sk-… \
  -e NETWORK=solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp \
  -e AGENT_SOLANA_SECRET_KEY=… \
  linkex/funder

Key-less funding with a Circle Agent Wallet (Base)

Don't want a private key to exist at all? The funder can pay through a Circle Agent Wallet instead: a 2-of-2 MPC wallet controlled by your email login, where Circle refuses to sign anything beyond the spending limits you set (per-transaction / daily / weekly / monthly caps, changed only with your email OTP). The container holds no key — just a revocable login session.

The funder discovers the wallet from your login session — set CIRCLE_WALLET_ADDRESS=auto and you never hand-copy an address.

One-time setup, in your own terminal (placeholders in CAPS are yours to fill — they are bracket-free on purpose, since an unreplaced <…> is shell redirection):

npm install -g @circle-fin/cli     # Node >= 20.18.2
export CIRCLE_ACCEPT_TERMS=1        # accept the CLI terms (the official image bakes this in)
circle wallet login YOUR-EMAIL   # one command — it prompts for the OTP emailed to you
circle wallet list --chain BASE --type agent   # login already provisioned it; prints the 0x address

# Fund the wallet with USDC on Base (an exchange withdrawal, or:
#   circle wallet fund --address YOUR-0x-ADDRESS --chain BASE --amount 20 --method fiat)

# Set YOUR spending limits (a second email OTP confirms — never share it).
# The address is filled in from your session — nothing to copy:
circle wallet limit set --chain BASE --policy-type stablecoin \
  --per-tx 5 --daily 20 --weekly 50 --monthly 100 \
  --address "$(circle wallet list --chain BASE --type agent --quiet | head -n1)"

Then run the funder with CIRCLE_WALLET_ADDRESS=auto instead of any key. The Circle session lives in the CLI's home directory, so give the container a volume:

docker volume create circle-session

# One-time inside the container's volume: log in (repeat when the session expires).
# One command — it prompts for the OTP emailed to you; paste the 6-digit code.
docker run --rm -it -v circle-session:/home/funder \
  --entrypoint circle linkex/funder wallet login YOUR-EMAIL

docker run --rm \
  -e LINKEXAI_API_KEY=sk-… \
  -e CIRCLE_WALLET_ADDRESS=auto \
  -e THRESHOLD_USD=1 \
  -e TOPUP_USD=5 \
  -e POLL_SECONDS=60 \
  -v circle-session:/home/funder \
  linkex/funder

Good to know:

  • auto vs a pinned address. CIRCLE_WALLET_ADDRESS=auto uses the one agent wallet in your session. If the session holds more than one, the funder refuses to guess and lists them — set CIRCLE_WALLET_ADDRESS to the exact 0x address you want. See your wallets any time with circle wallet list --chain BASE --type agent.
  • Sessions last ~28 days. When one expires the funder exits with the exact re-login commands; run them and restart. circle wallet logout is your kill switch — the funder is stranded instantly, funds untouched.
  • Two kinds of OTP. Both are typed interactively at the prompt. A login OTP only authenticates the session; a policy-change OTP raises your spending limits, so only ever type that one yourself — never relay it — which is what keeps the limits trustworthy.
  • First payment needs a deployed wallet. Circle agent wallets are smart contract accounts: before the first signature, make any one transaction from the wallet (e.g. circle wallet transfer YOUR-0x-ADDRESS --amount 0.01 --token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 --address YOUR-0x-ADDRESS --chain BASEboth addresses are your own agent wallet: it's a zero-value self-transfer that only deploys the account; gas is sponsored).
  • Base only for now (NETWORK defaults to eip155:8453); more chains as Circle's chain support and x402 facilitators expand.
  • SDK equivalent: pass circle: { walletAddress: "0x…" } to LinkexaiClient (the SDK needs the literal address — get it with circle wallet list --chain BASE --type agent; auto is a funder-daemon convenience) instead of agentPrivateKey.

Step 2 — Use it in your agent (every run)

Manual mode (you decide when to check)

import { LinkexaiClient } from "@linkexai/agent-sdk"
import OpenAI from "openai"

const linkexai = new LinkexaiClient({
  apiKey: process.env.LINKEXAI_API_KEY!,
  agentPrivateKey: process.env.AGENT_PRIVATE_KEY! as `0x${string}`,
  thresholdUSD: 1.0,    // top up when balance < $1
  topupAmountUSD: 5.0,  // buy $5 of quota when topping up
})

const openai = new OpenAI({
  apiKey: process.env.LINKEXAI_API_KEY,
  baseURL: "https://api.linkexai.com/v1",
})

await linkexai.ensureBalance()                  // check before calls; tops up if low
const r = await openai.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Hello" }],
})

ensureBalance() reads your balance and, if it's below thresholdUSD, creates (or resumes) an x402 order and pays it from the agent wallet. Call it wherever you want — once at startup, before each request, or before expensive batches.

Always-funded mode (wire once, runs on every call)

Set autoTopup: true and pass wrapFetch() as the vendor client's fetch. Every request that client makes now runs ensureBalance() first — no manual calls:

const linkexai = new LinkexaiClient({
  apiKey: process.env.LINKEXAI_API_KEY!,
  agentPrivateKey: process.env.AGENT_PRIVATE_KEY! as `0x${string}`,
  thresholdUSD: 1.0,
  topupAmountUSD: 5.0,
  autoTopup: true,                       // enable always-funded mode
  // autoTopupMinIntervalMs: 10_000,     // optional: re-check at most every 10s
})

const openai = new OpenAI({
  apiKey: process.env.LINKEXAI_API_KEY,
  baseURL: "https://api.linkexai.com/v1",
  fetch: linkexai.wrapFetch(),           // ← wire once
})

// From here on, every call auto-tops-up first. No ensureBalance() in your code.
const r = await openai.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Hello" }],
})

Notes:

  • A burst of concurrent requests shares a single balance check / top-up (coalesced).
  • The SDK's own balance/recharge calls use the raw fetch, so wrapping causes no recursion.
  • autoTopup: false (default) makes wrapFetch() a transparent passthrough — so you can toggle the behavior from one flag without changing where wrapFetch() is wired.
  • By default the balance is checked before every request; set autoTopupMinIntervalMs to skip checks that fall within that window and avoid an extra round-trip per call.

API

| Member | Description | |---|---| | new LinkexaiClient(opts) | apiKey, agentPrivateKey?, baseURL?, thresholdUSD?, topupAmountUSD?, autoTopup?, autoTopupMinIntervalMs?, network? (CAIP-2 rail), symbol? (token), agentSolanaSecretKey?, agentSolanaRpcUrl?, circle? ({ walletAddress, cliPath? } — key-less Circle rail) | | getBalance() | Returns { quota, quota_usd, agent_spending? }. | | recharge(amountUSD) | Create/resume an x402 order, sign, and pay it. | | ensureBalance(needed?) | If balance < needed ?? thresholdUSD, recharge topupAmountUSD. Concurrent calls are coalesced. | | wrapFetch(baseFetch?) | Returns a fetch that runs ensureBalance() first when autoTopup is on. | | encryptAgentKey / decryptAgentKey | AES-256-GCM backup of the agent key. |

Run

LINKEXAI_API_KEY=sk-lxAgent-… AGENT_PRIVATE_KEY=0x… npx tsx agent.ts