warpmetal 0.7.2 → 0.7.4

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.
package/README.md CHANGED
@@ -34,7 +34,7 @@ published wallet CLI with the exact version WarpMetal reports:
34
34
  npm install --global warpmetal
35
35
  warpmetal --help
36
36
 
37
- npm install --global @x402api/agent-wallet-cli@0.2.4
37
+ npm install --global @x402api/agent-wallet-cli@0.2.5
38
38
  x402api help --json
39
39
  ```
40
40
 
@@ -58,7 +58,7 @@ integration.
58
58
 
59
59
  The repository also contains a skills-only WarpMetal plugin for the public
60
60
  Plugins Directory shared by Codex and ChatGPT. The plugin remains a separate
61
- artifact from the npm CLI and requires `warpmetal` CLI version 0.7.1 or newer.
61
+ artifact from the npm CLI and requires `warpmetal` CLI version 0.7.4 or newer.
62
62
 
63
63
  To test the repository marketplace after the plugin lands on `main`:
64
64
 
@@ -116,8 +116,8 @@ that WarpMetal uses for merchant-side reconciliation, the pinned
116
116
  `paymentWorkflow`. It also writes an owner-only request envelope that contains
117
117
  the exact checkout URL and body but no WarpMetal credential or challenge
118
118
  handle. The handle is not a buyer payment identifier and is never a wallet
119
- signing input. The published
120
- launch wallet accepts sponsored Base USDC and sponsored Solana USDC/USDT only;
119
+ signing input. The published launch wallet accepts sponsored Base USDC and
120
+ sponsored Solana USDC/USDT only;
121
121
  the returned terms identify compatible alternatives and confirm that the buyer
122
122
  does not need ETH or SOL. x402api pays the actual network fee from its platform
123
123
  treasury; the merchant tenant's active allowance controls sponsorship
@@ -131,7 +131,31 @@ context:
131
131
  limits. When the live terms fit those limits, authorize and submit without
132
132
  waiting for conversational approval.
133
133
 
134
- Then invoke the returned authorization argv with a dedicated wallet name:
134
+ Follow the returned commands in order. `wallet setup` creates an owner-only,
135
+ managed unlock file inside the x402api home directory and is safe to repeat;
136
+ it never prints the generated passphrase. `X402API_WALLET_PASSWORD_FILE` is an
137
+ optional x402api override for an externally managed password file. It is not a
138
+ WarpMetal variable, and WarpMetal does not create, read, or receive that file.
139
+
140
+ ```sh
141
+ x402api wallet setup --json
142
+ x402api wallet list --json
143
+ x402api wallet create --name <wallet-name> \
144
+ --network <exact-challenge-network> \
145
+ --maximum-payment-atomic <exact-live-amount> --json
146
+ x402api wallet address --wallet <wallet-name> --json
147
+ x402api wallet balance --wallet <wallet-name> \
148
+ --asset <exact-challenge-asset> --json
149
+ x402api wallet funding --wallet <wallet-name> \
150
+ --asset <exact-challenge-asset> \
151
+ --target-balance-atomic <exact-live-amount> --json
152
+ ```
153
+
154
+ Create a wallet only when no compatible dedicated wallet exists. The funding
155
+ command reports the public payer address, QR payload, current balance, target,
156
+ and exact deficit in atomic and normal six-decimal units. After the selected
157
+ wallet is sufficiently funded, invoke the returned authorization and WarpMetal
158
+ submission argv:
135
159
 
136
160
  ```sh
137
161
  x402api payment authorize \
@@ -147,23 +171,6 @@ warpmetal checkout submit \
147
171
  --json
