run402 4.118.0 → 4.120.0

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 (45) hide show
  1. package/cli.mjs +2 -2
  2. package/lib/command-manifest.mjs +3 -0
  3. package/lib/contracts.mjs +126 -15
  4. package/lib/init.mjs +1 -1
  5. package/lib/orgs.mjs +18 -8
  6. package/lib/wallets.mjs +20 -61
  7. package/package.json +1 -1
  8. package/sdk/dist/namespaces/contracts.d.ts +47 -0
  9. package/sdk/dist/namespaces/contracts.d.ts.map +1 -1
  10. package/sdk/dist/namespaces/contracts.js +36 -0
  11. package/sdk/dist/namespaces/contracts.js.map +1 -1
  12. package/sdk/dist/namespaces/org.d.ts.map +1 -1
  13. package/sdk/dist/namespaces/org.js +5 -3
  14. package/sdk/dist/namespaces/org.js.map +1 -1
  15. package/sdk/dist/namespaces/org.types.d.ts +5 -6
  16. package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
  17. package/sdk/dist/node/index.d.ts +2 -0
  18. package/sdk/dist/node/index.d.ts.map +1 -1
  19. package/sdk/dist/node/index.js +2 -0
  20. package/sdk/dist/node/index.js.map +1 -1
  21. package/sdk/dist/node/init.d.ts +3 -0
  22. package/sdk/dist/node/init.d.ts.map +1 -1
  23. package/sdk/dist/node/init.js +11 -8
  24. package/sdk/dist/node/init.js.map +1 -1
  25. package/sdk/dist/node/onchain-balances.d.ts +24 -0
  26. package/sdk/dist/node/onchain-balances.d.ts.map +1 -0
  27. package/sdk/dist/node/onchain-balances.js +72 -0
  28. package/sdk/dist/node/onchain-balances.js.map +1 -0
  29. package/sdk/dist/node/paid-fetch.d.ts +26 -1
  30. package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
  31. package/sdk/dist/node/paid-fetch.js +93 -4
  32. package/sdk/dist/node/paid-fetch.js.map +1 -1
  33. package/sdk/dist/node/status.d.ts +4 -0
  34. package/sdk/dist/node/status.d.ts.map +1 -1
  35. package/sdk/dist/node/status.js +6 -47
  36. package/sdk/dist/node/status.js.map +1 -1
  37. package/sdk/dist/node/tempo.d.ts +30 -0
  38. package/sdk/dist/node/tempo.d.ts.map +1 -0
  39. package/sdk/dist/node/tempo.js +36 -0
  40. package/sdk/dist/node/tempo.js.map +1 -0
  41. package/sdk/dist/scoped.d.ts +4 -1
  42. package/sdk/dist/scoped.d.ts.map +1 -1
  43. package/sdk/dist/scoped.js +9 -0
  44. package/sdk/dist/scoped.js.map +1 -1
  45. package/vault-surface.json +1 -1
package/cli.mjs CHANGED
@@ -32,7 +32,7 @@ Commands, grouped by when you reach for them:
32
32
 
33
33
  SET UP — get an agent funded and entitled
34
34
  init Set up the local wallet, funding, and check tier status (x402 default)
35
- init mpp Set up with MPP payment rail (Tempo Moderato testnet)
35
+ init mpp Set up with MPP payment rail (pathUSD on Tempo; faucet-funded on Moderato)
36
36
  wallets Manage local wallets (list, current, new, use, fund, balance, import)
37
37
  tier Manage your tier and its lease (status, set)
38
38
  redeem Redeem a promo code into your organization's allowance
@@ -128,7 +128,7 @@ Examples:
128
128
 
129
129
  Getting started:
130
130
  run402 init Set up with x402 (Base Sepolia)
131
- run402 init mpp Set up with MPP (Tempo Moderato)
131
+ run402 init mpp Set up with MPP (pathUSD on Tempo)
132
132
  run402 tier set prototype Set a tier (prototype is free)
133
133
  run402 up --name my-app -y Create and deploy this app (explicit destination)
134
134
  run402 ci link github --project prj_... --manifest run402.deploy.json
