agent402-openclaw 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +28 -0
  2. package/cli.js +33 -2
  3. package/index.js +45 -8
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -42,6 +42,18 @@ export AGENT402_WALLET_KEY=0x... # an EVM key holding USDC on Base
42
42
  With no credits key present the proxy signs an x402 payment per call from that
43
43
  wallet. The key never leaves the machine.
44
44
 
45
+ Two ways the wallet can pay a metered call:
46
+
47
+ - **exact** (default): the 402 quotes the call from its body (input + your
48
+ `max_tokens` at the model's list price, x1.15) and the wallet pays that quote.
49
+ - **upto, actual usage**: run `agent402-openclaw permit2-approve` once (one USDC
50
+ approval transaction on Base; the wallet needs a little ETH for gas). From
51
+ then on the proxy authorizes the quote as a CEILING and the gateway settles
52
+ what the call actually cost x1.15, so a short answer costs a fraction of the
53
+ quote. `agent402-openclaw doctor` says which mode the wallet is in.
54
+
55
+ Card / credits buyers already pay actual usage on metered calls.
56
+
45
57
  ## Models and pricing
46
58
 
47
59
  `auto` (routed per prompt, flat $0.01/call) plus every id from
@@ -65,5 +77,21 @@ the key.
65
77
  - `agent402-openclaw setup [--credits-key K | --credits-key - (stdin) | AGENT402_CREDITS_KEY env] [--write] [--port N] [--flat]`
66
78
  - `agent402-openclaw proxy [--port N] [--upstream URL]`
67
79
  - `agent402-openclaw doctor`
80
+ - `agent402-openclaw permit2-approve [--rpc URL]` (one-time USDC approval so the wallet pays actual usage over upto)
81
+
82
+ ## What else the same key buys
83
+
84
+ The key or wallet that pays for chat also pays for the rest of the gateway and
85
+ the catalog, with one 402 shape and one receipt shape: OpenAI Responses and
86
+ Anthropic Messages wires, embeddings, rerank, images, video, speech,
87
+ transcription, grounded answers with citations, 500+ deterministic tools over
88
+ MCP or HTTP, wallet-keyed memory, finished reports (dossiers, insider flow, 13F,
89
+ domain audits, token risk, deep research) and monitors, plus routing that buys
90
+ from proven external sellers on the agent's behalf.
91
+
92
+ Why pay here, with every claim linked to its proof: https://agent402.tools/why.
93
+ Usage is priced under a ceiling quoted before you pay, a failed call is not
94
+ charged and the receipt proves it, a keyed retry never pays twice, and uptime and transactions are
95
+ published from outside production.
68
96
 
69
97
  Zero dependencies. MIT. Maintained by Havok Holdings LLC.
package/cli.js CHANGED
@@ -7,7 +7,7 @@ import { mkdirSync, writeFileSync, readFileSync, existsSync, chmodSync, statSync
7
7
  import { pathToFileURL } from "node:url";
8
8
  import { homedir } from "node:os";
9
9
  import { join } from "node:path";
10
- import { STATE_DIR, CREDITS_KEY_FILE, resolveCreditsKey, resolvePayFetch } from "./index.js";
10
+ import { STATE_DIR, CREDITS_KEY_FILE, resolveCreditsKey, resolvePayFetch, USDC_BASE, DEFAULT_BASE_RPC, uptoReady } from "./index.js";
11
11
  import { startProxy, loadRoutes, DEFAULT_UPSTREAM } from "./proxy.js";
12
12
  import { providerModelsConfig, stripTrailingSlashes, defaultPrimary, AUTO_ID, OPENCLAW_MIN_INPUT_CHARS } from "./models.js";
13
13
  import { DEFAULT_PORT, PROVIDER_ID } from "./provider.js";
@@ -45,7 +45,7 @@ function mergeInto(target, block, { port = DEFAULT_PORT } = {}) {
45
45
 
46
46
  async function main() {
47
47
  if (cmd === "help" || has("--help")) {
48
- out("agent402-openclaw setup [--credits-key a402_...|-] [--write] [--flat] | proxy [--port N] [--upstream URL] [--flat] | doctor");
48
+ out("agent402-openclaw setup [--credits-key a402_...|-] [--write] [--flat] | proxy [--port N] [--upstream URL] [--flat] | doctor | permit2-approve [--rpc URL]");
49
49
  return 0;
50
50
  }
51
51
  const upstream = stripTrailingSlashes(opt("--upstream") || process.env.AGENT402_UPSTREAM || DEFAULT_UPSTREAM);
@@ -108,11 +108,42 @@ async function main() {
108
108
  await new Promise(() => {}); // run until killed
109
109
  }
110
110
 
111
+ // One-time USDC -> Permit2 approval on Base so the wallet can pay ACTUAL
112
+ // usage over `upto` instead of the per-request quote over `exact`.
113
+ if (cmd === "permit2-approve") {
114
+ const pk = (process.env.AGENT402_WALLET_KEY || "").trim();
115
+ if (!/^0x[0-9a-fA-F]{64}$/.test(pk)) { console.error("AGENT402_WALLET_KEY (0x + 64 hex) is required"); return 2; }
116
+ try {
117
+ const [{ createPermit2ApprovalTx, getPermit2AllowanceReadParams }, { createWalletClient, createPublicClient, http }, { privateKeyToAccount }, { base }] = await Promise.all([import("@x402/evm/upto/client"), import("viem"), import("viem/accounts"), import("viem/chains")]);
118
+ const account = privateKeyToAccount(pk);
119
+ const rpc = (opt("--rpc") || process.env.AGENT402_BASE_RPC || DEFAULT_BASE_RPC).trim();
120
+ const pub = createPublicClient({ chain: base, transport: http(rpc) });
121
+ const read = () => pub.readContract(getPermit2AllowanceReadParams({ tokenAddress: USDC_BASE, ownerAddress: account.address }));
122
+ if (uptoReady(await read())) { out(`already approved: ${account.address} has a Permit2 allowance on USDC (Base); the proxy pays actual usage (upto)`); return 0; }
123
+ const tx = createPermit2ApprovalTx(USDC_BASE);
124
+ const wallet = createWalletClient({ account, chain: base, transport: http(rpc) });
125
+ const hash = await wallet.sendTransaction({ to: tx.to, data: tx.data });
126
+ out(`approval sent: ${hash} (waiting for confirmation)`);
127
+ await pub.waitForTransactionReceipt({ hash, timeout: 120_000 });
128
+ out(uptoReady(await read()) ? `approved: ${account.address} can now pay actual usage over upto. Restart the gateway.` : "transaction confirmed but the allowance still reads low - check the wallet on basescan");
129
+ return 0;
130
+ } catch (e) { console.error(`permit2-approve failed: ${String(e?.message || e).slice(0, 300)}`); return 1; }
131
+ }
132
+
111
133
  if (cmd === "doctor") {
112
134
  const key = resolveCreditsKey();
113
135
  out(`upstream: ${upstream}`);
114
136
  out(`credits key: ${key ? `present (${key.slice(0, 8)}…)` : "none"}`);
115
137
  out(`wallet key: ${/^0x[0-9a-fA-F]{64}$/.test(process.env.AGENT402_WALLET_KEY || "") ? "present" : "none"}`);
138
+ if (/^0x[0-9a-fA-F]{64}$/.test(process.env.AGENT402_WALLET_KEY || "")) {
139
+ try {
140
+ const [{ getPermit2AllowanceReadParams }, { createPublicClient, http }, { privateKeyToAccount }, { base }] = await Promise.all([import("@x402/evm/upto/client"), import("viem"), import("viem/accounts"), import("viem/chains")]);
141
+ const account = privateKeyToAccount(process.env.AGENT402_WALLET_KEY.trim());
142
+ const pub = createPublicClient({ chain: base, transport: http((process.env.AGENT402_BASE_RPC || DEFAULT_BASE_RPC).trim()) });
143
+ const allowance = await pub.readContract(getPermit2AllowanceReadParams({ tokenAddress: USDC_BASE, ownerAddress: account.address }));
144
+ out(`wallet ${account.address}: ${uptoReady(allowance) ? "pays actual usage (upto; Permit2 approved)" : "pays the per-request quote (exact) - run permit2-approve once to pay actual usage"}`);
145
+ } catch (e) { out(`wallet: allowance unreadable (${String(e?.message || e).slice(0, 80)})`); }
146
+ }
116
147
  try { const r = await loadRoutes(upstream, fetch, { pricing }); out(`gateway: ok, ${r.size} models (${pricing} pricing: ${[...r.values()].filter((x) => x.metered).length} metered)`); } catch (e) { out(`gateway: unreachable (${e?.message || e})`); return 1; }
117
148
  if (key) {
118
149
  try {
package/index.js CHANGED
@@ -22,18 +22,55 @@ export function resolveCreditsKey(pluginConfig = {}) {
22
22
  return null;
23
23
  }
24
24
 
25
- /** Optional x402 wallet payment: only when the peer deps are installed and a key is present. */
25
+ // x402 wallet payment - optional (peer deps) and, when the wallet has a
26
+ // Permit2 allowance, METERED: the `upto` scheme authorizes a CEILING (the
27
+ // per-request quote) and the gateway settles actual usage x 1.15, the way a
28
+ // per-token router bills. Without the allowance it pays `exact` (the quote).
29
+ export const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
30
+ export const BASE_CAIP2 = "eip155:8453";
31
+ export const DEFAULT_BASE_RPC = "https://mainnet.base.org";
32
+ // Permit2 approvals are max-uint by convention; anything this large is "approved".
33
+ export const PERMIT2_READY_MIN = 10n ** 12n; // 1,000,000 USDC (6 decimals): a real approval, not dust
34
+
35
+ /** Which 402 accept to pay: upto on Base when the client can (settle-actual),
36
+ * else exact on Base, else the first the client supports. Pure. */
37
+ export function selectAccept(accepts, { preferUpto = true } = {}) {
38
+ const list = Array.isArray(accepts) ? accepts : [];
39
+ if (preferUpto) { const u = list.find((a) => a?.scheme === "upto" && a?.network === BASE_CAIP2); if (u) return u; }
40
+ return list.find((a) => a?.scheme === "exact" && a?.network === BASE_CAIP2) || list[0];
41
+ }
42
+ export const uptoReady = (allowance) => { try { return BigInt(allowance ?? 0) >= PERMIT2_READY_MIN; } catch { return false; } };
43
+
26
44
  export async function resolvePayFetch(pluginConfig = {}, log = () => {}) {
27
45
  const pk = (pluginConfig.walletKey || process.env.AGENT402_WALLET_KEY || "").trim();
28
46
  if (!/^0x[0-9a-fA-F]{64}$/.test(pk)) return null;
29
47
  try {
30
- const [{ wrapFetchWithPayment }, { privateKeyToAccount }] = await Promise.all([import("@x402/fetch"), import("viem/accounts")]);
31
- const { toClientEvmSigner } = await import("@x402/evm");
32
- const { registerExactEvmScheme } = await import("@x402/evm/exact/client");
33
- const { x402Client } = await import("@x402/fetch");
34
- const client = new x402Client();
35
- registerExactEvmScheme(client, { signer: toClientEvmSigner(privateKeyToAccount(pk)) });
36
- return wrapFetchWithPayment(fetch, client);
48
+ const [{ wrapFetchWithPayment, x402Client }, { privateKeyToAccount }, { toClientEvmSigner }, { registerExactEvmScheme }] = await Promise.all([
49
+ import("@x402/fetch"), import("viem/accounts"), import("@x402/evm"), import("@x402/evm/exact/client"),
50
+ ]);
51
+ const account = privateKeyToAccount(pk);
52
+ const signer = toClientEvmSigner(account);
53
+ // Upto needs a Permit2 allowance on USDC (one approval transaction, ever).
54
+ let upto = false;
55
+ try {
56
+ const [{ UptoEvmScheme, getPermit2AllowanceReadParams }, { createPublicClient, http }, { base }] = await Promise.all([import("@x402/evm/upto/client"), import("viem"), import("viem/chains")]);
57
+ const pub = createPublicClient({ chain: base, transport: http((pluginConfig.baseRpc || process.env.AGENT402_BASE_RPC || DEFAULT_BASE_RPC).trim()) });
58
+ const allowance = await pub.readContract(getPermit2AllowanceReadParams({ tokenAddress: USDC_BASE, ownerAddress: account.address }));
59
+ upto = uptoReady(allowance);
60
+ const client = new x402Client((v, accepts) => selectAccept(accepts, { preferUpto: upto }));
61
+ registerExactEvmScheme(client, { signer });
62
+ if (upto) client.register(BASE_CAIP2, new UptoEvmScheme(signer));
63
+ log(upto
64
+ ? `[agent402-openclaw] x402 wallet ${account.address}: paying actual usage (upto, Permit2 allowance present) on Base; exact elsewhere`
65
+ : `[agent402-openclaw] x402 wallet ${account.address}: paying the per-request quote (exact). To pay actual usage instead, run \`agent402-openclaw permit2-approve\` once (one USDC approval transaction on Base; needs a little ETH for gas)`);
66
+ return wrapFetchWithPayment(fetch, client);
67
+ } catch (e) {
68
+ // The allowance read or the upto scheme failed: exact still works.
69
+ log(`[agent402-openclaw] upto unavailable (${String(e?.message || e).slice(0, 120)}) - paying exact`);
70
+ const client = new x402Client((v, accepts) => selectAccept(accepts, { preferUpto: false }));
71
+ registerExactEvmScheme(client, { signer });
72
+ return wrapFetchWithPayment(fetch, client);
73
+ }
37
74
  } catch (e) {
38
75
  log(`[agent402-openclaw] x402 wallet payment unavailable (${e?.message || e}); install @x402/fetch @x402/evm viem, or use a credits key`);
39
76
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent402-openclaw",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "OpenClaw model provider for Agent402: auto-routed and explicit LLM models at a flat per-call price, paid by card (prepaid credits key) or in USDC over x402 through a local proxy. Zero dependencies; x402 wallet payment is an optional peer.",
5
5
  "type": "module",
6
6
  "main": "index.js",