148
172
  ```
149
173
 
150
- Check the payer wallet address and balance with:
151
-
152
- ```sh
153
- x402api wallet show --wallet <wallet-name> --json
154
- x402api wallet address --wallet <wallet-name> --json
155
- x402api wallet balance --wallet <wallet-name> --json
156
- ```
157
-
158
- For a new dedicated wallet, configure the enforceable local per-payment
159
- ceiling when it is created:
160
-
161
- ```sh
162
- x402api wallet create --name <wallet-name> \
163
- --network <exact-challenge-network> \
164
- --maximum-payment-atomic <per-payment-policy-cap> --json
165
- ```
166
-
167
174
  Require `maximumPaymentAtomic` to cover the live charge without exceeding the
168
175
  task or operator limit. Among valid sponsored terms, honor an explicit network
169
176
  or asset preference, otherwise prefer an already funded compatible wallet,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "warpmetal",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Agent-safe CLI and skill for purchasing, renewing, and managing WarpMetal VPS servers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,20 +11,20 @@ with ad hoc HTTP commands.
11
11
 
12
12
  ## Start safely
13
13
 
14
- 1. Run `warpmetal --version` and require version `0.7.1` or newer for this
14
+ 1. Run `warpmetal --version` and require version `0.7.4` or newer for this
15
15
  plugin. If it is missing or older, explain the compatibility requirement,
16
16
  ask before installing or upgrading software, and use only the official npm
17
17
  package from `https://www.npmjs.com/package/warpmetal`.
18
- 3. Use `--json` for every agent-driven command.
19
- 4. Read [references/safety.md](references/safety.md) before preparing an order,
18
+ 2. Use `--json` for every agent-driven command.
19
+ 3. Read [references/safety.md](references/safety.md) before preparing an order,
20
20
  authorizing payment, using an SSH identity, or changing a server.
21
- 5. Read [references/cli-reference.md](references/cli-reference.md) when choosing
21
+ 4. Read [references/cli-reference.md](references/cli-reference.md) when choosing
22
22
  a command or interpreting an exit code.
23
- 6. Read [references/payments.md](references/payments.md) before creating or
23
+ 5. Read [references/payments.md](references/payments.md) before creating or
24
24
  funding a wallet, authorizing payment, or resolving an ambiguous attempt.
25
- 7. Read [references/runtime.md](references/runtime.md) before requesting,
25
+ 6. Read [references/runtime.md](references/runtime.md) before requesting,
26
26
  installing, accessing, expiring, or deleting Agent Runtime sandboxes.
27
- 8. Read [references/renewals.md](references/renewals.md) before configuring or
27
+ 7. Read [references/renewals.md](references/renewals.md) before configuring or
28
28
  executing an autonomous renewal or requesting a wallet refill.
29
29
 
30
30
  Never read, print, summarize, upload, or commit the WarpMetal state file,
@@ -99,8 +99,12 @@ when the exact live terms fit those limits.
99
99
 
100
100
  Follow [references/payments.md](references/payments.md) to install the exact
101
101
  `paymentWorkflow.signerPackage.spec`, verify the V1 machine contract, install
102
- its matching `x402api-pay` skill, and select or fund a dedicated
103
- network-specific wallet. Require `agentWalletSupported: true`,
102
+ its matching `x402api-pay` skill, and invoke the returned wallet sequence:
103
+ `wallet setup`, `wallet list`, network-specific `wallet create` only if needed,
104
+ then the exact address, asset-balance, and funding commands. `wallet setup`
105
+ owns the managed unlock file; `X402API_WALLET_PASSWORD_FILE` is only an
106
+ optional x402api override and is never a WarpMetal input. Require
107
+ `agentWalletSupported: true`,
104
108
  `sponsoredNetworkFee: true`, and `buyerNativeFeeRequired: false` on the chosen
105
109
  live term. Never fall back to a historical buyer-funded or TRON profile.
106
110
  Honor an explicit task or operator network and asset preference. Otherwise use
@@ -56,13 +56,20 @@ On HTTP 402, `checkout challenge` validates and displays exact payment terms,
56
56
  writes the opaque x402api `challengeHandle` to private WarpMetal state for
57
57
  merchant reconciliation, and writes a credential-free x402api V1 request envelope with owner-only
58
58
  permissions, and returns the exact pinned wallet package, V1 contract probe,
59
- matching wallet-skill install, authorization, and WarpMetal submission argv
60
- arrays. The default envelope and suggested artifact paths live under the
59
+ matching wallet-skill install, setup, list, network-specific create,
60
+ address/balance/funding, authorization, and WarpMetal submission argv arrays.
61
+ The returned top-level `paymentWorkflow.sequence` fixes their order. Funding
62
+ options remain paired by exact live network and asset. The default envelope and
63
+ suggested artifact paths live under the
61
64
  private WarpMetal state directory. An explicit output path must not already