@@ -466,6 +466,9 @@ export const COMMAND_MANIFEST = [
466
466
  { path: ["contracts", "set-alert"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--threshold-wei", "1"] },
467
467
  { path: ["contracts", "call"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--to", "0x4444444444444444444444444444444444444444", "--abi", "[]", "--fn", "noop", "--args", "[]"] },
468
468
  { path: ["contracts", "deploy"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--bytecode", "0x00"] },
469
+ { path: ["contracts", "transfers", "create"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--to", "0x4444444444444444444444444444444444444444", "--amount", "1"] },
470
+ { path: ["contracts", "transfers", "get"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--transfer", "txf_gate1"] },
471
+ { path: ["contracts", "transfers", "list"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1"] },
469
472
  { path: ["contracts", "read"], positionals: [], projectScoped: false, legacyPositionalProject: false, minimalArgs: ["--chain", "base-sepolia", "--to", "0x4444444444444444444444444444444444444444", "--abi", "[]", "--fn", "noop", "--args", "[]"] },
470
473
  { path: ["contracts", "status"], positionals: [p("call_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["ccall_gate1"] },
471
474
  { path: ["contracts", "drain"], positionals: [p("signer_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["cwlt_gate1", "--to", "0x4444444444444444444444444444444444444444", "--confirm"] },
package/lib/contracts.mjs CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  failUnknownSubcommand,
12
12
  } from "./argparse.mjs";
13
13
 
14
- const HELP = `run402 contracts — KMS-backed Ethereum signers for smart-contract calls
14
+ const HELP = `run402 contracts — signers, your app's wallet, for smart-contract calls
15
15
 
16
16
  Pricing: $0.04/day per signer ($1.20/month) plus $0.000005 per contract call.
17
17
  Signer creation requires $1.20 of allowance (30 days of rent).
@@ -22,19 +22,25 @@ Usage:
22
22
 
23
23
  Subcommands:
24
24
  provision-signer <project_id> --chain <base-mainnet|base-sepolia> [--recovery 0x...]
25
- Provision a KMS signer ($0.04/day, requires $1.20 prepay).
25
+ Provision a signer ($0.04/day wallet rent, requires $1.20 prepay).
26
26
  get-signer <project_id> <signer_id>
27
27
  Get signer metadata + live native balance.
28
28
  list-signers <project_id>
29
- List all KMS signers for the project (includes deleted).
29
+ List all signers for the project (includes deleted).
30
+ transfers create <signer_id> --to 0x... --amount <base units> [--asset usdc] [--chain <base-mainnet|base-sepolia>] [--idempotency-key <k>]
31
+ Send a token from the signer with no gas on it: the platform's facilitator settles the signed EIP-3009 authorization and pays the gas. One signing fee.
32
+ transfers get <signer_id> --transfer <transfer_id>
33
+ Read one transfer (status, tx hash, nonce, failure phase).
34
+ transfers list <signer_id>
35
+ The signer's transfers, newest first.
30
36
  set-recovery <project_id> <signer_id> [--address 0x... | --clear]
31
37
  Set/clear the optional recovery address.
32
38
  set-alert <project_id> <signer_id> --threshold-wei <n>
33
39
  Set the low-balance alert threshold (in wei).
34
40
  call <project_id> <signer_id> --to 0x... --abi <json> --fn <name> --args <json> [--value-wei <n>] [--idempotency-key <k>]
35
- Submit a contract write call (chain gas + $0.000005 KMS sign fee).
41
+ Submit a contract write call (gas from the signer's balance, topped up at cost when short, + $0.000005 signing fee).
36
42
  deploy <project_id> <signer_id> --bytecode 0x... [--chain <base-mainnet|base-sepolia>] [--value-wei <n>] [--idempotency-key <k>]
37
- Deploy a contract (chain gas + $0.000005 KMS sign fee). Returns deterministic CREATE address synchronously.
43
+ Deploy a contract (gas from the signer's balance + $0.000005 signing fee). Returns deterministic CREATE address synchronously.
38
44
  read --chain <chain> --to 0x... --abi <json> --fn <name> --args <json>
39
45
  Read-only contract call (free).
40
46
  status <project_id> <call_id>
@@ -42,7 +48,7 @@ Subcommands:
42
48
  drain <project_id> <signer_id> --to 0x... --confirm
43
49
  Drain native balance to a destination address. Works on suspended signers.
44
50
  delete <project_id> <signer_id> --confirm
45
- Schedule the KMS key for deletion (refused if balance >= dust).
51
+ Schedule the signing key for deletion (refused if balance >= dust).
46
52
 
47
53
  Examples:
48
54
  run402 contracts provision-signer proj_abc --chain base-mainnet
@@ -50,7 +56,22 @@ Examples:
50
56
  `;
51
57
 
52
58
  const SUB_HELP = {
53
- "provision-signer": `run402 contracts provision-signer — Provision a KMS-backed signer
59
+ transfers: `run402 contracts transfers — Send a token from a signer with no gas on it
60
+
61
+ Usage:
62
+ run402 contracts transfers create <signer_id> --to 0x... --amount <base units> [--asset usdc] [--chain <base-mainnet|base-sepolia>] [--idempotency-key <k>] [--project <project_id>]
63
+ run402 contracts transfers get <signer_id> --transfer <transfer_id> [--project <project_id>]
64
+ run402 contracts transfers list <signer_id> [--project <project_id>]
65
+
66
+ The signer is the payer of an x402 payment: Run402 signs the token's EIP-3009
67
+ authorization with the signer's key and its facilitator settles it and pays the
68
+ gas. One $0.000005 signing fee, no gas, no ETH on the signer. --amount is in the
69
+ asset's base units (USDC: 6 decimals; 100000 is $0.10). An idempotency key
70
+ makes a repeat return the transfer already made; a transfer that failed before
71
+ settlement re-executes under its key, one that died in settlement never does.
72
+ A "unknown" status means the settle outcome never came back: poll it with get.
73
+ `,
74
+ "provision-signer": `run402 contracts provision-signer — Provision a signer
54
75
 
55
76
  Usage:
56
77
  run402 contracts provision-signer --chain <chain> [--project <project_id>] [options]
@@ -91,11 +112,11 @@ Legacy (still supported):
91
112
  run402 contracts call <project_id> <signer_id> --to 0x... --abi <json>
92
113
  --fn <name> --args <json> [options]
93
114
  `,
94
- deploy: `run402 contracts deploy — Deploy a smart contract from a KMS signer
115
+ deploy: `run402 contracts deploy — Deploy a smart contract from a signer
95
116
 
96
- KMS-signs a contract-creation transaction (to: null + data: bytecode) and broadcasts.
117
+ the platform's key custody-signs a contract-creation transaction (to: null + data: bytecode) and broadcasts.
97
118
  Returns the deterministic CREATE address synchronously — known from (signer, nonce)
98
- before the tx confirms. Cost: chain gas at-cost + $0.000005 KMS sign fee.
119
+ before the tx confirms. Cost: chain gas at-cost + $0.000005 signing fee.
99
120
 
100
121
  Usage:
101
122
  run402 contracts deploy <signer_id> --bytecode 0x... [--project <project_id>] [options]
@@ -130,7 +151,7 @@ Usage:
130
151
  Legacy (still supported):
131
152
  run402 contracts drain <project_id> <signer_id> --to 0x... --confirm
132
153
  `,
133
- delete: `run402 contracts delete — Schedule the KMS key for deletion
154
+ delete: `run402 contracts delete — Schedule the signing key for deletion
134
155
 
135
156
  Usage:
136
157
  run402 contracts delete <signer_id> --confirm [--project <project_id>]
@@ -153,7 +174,7 @@ Arguments:
153
174
  Examples:
154
175
  run402 contracts get-signer prj_abc123 cwlt_abc123
155
176
  `,
156
- "list-signers": `run402 contracts list-signers — List all KMS signers for a project
177
+ "list-signers": `run402 contracts list-signers — List all signers for a project
157
178
 
158
179
  Usage:
159
180
  run402 contracts list-signers [--project <project_id>]
@@ -344,7 +365,7 @@ async function call(projectId, signerId, args) {
344
365
  fail({
345
366
  code: "BAD_USAGE",
346
367
  message: "Required flags: --to, --abi, --fn, --args.",
347
- hint: "Cost: chain gas + $0.000005 KMS sign fee.",
368
+ hint: "Cost: chain gas + $0.000005 signing fee.",
348
369
  });
349
370
  }
350
371
  assertAllowedValue(chain, ["base-mainnet", "base-sepolia"], "--chain");
@@ -369,6 +390,89 @@ async function call(projectId, signerId, args) {
369
390
  }
370
391
  }
371
392
 
393
+ /**
394
+ * `run402 contracts transfers create|get|list` — a signer sends a token with
395
+ * no gas on it: the gateway signs the EIP-3009 authorization with the
396
+ * signer's key and the platform's facilitator settles it and pays the gas.
397
+ */
398
+ async function transfers(projectId, args) {
399
+ const [transferAction, ...rest] = Array.isArray(args) ? args : [];
400
+ if (transferAction === "create") return transfersCreate(projectId, rest[0], rest.slice(1));
401
+ if (transferAction === "get") return transfersGet(projectId, rest[0], rest.slice(1));
402
+ if (transferAction === "list") return transfersList(projectId, rest[0], rest.slice(1));
403
+ return fail({
404
+ code: "BAD_USAGE",
405
+ message: `Unknown contracts transfers verb: ${transferAction ?? "(none)"}`,
406
+ hint: "Verbs: create <signer_id> --to 0x… --amount <base units> [--asset usdc] [--chain …] [--idempotency-key <k>]; get <signer_id> --transfer <transfer_id>; list <signer_id>.",
407
+ });
408
+ }
409
+
410
+ async function transfersCreate(projectId, signerId, args) {
411
+ const parsedArgs = normalizeArgv(args);
412
+ const valueFlags = ["--to", "--amount", "--asset", "--chain", "--idempotency-key"];
413
+ assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
414
+ const extra = positionalArgs(parsedArgs, valueFlags);
415
+ if (!signerId || !/^[a-z]+_[A-Za-z0-9]{8,}$/.test(signerId)) {
416
+ fail({ code: "BAD_USAGE", message: "Missing <signer_id>.", hint: "run402 contracts transfers create <signer_id> --to 0x… --amount <base units>" });
417
+ }
418
+ if (extra.length > 0) {
419
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts transfers create: ${extra[0]}` });
420
+ }
421
+ const to = flagValue(parsedArgs, "--to");
422
+ const amount = flagValue(parsedArgs, "--amount");
423
+ const asset = flagValue(parsedArgs, "--asset") || "usdc";
424
+ const chain = flagValue(parsedArgs, "--chain") || "base-mainnet";
425
+ const idempotency = flagValue(parsedArgs, "--idempotency-key");
426
+ if (!to || !amount) {
427
+ fail({
428
+ code: "BAD_USAGE",
429
+ message: "Required flags: --to, --amount.",
430
+ hint: "--amount is in the asset's base units as a decimal string (USDC: 6 decimals, so 100000 is $0.10). Cost: one $0.000005 signing fee, no gas.",
431
+ });
432
+ }
433
+ assertAllowedValue(chain, ["base-mainnet", "base-sepolia"], "--chain");
434
+ validateEvmAddress(to, "--to");
435
+ if (!/^[1-9][0-9]*$/.test(amount)) {
436
+ fail({ code: "BAD_USAGE", message: "--amount must be a positive integer in the asset's base units." });
437
+ }
438
+ try {
439
+ const data = await getSdk().contracts.transfer(projectId, {
440
+ signerId, chain, asset, to, amount, idempotencyKey: idempotency ?? undefined,
441
+ });
442
+ console.log(JSON.stringify(data, null, 2));
443
+ } catch (err) {
444
+ reportSdkError(err);
445
+ }
446
+ }
447
+
448
+ async function transfersGet(projectId, signerId, args) {
449
+ const parsedArgs = normalizeArgv(args);
450
+ assertKnownFlags(parsedArgs, ["--transfer", "--help", "-h"], ["--transfer"]);
451
+ const extra = positionalArgs(parsedArgs, ["--transfer"]);
452
+ const transferId = flagValue(parsedArgs, "--transfer");
453
+ if (!signerId || !transferId || extra.length > 0) {
454
+ fail({ code: "BAD_USAGE", message: "Usage: run402 contracts transfers get <signer_id> --transfer <transfer_id>" });
455
+ }
456
+ try {
457
+ console.log(JSON.stringify(await getSdk().contracts.transferStatus(projectId, signerId, transferId), null, 2));
458
+ } catch (err) {
459
+ reportSdkError(err);
460
+ }
461
+ }
462
+
463
+ async function transfersList(projectId, signerId, args) {
464
+ const parsedArgs = normalizeArgv(args);
465
+ assertKnownFlags(parsedArgs, ["--help", "-h"], []);
466
+ if (!signerId) {
467
+ fail({ code: "BAD_USAGE", message: "Usage: run402 contracts transfers list <signer_id>" });
468
+ }
469
+ try {
470
+ console.log(JSON.stringify(await getSdk().contracts.listTransfers(projectId, signerId), null, 2));
471
+ } catch (err) {
472
+ reportSdkError(err);
473
+ }
474
+ }
475
+
372
476
  async function deploy(projectId, signerId, args) {
373
477
  const parsedArgs = normalizeArgv(args);
374
478
  const valueFlags = ["--bytecode", "--value-wei", "--chain", "--idempotency-key"];
@@ -385,7 +489,7 @@ async function deploy(projectId, signerId, args) {
385
489
  fail({
386
490
  code: "BAD_USAGE",
387
491
  message: "Required flag: --bytecode (0x-prefixed hex; full creation calldata = creation bytecode + ABI-encoded constructor args, concatenated client-side).",
388
- hint: "Cost: chain gas + $0.000005 KMS sign fee. Returns deterministic CREATE address synchronously.",
492
+ hint: "Cost: chain gas + $0.000005 signing fee. Returns deterministic CREATE address synchronously.",
389
493
  });
390
494
  }
391
495
  assertAllowedValue(chain, ["base-mainnet", "base-sepolia"], "--chain");
@@ -469,7 +573,7 @@ async function drain(projectId, signerId, args) {
469
573
  fail({
470
574
  code: "BAD_USAGE",
471
575
  message: "Required: --to 0x... and --confirm.",
472
- hint: "Cost: chain gas + $0.000005 KMS sign fee.",
576
+ hint: "Cost: chain gas + $0.000005 signing fee.",
473
577
  });
474
578
  }
475
579
  validateEvmAddress(to, "--to");
@@ -508,6 +612,13 @@ export async function run(sub, args) {
508
612
  // unknown subcommands (and the anonymous `read`) never trip project
509
613
  // resolution.
510
614
  const select = () => resolveProjectSelector(normalizeArgv(Array.isArray(args) ? args : []));
615
+ // The transfers group dispatches with `if` (not `case`) so the sync test
616
+ // inventories its leaf actions through parseContractsTransferActions.
617
+ if (sub === "transfers") {
618
+ const { projectId, rest } = select();
619
+ await transfers(projectId, rest);
620
+ return;
621
+ }
511
622
  switch (sub) {
512
623
  case "provision-signer": { const { projectId, rest } = select(); await provisionSigner(projectId, rest); break; }
513
624
  case "get-signer": { const { projectId, rest } = select(); await getSigner(projectId, rest[0], rest.slice(1)); break; }
package/lib/init.mjs CHANGED
@@ -17,7 +17,7 @@ Usage:
17
17
  run402 init --api-base <url>
18
18
  Configure a Run402 Core/API target for the active
19
19
  profile without setting up Cloud payment.
20
- run402 init mpp Set up with MPP (Tempo Moderato)
20
+ run402 init mpp Set up with MPP (pathUSD on Tempo)
21
21
  run402 init lightning Set up the Lightning wallet: a budgeted wallet
22
22
  minted on Run402's Hub (custody: Run402), plus the
23
23
  Base wallet as the x402 fallback. Lightning
package/lib/orgs.mjs CHANGED
@@ -21,7 +21,7 @@ Usage:
21
21
  run402 orgs list
22
22
  run402 orgs get [<org_id>]
23
23
  run402 orgs rename [<org_id>] --name <display_name> (or: --clear to remove the label)
24
- run402 orgs payout-wallet [<org_id>] <wallet_address> (or: --clear to remove the explicit default)
24
+ run402 orgs payout-wallet [<org_id>] <wallet_address> (or: --signer <signer_id>; --clear removes the explicit default)
25
25
  run402 orgs lightning-receiver set [<org_id>] <nwc-uri | -> (receive-only NWC pairing; - reads it from stdin)
26
26
  run402 orgs lightning-receiver status [<org_id>]
27
27
  run402 orgs lightning-receiver delete [<org_id>]
@@ -161,6 +161,7 @@ call after a dropped response can never double-bill.
161
161
 
162
162
  Usage:
163
163
  run402 orgs payout-wallet [<org_id>] <wallet_address>
164
+ run402 orgs payout-wallet [<org_id>] --signer <signer_id>
164
165
  run402 orgs payout-wallet [<org_id>] --clear
165
166
 
166
167
  Legacy (still supported):
@@ -170,6 +171,11 @@ Admin/owner-only + step-up gated. The wallet must already be active and linked
170
171
  to the same org. This wallet receives x402 settlement for function web routes
171
172
  that declare pricing.pay_to = "org_default_payout". Pass --clear to remove the
172
173
  explicit default; a single active org wallet may still resolve automatically.
174
+ Pass --signer <signer_id> to make one of the org's own signers (your app's
175
+ wallet, run402 contracts provision-signer) the payout address instead, so what
176
+ your priced routes earn is what your app pays out from. The signer must be
177
+ active and owned by a project of this org; while it is selected, that project
178
+ cannot be transferred out.
173
179
 
174
180
  The JSON response includes recovery.status, active_wallet_count, and
175
181
  next_actions for PAYOUT_WALLET_REQUIRED / PAYOUT_WALLET_AMBIGUOUS setup.
@@ -478,20 +484,24 @@ async function rename(args) {
478
484
 
479
485
  async function payoutWallet(args) {
480
486
  const a = normalizeArgv(args);
481
- assertKnownFlags(a, ["--org", "--address", "--clear", "--help", "-h"], ["--org", "--address"]);
487
+ assertKnownFlags(a, ["--org", "--address", "--signer", "--clear", "--help", "-h"], ["--org", "--address", "--signer"]);
482
488
  const clear = a.includes("--clear");
483
489
  const walletFlag = flagValue(a, "--address");
484
- const single = clear || walletFlag !== null;
485
- const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org", "--address"], { cmd: "orgs" });
490
+ const signerFlag = flagValue(a, "--signer");
491
+ if (signerFlag !== null && (clear || walletFlag !== null)) {
492
+ fail({ code: "BAD_USAGE", message: "--signer cannot be combined with --address or --clear." });
493
+ }
494
+ const single = clear || walletFlag !== null || signerFlag !== null;
495
+ const { orgId: org, rest: pos } = await takeOrgPositional(a, ["--org", "--address", "--signer"], { cmd: "orgs" });
486
496
  requireRest(pos, {
487
497
  min: single ? 0 : 1,
488
498
  max: single ? 0 : 1,
489
- command: "run402 orgs payout-wallet [<org_id>] --address <wallet_address>",
490
- missing: "Missing <wallet_address> (use --address, or pass --clear).",
499
+ command: "run402 orgs payout-wallet [<org_id>] --address <wallet_address> | --signer <signer_id> | --clear",
500
+ missing: "Missing <wallet_address> (use --address, --signer <signer_id>, or --clear).",
491
501
  });
492
- const walletAddress = clear ? null : (walletFlag ?? pos[0]);
502
+ const input = signerFlag !== null ? { signerId: signerFlag } : { walletAddress: clear ? null : (walletFlag ?? pos[0]) };
493
503
  try {
494
- console.log(JSON.stringify(await getSdk().org(org).setPayoutWallet({ walletAddress }), null, 2));
504
+ console.log(JSON.stringify(await getSdk().org(org).setPayoutWallet(input), null, 2));
495
505
  } catch (err) {
496
506
  reportSdkError(err);
497
507
  }
package/lib/wallets.mjs CHANGED
@@ -15,6 +15,7 @@ import { failUnknownSubcommand } from "./argparse.mjs";
15
15
  import { fail, reportLocalOrSdkError, reportSdkError } from "./sdk-errors.mjs";
16
16
  import { readWallet as readActiveWallet, saveWallet as saveActiveWallet } from "./config.mjs";
17
17
  import { getSdk } from "./sdk.mjs";
18
+ import { ALL_ON_CHAIN_BALANCE_KEYS, readOnChainBalances, TEMPO_MODERATO } from "#sdk/node";
18
19
 
19
20
  const HELP = `run402 wallets — manage named wallets (profiles)
20
21
 
@@ -175,25 +176,11 @@ async function cmdLightning(args) {
175
176
  }
176
177
 
177
178
  // ── fund / balance (the ACTIVE wallet) ─────────────────────────────────────
178
-
179
- const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
180
- const USDC_MAINNET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
181
- const USDC_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
182
- const PATH_USD = "0x20c0000000000000000000000000000000000000";
183
- const TEMPO_RPC = "https://rpc.moderato.tempo.xyz/";
184
-
185
- async function loadDeps() {
186
- const { generatePrivateKey, privateKeyToAccount } = await import("viem/accounts");
187
- const { createPublicClient, http, defineChain } = await import("viem");
188
- const { base, baseSepolia } = await import("viem/chains");
189
- const tempoModerato = defineChain({
190
- id: 42431,
191
- name: "Tempo Moderato",
192
- nativeCurrency: { name: "pathUSD", symbol: "pathUSD", decimals: 6 },
193
- rpcUrls: { default: { http: [TEMPO_RPC] } },
194
- });
195
- return { generatePrivateKey, privateKeyToAccount, createPublicClient, http, base, baseSepolia, tempoModerato };
196
- }
179
+ //
180
+ // Balances come from the SDK's one reader, under the snake_case keys
181
+ // `run402 status` and `run402 init` share (spec status-account-balances):
182
+ // base_mainnet_usd_micros, base_sepolia_usd_micros,
183
+ // tempo_mainnet_pathusd_micros, tempo_moderato_pathusd_micros.
197
184
 
198
185
  async function cmdFund() {
199
186
  const w = readActiveWallet();
@@ -206,12 +193,9 @@ async function cmdFund() {
206
193
  }
207
194
 
208
195
  if (w.rail === "mpp") {
209
- // Tempo Moderato faucet — instant, no polling needed
210
- const { createPublicClient, http, tempoModerato } = await loadDeps();
211
- const client = createPublicClient({ chain: tempoModerato, transport: http() });
212
- const before = await readUsdcBalance(client, PATH_USD, w.address).catch(() => 0);
213
-
214
- const res = await fetch(TEMPO_RPC, {
196
+ // Tempo Moderato faucet (test pathUSD) — instant, no polling needed.
197
+ // Paid tiers and images prefer pathUSD on Tempo mainnet, which no faucet gives.
198
+ const res = await fetch(TEMPO_MODERATO.rpcUrl, {
215
199
  method: "POST",
216
200
  headers: { "Content-Type": "application/json" },
217
201
  body: JSON.stringify({ jsonrpc: "2.0", method: "tempo_fundAddress", params: [w.address], id: 1 }),
@@ -225,23 +209,16 @@ async function cmdFund() {
225
209
  });
226
210
  }
227
211
 
228
- // Re-read balance once (instant confirmation)
229
- const now = await readUsdcBalance(client, PATH_USD, w.address).catch(() => before);
212
+ const onchain = await readOnChainBalances(w.address, ["tempo_moderato_pathusd_micros"]);
230
213
  saveActiveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
231
- console.log(JSON.stringify({
232
- address: w.address,
233
- rail: "mpp",
234
- onchain: {
235
- "tempo-moderato_pathusd_micros": now,
236
- },
237
- }, null, 2));
214
+ console.log(JSON.stringify({ address: w.address, rail: "mpp", onchain }, null, 2));
238
215
  return;
239
216
  }
240
217
 
241
- // Default: Base Sepolia faucet (existing behavior)
242
- const { createPublicClient, http, baseSepolia } = await loadDeps();
243
- const client = createPublicClient({ chain: baseSepolia, transport: http() });
244
- const before = await readUsdcBalance(client, USDC_SEPOLIA, w.address).catch(() => 0);
218
+ // Default: Base Sepolia faucet
219
+ const readSepolia = async () =>
220
+ (await readOnChainBalances(w.address, ["base_sepolia_usd_micros"])).base_sepolia_usd_micros;
221
+ const before = (await readSepolia()) ?? 0;
245
222
 
246
223
  let data;
247
224
  try {
@@ -253,15 +230,13 @@ async function cmdFund() {
253
230
  const MAX_WAIT = 30;
254
231
  for (let i = 0; i < MAX_WAIT; i++) {
255
232
  await new Promise(r => setTimeout(r, 1000));
256
- const now = await readUsdcBalance(client, USDC_SEPOLIA, w.address).catch(() => before);
233
+ const now = (await readSepolia()) ?? before;
257
234
  if (now > before) {
258
235
  saveActiveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
259
236
  console.log(JSON.stringify({
260
237
  address: w.address,
261
238
  rail: w.rail || "x402",
262
- onchain: {
263
- "base-sepolia_usd_micros": now,
264
- },
239
+ onchain: { base_sepolia_usd_micros: now },
265
240
  }, null, 2));
266
241
  return;
267
242
  }
@@ -271,11 +246,6 @@ async function cmdFund() {
271
246
  console.log(JSON.stringify({ ...data, balance_confirmed: false, hint: "Faucet request sent but on-chain balance not yet confirmed" }));
272
247
  }
273
248
 
274
- async function readUsdcBalance(client, usdc, address) {
275
- const raw = await client.readContract({ address: usdc, abi: USDC_ABI, functionName: "balanceOf", args: [address] });
276
- return Number(raw);
277
- }
278
-
279
249
  async function cmdBalance() {
280
250
  const w = readActiveWallet();
281
251
  if (!w) {
@@ -286,26 +256,15 @@ async function cmdBalance() {
286
256
  });
287
257
  }
288
258
 
289
- const { createPublicClient, http, base, baseSepolia, tempoModerato } = await loadDeps();
290
- const mainnetClient = createPublicClient({ chain: base, transport: http() });
291
- const sepoliaClient = createPublicClient({ chain: baseSepolia, transport: http() });
292
- const tempoClient = createPublicClient({ chain: tempoModerato, transport: http() });
293
-
294
- const [mainnetUsdc, sepoliaUsdc, tempoPathUsd, billingRes] = await Promise.all([
295
- readUsdcBalance(mainnetClient, USDC_MAINNET, w.address).catch(() => null),
296
- readUsdcBalance(sepoliaClient, USDC_SEPOLIA, w.address).catch(() => null),
297
- readUsdcBalance(tempoClient, PATH_USD, w.address).catch(() => null),
259
+ const [onchain, billingRes] = await Promise.all([
260
+ readOnChainBalances(w.address, ALL_ON_CHAIN_BALANCE_KEYS),
298
261
  getSdk().billing.checkBalance(w.address).catch(() => null),
299
262
  ]);
300
263
 
301
264
  console.log(JSON.stringify({
302
265
  address: w.address,
303
266
  rail: w.rail || "x402",
304
- onchain: {
305
- "base-mainnet_usd_micros": mainnetUsdc,
306
- "base-sepolia_usd_micros": sepoliaUsdc,
307
- "tempo-moderato_pathusd_micros": tempoPathUsd,
308
- },
267
+ onchain,
309
268
  allowance_usd_micros: billingRes ? billingRes.allowance_usd_micros : null,
310
269
  }, null, 2));
311
270
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "4.118.0",
3
+ "version": "4.120.0",
4
4
  "description": "CLI for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,6 +46,39 @@ export interface ContractCallResult {
46
46
  export interface ContractReadResult {
47
47
  result: unknown;
48
48
  }
49
+ /** app-wallet: a gasless token transfer from a signer, settled through the platform's facilitator. */
50
+ export type SignerTransferStatus = "pending" | "settled" | "failed" | "unknown";
51
+ export type SignerTransferPhase = "precheck" | "sign" | "verify" | "settle";
52
+ export interface SignerTransfer {
53
+ transfer_id: string;
54
+ signer_id: string;
55
+ status: SignerTransferStatus;
56
+ tx_hash: string | null;
57
+ chain: EvmChain;
58
+ network: string;
59
+ asset: string;
60
+ asset_address: string;
61
+ to: string;
62
+ amount: string;
63
+ nonce: string | null;
64
+ failed_phase: SignerTransferPhase | null;
65
+ facilitator_reason: string | null;
66
+ created_at: string;
67
+ updated_at: string;
68
+ }
69
+ export interface ListSignerTransfersResult {
70
+ transfers: SignerTransfer[];
71
+ }
72
+ export interface SignerTransferOptions {
73
+ signerId: string;
74
+ chain: EvmChain;
75
+ /** The token to send; `usdc` (the default) today. */
76
+ asset?: string;
77
+ to: string;
78
+ /** The amount in the asset's base units (USDC: 6 decimals), as a decimal string. */
79
+ amount: string;
80
+ idempotencyKey?: string;
81
+ }
49
82
  export interface DrainResult {
50
83
  call_id: string;
51
84
  status: ContractCallStatus;
@@ -146,6 +179,20 @@ export declare class Contracts {
146
179
  /** Read-only smart-contract call (view/pure). No auth, no gas, no billing. */
147
180
  read(opts: ContractReadOptions): Promise<ContractReadResult>;
148
181
  /** Look up a previously submitted call by id. */
182
+ /**
183
+ * Transfers a token from a signer with no gas on the signer: the gateway
184
+ * signs the token's EIP-3009 authorization with the signer's key and the
185
+ * platform's facilitator settles it and pays the gas. One signing fee, no
186
+ * gas. Idempotent on `idempotencyKey` like `call`: a transfer that failed
187
+ * before settlement re-executes under its key; one that died in settlement
188
+ * never does. `status: "unknown"` means the settle outcome never came back;
189
+ * poll `transferStatus` until the reconciler finishes it.
190
+ */
191
+ transfer(projectId: string, opts: SignerTransferOptions): Promise<SignerTransfer>;
192
+ /** One transfer by id. */
193
+ transferStatus(projectId: string, signerId: string, transferId: string): Promise<SignerTransfer>;
194
+ /** The signer's transfers, newest first. */
195
+ listTransfers(projectId: string, signerId: string): Promise<ListSignerTransfersResult>;
149
196
  callStatus(projectId: string, callId: string): Promise<ContractCallResult>;
150
197
  /**
151
198
  * Drain the signer's native-token balance to a destination address.
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/namespaces/contracts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/namespaces/contracts.ts"],"names":[],"mappings":"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"}
@@ -198,6 +198,42 @@ export class Contracts {
198
198
  });
199
199
  }
200
200
  /** Look up a previously submitted call by id. */
201
+ /**
202
+ * Transfers a token from a signer with no gas on the signer: the gateway
203
+ * signs the token's EIP-3009 authorization with the signer's key and the
204
+ * platform's facilitator settles it and pays the gas. One signing fee, no
205
+ * gas. Idempotent on `idempotencyKey` like `call`: a transfer that failed
206
+ * before settlement re-executes under its key; one that died in settlement
207
+ * never does. `status: "unknown"` means the settle outcome never came back;
208
+ * poll `transferStatus` until the reconciler finishes it.
209
+ */
210
+ async transfer(projectId, opts) {
211
+ assertStringInSet(opts.chain, EVM_CHAINS, "chain", "transferring from signer");
212
+ assertEvmAddress(opts.to, "to", "transferring from signer");
213
+ if (typeof opts.amount !== "string" || !/^[1-9][0-9]*$/.test(opts.amount)) {
214
+ throw new LocalError("contracts.transfer requires amount as a positive integer string in the asset's base units", "transferring from signer");
215
+ }
216
+ const project = await requireProjectCredentials(this.client, projectId, "transferring from signer");
217
+ const headers = { Authorization: `Bearer ${project.service_key}` };
218
+ if (opts.idempotencyKey)
219
+ headers["Idempotency-Key"] = opts.idempotencyKey;
220
+ return this.client.request(`/contracts/v1/signers/${encodeURIComponent(opts.signerId)}/transfers`, {
221
+ method: "POST",
222
+ headers,
223
+ body: { chain: opts.chain, asset: opts.asset ?? "usdc", to: opts.to, amount: opts.amount },
224
+ context: "transferring from signer",
225
+ });
226
+ }
227
+ /** One transfer by id. */
228
+ async transferStatus(projectId, signerId, transferId) {
229
+ const project = await requireProjectCredentials(this.client, projectId, "fetching transfer status");
230
+ return this.client.request(`/contracts/v1/signers/${encodeURIComponent(signerId)}/transfers/${encodeURIComponent(transferId)}`, { headers: { Authorization: `Bearer ${project.service_key}` }, context: "fetching transfer status" });
231
+ }
232
+ /** The signer's transfers, newest first. */
233
+ async listTransfers(projectId, signerId) {
234
+ const project = await requireProjectCredentials(this.client, projectId, "listing signer transfers");
235
+ return this.client.request(`/contracts/v1/signers/${encodeURIComponent(signerId)}/transfers`, { headers: { Authorization: `Bearer ${project.service_key}` }, context: "listing signer transfers" });
236
+ }
201
237
  async callStatus(projectId, callId) {
202
238
  const project = await requireProjectCredentials(this.client, projectId, "fetching call status");
203
239
  return this.client.request(`/contracts/v1/calls/${encodeURIComponent(callId)}`, {