62
65
  contain different content. `challengeHandle` is intentionally absent from the
63
66
  wallet envelope: it is neither a buyer payment identifier nor signing input.
67
+ If a previously issued unsigned sponsorship reservation expired, WarpMetal may
68
+ retire that attempt and return a new `paymentAttemptId`. The CLI replaces the
69
+ saved challenge and stale wallet-attempt metadata; use only the newly returned
70
+ workflow.
64
71
 
65
- The current integration targets `@x402api/agent-wallet-cli@0.2.4`. A compatible
72
+ The current integration targets `@x402api/agent-wallet-cli@0.2.5`. A compatible
66
73
  live term is marked `agentWalletSupported: true` and must use the sponsored
67
74
  Base USDC or Solana USDC/USDT launch profile with buyer native fees disabled.
68
75
  For the current declaration, x402api pays actual gas from its platform treasury
@@ -25,7 +25,7 @@ contract.
25
25
  WarpMetal runs on Node.js 20 or 22. The x402api Agent Wallet currently requires
26
26
  Node.js 22. Use the exact published package reported by
27
27
  `paymentWorkflow.signerPackage.spec`; the current contract is
28
- `@x402api/agent-wallet-cli@0.2.4`. Do not add it as a WarpMetal dependency,
28
+ `@x402api/agent-wallet-cli@0.2.5`. Do not add it as a WarpMetal dependency,
29
29
  install executable wallet code from an unpinned repository URL, or substitute
30
30
  a similarly named package.
31
31
 
@@ -40,9 +40,15 @@ skill automatically.
40
40
  1. Install the exact `paymentWorkflow.signerPackage.install.argv` under Node
41
41
  22. Run `command -v x402api` and the returned
42
42
  `paymentWorkflow.signerContract.probe.argv`. Require contract version 1.
43
- 2. Ensure an operator configured `X402API_WALLET_PASSWORD_FILE` as an owner-only
44
- file, or is supervising `--password-stdin`. Never read either value.
45
- 3. Use `x402api wallet list --json` and reuse a dedicated persistent wallet for
43
+ 2. Run the exact `paymentWorkflow.walletWorkflow.setup.argv`. The idempotent
44
+ `x402api wallet setup --json` command creates an owner-only managed unlock
45
+ file inside the x402api home directory and never prints its generated
46
+ passphrase. `X402API_WALLET_PASSWORD_FILE` is an optional x402api override
47
+ for an externally managed password file; it is not a WarpMetal setting.
48
+ Never create it on WarpMetal's behalf, read it, or pass it to WarpMetal. A
49
+ supervised operator may instead use `--password-stdin` where supported.
50
+ 3. Run the exact `paymentWorkflow.walletWorkflow.list.argv` and reuse a
51
+ dedicated persistent wallet for
46
52
  the exact challenge network.
47
53
  4. In an interactive conversation, create a wallet only with explicit
48
54
  approval. In an unattended run, create one only when the automation policy
@@ -63,13 +69,20 @@ skill automatically.
63
69
  higher than the task or operator limit. If an existing wallet has no ceiling
64
70
  or an unsuitable one, do not treat its full balance as bounded authority;
65
71
  use a suitably capped dedicated wallet or stop.
66
- 5. Use both public, safe commands before requesting funding:
72
+ 5. Use the returned public address, exact asset-balance, and funding commands:
67
73
 
68
74
  ```sh
69
75
  x402api wallet address --wallet <name> --json
70
- x402api wallet balance --wallet <name> --json
76
+ x402api wallet balance --wallet <name> \
77
+ --asset <exact-challenge-asset> --json
78
+ x402api wallet funding --wallet <name> \
79
+ --asset <exact-challenge-asset> \
80
+ --target-balance-atomic <required-or-policy-target> --json
71
81
  ```
72
82
 
83
+ Use only the selected term's returned argv; do not combine one network with
84
+ another term's asset. The funding result provides the current balance,
85
+ target, exact deficit, payer address, and QR payload without moving funds.
73
86
  6. If funding is short in an interactive conversation, show the exact deficit
74
87
  in normal token units and atomic units, the exact network, token symbol and
75
88
  contract/mint, and the payer wallet's public receiving address. Render only
@@ -136,7 +149,7 @@ reservation or any buyer-funded, unsupported, or unbound alternative.
136
149
 
137
150
  x402api pays actual gas from its platform treasury. The merchant tenant's
138
151
  active sponsorship allowance controls admission, but actual gas is not a
139
- tenant debit. During the coordinated rollout, accept only the exact 0.2.4
152
+ tenant debit. During the coordinated rollout, accept only the exact 0.2.5
140
153
  platform-treasury declaration or the matched legacy tenant-credit declaration;
141
154
  never accept a mixed billing and final-charge policy.
142
155
 
@@ -171,6 +184,12 @@ current `termEndsAt` generation are different.
171
184
 
172
185
  ## Retries and recovery
173
186
 
187
+ - Expired unsigned gas sponsorship: run the WarpMetal challenge flow again.
188
+ WarpMetal retires the expired merchant attempt and requests fresh instructions
189
+ under a new payment-attempt ID and idempotency key. The CLI replaces stale
190
+ wallet-attempt metadata when it saves the new challenge. Inspect the new
191
+ terms and authorize only the new workflow; never reuse an expired envelope or
192
+ artifact.
174
193
  - `payment_pending` or `payment_finalizing`: keep the same checkout bytes and
175
194
  artifact. `--wait` performs bounded retries with that exact authorization.
176
195
  - Timeout or process restart after authorization: use the saved x402api attempt
@@ -188,6 +207,6 @@ current `termEndsAt` generation are different.
188
207
  a fresh challenge; never retry the artifact or fall back to buyer-funded gas.
189
208
  - `manual_review`: payment or fulfillment may be final. Stop all payment and
190
209
  mutation retries.
191
- - Expired or corrupt artifact, changed request digest, unexpected recipient,
210
+ - Expired or corrupt signed artifact, changed request digest, unexpected recipient,
192
211
  unsupported network, asset, profile, sponsorship error, or request-binding
193
212
  mismatch: stop instead of falling back or asking the buyer to fund ETH/SOL.
@@ -46,6 +46,10 @@
46
46
  - Install only the exact `@x402api/agent-wallet-cli` version and matching
47
47
  bundled `x402api-pay` skill reported by WarpMetal. Never substitute an
48
48
  unpinned repository checkout or similarly named wallet package.
49
+ - Let `x402api wallet setup` create and permission its managed unlock file.
50
+ `X402API_WALLET_PASSWORD_FILE` belongs to x402api only as an optional
51
+ externally managed override; WarpMetal must never create, read, or receive
52
+ it. Stop if x402api rejects unsafe ownership or permissions.
49
53
  - Never read or display an x402api password file, encrypted keystore, request
50
54
  envelope, payment artifact, or complete payment signature. Pass only their
51
55
  paths to the documented commands.
@@ -61,7 +65,7 @@ explicit approval immediately before:
61
65
  - installing the CLI or skill;
62
66
  - installing or repairing the Agent Runtime supervisor;
63
67
  - generating a new SSH key pair;
64
- - creating or funding a wallet;
68
+ - creating a wallet or asking the human to transfer funds to it;
65
69
  - signing or submitting an x402 payment authorization;
66
70
  - booting, rebooting, or shutting down a server; and
67
71
  - creating a temporary sandbox after explaining its irreversible expiry;
@@ -105,6 +109,11 @@ Confirm the plan, hostname, OS, and public key before preparing it.
105
109
  ## Retry and terminal-state rules
106
110
 
107
111
  - Reuse the same idempotency key only for the exact same logical request.
112
+ - If an unsigned gas-sponsorship reservation expires, run `warpmetal checkout
113
+ challenge` again. WarpMetal retires the old merchant attempt, requests fresh
114
+ instructions with a new payment-attempt ID and idempotency key, and the CLI
115
+ replaces its saved challenge metadata. Re-evaluate the new terms before
116
+ authorization; never sign or submit the expired challenge.
108
117
  - For `payment_pending`, retry the exact checkout body and the exact same
109
118
  payment artifact. Do not create a replacement payment.
110
119
  - A signed HTTP 402 rejects that signature. Use the new live challenge for one
package/src/cli.js CHANGED
@@ -147,6 +147,22 @@ function shellCommand(argv) {
147
147
  .join(" ");
148
148
  }
149
149
 
150
+ function walletWorkflowInstructions(workflow) {
151
+ const create = workflow.walletWorkflow.createOptions
152
+ .map(
153
+ (option) =>
154
+ `${option.network} ${option.asset}: ${shellCommand(option.argv)}`,
155
+ )
156
+ .join("\n");
157
+ const balanceAndFunding = workflow.fundingWorkflow.options
158
+ .map(
159
+ (option) =>
160
+ `${option.network} ${option.asset}:\n Balance: ${shellCommand(option.balance.argv)}\n Funding: ${shellCommand(option.funding.argv)}`,
161
+ )
162
+ .join("\n");
163
+ return `Wallet setup: ${shellCommand(workflow.walletWorkflow.setup.argv)}\nWallet list: ${shellCommand(workflow.walletWorkflow.list.argv)}\nCreate a compatible wallet only if needed:\n${create}\nCheck the selected wallet address: ${shellCommand(workflow.fundingWorkflow.address.argv)}\nCheck or fund the exact selected asset:\n${balanceAndFunding}`;
164
+ }
165
+
150
166
  function emit(stream, value, json, human) {
151
167
  if (json) writeLine(stream, JSON.stringify(value, null, 2));
152
168
  else writeLine(stream, human);
@@ -413,6 +429,7 @@ async function attachPaymentWorkflow(
413
429
  );
414
430
  const workflow = paymentWorkflow({
415
431
  taskId,
432
+ terms: request.terms,
416
433
  requestEnvelopePath,
417
434
  paymentArtifactPath: defaults.paymentArtifactPath,
418
435
  });
@@ -653,7 +670,7 @@ async function handleCheckoutChallenge(client, store, options, context) {
653
670
  stringOption(options, "request-envelope-out"),
654
671
  );
655
672
  const paymentInstructions = safe.paymentWorkflow
656
- ? `\nWallet package: ${safe.paymentWorkflow.signerPackage.spec} (Node ${safe.paymentWorkflow.signerNodeRequirement})\nInstall: ${shellCommand(safe.paymentWorkflow.signerPackage.install.argv)}\nVerify: ${shellCommand(safe.paymentWorkflow.signerContract.probe.argv)}\nRequest envelope: ${safe.paymentWorkflow.requestEnvelopePath}\nAuthorize: ${shellCommand(safe.paymentWorkflow.authorize.argv)}\nIf funding is short, run ${shellCommand(safe.paymentWorkflow.fundingWorkflow.address.argv)} and ${shellCommand(safe.paymentWorkflow.fundingWorkflow.balance.argv)}, then show the payer address as both a QR code and copyable text.\nSubmit with WarpMetal: ${shellCommand(safe.paymentWorkflow.submit.argv)}`
673
+ ? `\nWallet package: ${safe.paymentWorkflow.signerPackage.spec} (Node ${safe.paymentWorkflow.signerNodeRequirement})\nInstall: ${shellCommand(safe.paymentWorkflow.signerPackage.install.argv)}\nVerify: ${shellCommand(safe.paymentWorkflow.signerContract.probe.argv)}\n${walletWorkflowInstructions(safe.paymentWorkflow)}\nRequest envelope: ${safe.paymentWorkflow.requestEnvelopePath}\nAuthorize only after selecting/funding one wallet: ${shellCommand(safe.paymentWorkflow.authorize.argv)}\nSubmit with WarpMetal: ${shellCommand(safe.paymentWorkflow.submit.argv)}`
657
674
  : "";
658
675
  emit(
659
676
  context.stdout,
@@ -1040,25 +1057,21 @@ async function attachRenewalPaymentWorkflow(
1040
1057
  requestedEnvelopePath || defaults.requestEnvelopePath,
1041
1058
  request.envelope,
1042
1059
  );
1060
+ const refillTarget =
1061
+ renewal.refillTargetAtomic &&
1062
+ BigInt(renewal.refillTargetAtomic) >= BigInt(term.amountAtomic)
1063
+ ? renewal.refillTargetAtomic
1064
+ : term.amountAtomic;
1043
1065
  const workflow = paymentWorkflow({
1044
1066
  taskId: server.id,
1045
1067
  serverId,
1046
1068
  kind: "renewal",
1047
1069
  wallet: renewal.wallet,
1070
+ terms: [term],
1071
+ fundingTargetAtomic: refillTarget,
1048
1072
  requestEnvelopePath,
1049
1073
  paymentArtifactPath: defaults.paymentArtifactPath,
1050
1074
  });
1051
- const refillTarget =
1052
- renewal.refillTargetAtomic &&
1053
- BigInt(renewal.refillTargetAtomic) >= BigInt(term.amountAtomic)
1054
- ? renewal.refillTargetAtomic
1055
- : term.amountAtomic;
1056
- Object.assign(workflow.fundingWorkflow, {
1057
- network: term.network,
1058
- asset: term.asset,
1059
- requiredPaymentAtomic: term.amountAtomic,
1060
- targetBalanceAtomic: refillTarget,
1061
- });
1062
1075
  const refillNotification = refillNotificationState(
1063
1076
  notifications,
1064
1077
  renewal.policy,
@@ -1191,7 +1204,7 @@ async function handleRenewalPrepare(client, store, options, context) {
1191
1204
  const serverId = stringOption(options, "server", { required: true });
1192
1205
  const safe = await prepareRenewal(client, store, serverId, options, context);
1193
1206
  const fundingInstructions = safe.paymentWorkflow
1194
- ? `\nIf funding is short, run ${shellCommand(safe.paymentWorkflow.fundingWorkflow.address.argv)} and ${shellCommand(safe.paymentWorkflow.fundingWorkflow.balance.argv)}. Show the returned payer address as both a QR code and copyable text, with the exact network, asset, and deficit.`
1207
+ ? `\n${walletWorkflowInstructions(safe.paymentWorkflow)}. Show the returned payer address as both a QR code and copyable text, with the exact network, asset, and deficit.`
1195
1208
  : "";
1196
1209
  const refillInstructions = safe.refillWorkflow
1197
1210
  ? `\nFor verified email refill, set ${Object.entries(safe.refillWorkflow.environment).map(([key, value]) => `${key}=${value}`).join(" ")} and run: ${shellCommand(safe.refillWorkflow.argv)}`
package/src/payment.js CHANGED
@@ -15,7 +15,7 @@ const MAX_ARTIFACT_BYTES = 1024 * 1024;
15
15
  const MAX_SIGNATURE_BYTES = 512 * 1024;
16
16
 
17
17
  export const AGENT_WALLET_PACKAGE = "@x402api/agent-wallet-cli";
18
- export const AGENT_WALLET_VERSION = "0.2.4";
18
+ export const AGENT_WALLET_VERSION = "0.2.5";
19
19
  const AGENT_WALLET_SPEC = `${AGENT_WALLET_PACKAGE}@${AGENT_WALLET_VERSION}`;
20
20
  const GAS_SPONSORSHIP_EXTENSION = "com.x402api.gas-sponsorship";
21
21
  const BASE_NETWORK = "eip155:8453";
@@ -707,10 +707,75 @@ export function paymentWorkflow({
707
707
  serverId,
708
708
  kind = "checkout",
709
709
  wallet = "<wallet-name>",
710
+ terms = [],
711
+ fundingTargetAtomic,
710
712
  requestEnvelopePath,
711
713
  paymentArtifactPath,
712
714
  }) {
715
+ const walletOptions = terms
716
+ .filter((term) => term?.agentWalletSupported)
717
+ .map((term) => {
718
+ const targetBalanceAtomic = fundingTargetAtomic || term.amountAtomic;
719
+ return {
720
+ network: term.network,
721
+ asset: term.asset,
722
+ requiredPaymentAtomic: term.amountAtomic,
723
+ targetBalanceAtomic,
724
+ create: {
725
+ argv: [
726
+ "x402api",
727
+ "wallet",
728
+ "create",
729
+ "--name",
730
+ "<wallet-name>",
731
+ "--network",
732
+ term.network,
733
+ "--maximum-payment-atomic",
734
+ term.amountAtomic,
735
+ "--json",
736
+ ],
737
+ },
738
+ address: {
739
+ argv: ["x402api", "wallet", "address", "--wallet", wallet, "--json"],
740
+ },
741
+ balance: {
742
+ argv: [
743
+ "x402api",
744
+ "wallet",
745
+ "balance",
746
+ "--wallet",
747
+ wallet,
748
+ "--asset",
749
+ term.asset,
750
+ "--json",
751
+ ],
752
+ },
753
+ funding: {
754
+ argv: [
755
+ "x402api",
756
+ "wallet",
757
+ "funding",
758
+ "--wallet",
759
+ wallet,
760
+ "--asset",
761
+ term.asset,
762
+ "--target-balance-atomic",
763
+ targetBalanceAtomic,
764
+ "--json",
765
+ ],
766
+ },
767
+ };
768
+ });
769
+ const selectedFunding = walletOptions.length === 1 ? walletOptions[0] : null;
713
770
  return {
771
+ sequence: [
772
+ "wallet_setup",
773
+ "wallet_list",
774
+ "wallet_create_if_needed",
775
+ "wallet_address_balance_funding",
776
+ "payment_authorize",
777
+ "warpmetal_submit",
778
+ ],
714
779
  signer: "x402api Agent Wallet",
715
780
  signerExecutable: "x402api",
716
781
  signerNodeRequirement: ">=22",
@@ -742,6 +807,28 @@ export function paymentWorkflow({
742
807
  ],
743
808
  },
744
809
  },
810
+ walletWorkflow: {
811
+ sequence: [
812
+ "setup",
813
+ "list",
814
+ "create_if_needed",
815
+ "address",
816
+ "balance",
817
+ "fund_if_needed",
818
+ ],
819
+ setup: { argv: ["x402api", "wallet", "setup", "--json"] },
820
+ list: { argv: ["x402api", "wallet", "list", "--json"] },
821
+ createOptions: walletOptions.map(
822
+ ({ network, asset, requiredPaymentAtomic, create }) => ({
823
+ network,
824
+ asset,
825
+ requiredPaymentAtomic,
826
+ ...create,
827
+ }),
828
+ ),
829
+ selection:
830
+ "Reuse a compatible capped wallet when available. Otherwise choose exactly one advertised option and replace only <wallet-name>.",
831
+ },
745
832
  requestEnvelopePath,
746
833
  paymentArtifactPath,
747
834
  authorize: {
@@ -766,8 +853,62 @@ export function paymentWorkflow({
766
853
  argv: ["x402api", "wallet", "address", "--wallet", wallet, "--json"],
767
854
  },
768
855
  balance: {
769
- argv: ["x402api", "wallet", "balance", "--wallet", wallet, "--json"],
856
+ argv:
857
+ selectedFunding?.balance.argv ||
858
+ [
859
+ "x402api",
860
+ "wallet",
861
+ "balance",
862
+ "--wallet",
863
+ wallet,
864
+ "--asset",
865
+ "<asset-from-selected-term>",
866
+ "--json",
867
+ ],
770
868
  },
869
+ funding: {
870
+ argv:
871
+ selectedFunding?.funding.argv ||
872
+ [
873
+ "x402api",
874
+ "wallet",
875
+ "funding",
876
+ "--wallet",
877
+ wallet,
878
+ "--asset",
879
+ "<asset-from-selected-term>",
880
+ "--target-balance-atomic",
881
+ "<target-balance-atomic>",
882
+ "--json",
883
+ ],
884
+ },
885
+ options: walletOptions.map(
886
+ ({
887
+ network,
888
+ asset,
889
+ requiredPaymentAtomic,
890
+ targetBalanceAtomic,
891
+ address,
892
+ balance,
893
+ funding,
894
+ }) => ({
895
+ network,
896
+ asset,
897
+ requiredPaymentAtomic,
898
+ targetBalanceAtomic,
899
+ address,
900
+ balance,
901
+ funding,
902
+ }),
903
+ ),
904
+ ...(selectedFunding
905
+ ? {
906
+ network: selectedFunding.network,
907
+ asset: selectedFunding.asset,
908
+ requiredPaymentAtomic: selectedFunding.requiredPaymentAtomic,
909
+ targetBalanceAtomic: selectedFunding.targetBalanceAtomic,
910
+ }
911
+ : {}),
771
912
  presentation: {
772
913
  qrPayloadField: "address",
773
914
  textPayloadField: "address",
@@ -777,7 +918,7 @@ export function paymentWorkflow({
777
918
  showNetworkAndAsset: true,
778
919
  },
779
920
  safety:
780
- "Encode and fund only the payer wallet public address. Never fund the token contract, mint, or WarpMetal merchant recipient; sponsored payments do not require ETH or SOL.",
921
+ "Run the exact selected funding argv. Encode and fund only its payer wallet public address. Never fund the token contract, mint, or WarpMetal merchant recipient; sponsored payments do not require ETH or SOL.",
781
922
  },
782
923
  submit: {
783
924
  argv:
package/src/state.js CHANGED
@@ -6,6 +6,26 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
6
6
  import { CliError } from "./errors.js";
7
7
 
8
8
  const STATE_VERSION = 3;
9
+ const WALLET_PAYMENT_STATE_FIELDS = [
10
+ "walletPaymentAttemptId",
11
+ "walletBuyerPaymentIdentifier",
12
+ "walletName",
13
+ "walletPayerAddress",
14
+ "paymentArtifactExpiresAt",
15
+ "paymentArtifactSavedAt",
16
+ ];
17
+
18
+ function replaceChallenge(record, challenge) {
19
+ const changed =
20
+ (record.paymentAttemptId &&
21
+ record.paymentAttemptId !== challenge.paymentAttemptId) ||
22
+ (record.paymentChallengeDigest &&
23
+ record.paymentChallengeDigest !== challenge.paymentChallengeDigest);
24
+ if (changed) {
25
+ for (const name of WALLET_PAYMENT_STATE_FIELDS) delete record[name];
26
+ }
27
+ Object.assign(record, challenge);
28
+ }
9
29
 
10
30
  function emptyState() {
11
31
  return {
@@ -235,9 +255,8 @@ export class StateStore {
235
255
  async saveRenewalChallenge(serverId, challenge) {
236
256
  await this.update((state) => {
237
257
  const renewal = state.renewals[serverId] || { serverId };
238
- Object.assign(renewal, challenge, {
239
- challengeSavedAt: new Date().toISOString(),
240
- });
258
+ replaceChallenge(renewal, challenge);
259
+ renewal.challengeSavedAt = new Date().toISOString();
241
260
  state.renewals[serverId] = renewal;
242
261
  });
243
262
  }
@@ -292,13 +311,15 @@ export class StateStore {
292
311
  throw new CliError(`No local order state exists for ${taskId}.`, {
293
312
  exitCode: 2,
294
313
  });
295
- order.paymentRequired = paymentRequired;
296
- order.paymentAttemptId = paymentAttemptId;
297
- order.challengeHandle = challengeHandle;
298
- order.paymentRequestDigest = paymentRequestDigest;
299
- order.paymentChallengeDigest = paymentChallengeDigest;
300
- order.paymentRequestEnvelopePath = paymentWorkflow?.requestEnvelopePath;
301
- order.paymentArtifactPath = paymentWorkflow?.paymentArtifactPath;
314
+ replaceChallenge(order, {
315
+ paymentRequired,
316
+ paymentAttemptId,
317
+ challengeHandle,
318
+ paymentRequestDigest,
319
+ paymentChallengeDigest,
320
+ paymentRequestEnvelopePath: paymentWorkflow?.requestEnvelopePath,
321
+ paymentArtifactPath: paymentWorkflow?.paymentArtifactPath,
322
+ });
302
323
  order.paymentChallengeSavedAt = new Date().toISOString();
303
324
  });
304
325
  }