warpmetal 0.6.0 → 0.7.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.
- package/README.md +54 -15
- package/package.json +2 -1
- package/skills/warpmetal/SKILL.md +13 -7
- package/skills/warpmetal/references/cli-reference.md +20 -10
- package/skills/warpmetal/references/payments.md +25 -7
- package/skills/warpmetal/references/renewals.md +17 -10
- package/skills/warpmetal/references/safety.md +7 -0
- package/src/api.js +8 -0
- package/src/cli.js +179 -33
- package/src/payment.js +33 -4
- package/src/state.js +4 -0
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ payment artifact, and submits it without absorbing wallet custody.
|
|
|
17
17
|
executable
|
|
18
18
|
- Skill: `skills/warpmetal` in the GitHub repository and bundled inside the
|
|
19
19
|
npm package
|
|
20
|
+
- Codex plugin: the skills-only package under `plugins/warpmetal`, exposed for
|
|
21
|
+
repository testing by `.agents/plugins/marketplace.json`
|
|
20
22
|
|
|
21
23
|
Keeping the skill beside the CLI gives Codex, Claude, and other Agent
|
|
22
24
|
Skills-compatible tools one canonical set of safety instructions while the CLI
|
|
@@ -32,7 +34,7 @@ published wallet CLI with the exact version WarpMetal reports:
|
|
|
32
34
|
npm install --global warpmetal
|
|
33
35
|
warpmetal --help
|
|
34
36
|
|
|
35
|
-
npm install --global @x402api/agent-wallet-cli@0.2.
|
|
37
|
+
npm install --global @x402api/agent-wallet-cli@0.2.3
|
|
36
38
|
x402api help --json
|
|
37
39
|
```
|
|
38
40
|
|
|
@@ -52,6 +54,29 @@ layout. Agents that support that layout but use another installation path can
|
|
|
52
54
|
consume that directory directly; they do not need a different WarpMetal API
|
|
53
55
|
integration.
|
|
54
56
|
|
|
57
|
+
## Codex plugin
|
|
58
|
+
|
|
59
|
+
The repository also contains a skills-only WarpMetal plugin for the public
|
|
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.
|
|
62
|
+
|
|
63
|
+
To test the repository marketplace after the plugin lands on `main`:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
codex plugin marketplace add warpmetal/agent-kit --ref main
|
|
67
|
+
codex plugin add warpmetal@warpmetal
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Run the plugin-specific repository checks with:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npm run plugin:check
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Public submission materials, reviewer cases, and the owner checklist live in
|
|
77
|
+
`submission/openai`. Public publication happens through the OpenAI Platform;
|
|
78
|
+
the repo marketplace is only for development, testing, and direct distribution.
|
|
79
|
+
|
|
55
80
|
## First commands
|
|
56
81
|
|
|
57
82
|
```sh
|
|
@@ -85,13 +110,19 @@ After preparing an order, request its live payment challenge:
|
|
|
85
110
|
warpmetal checkout challenge --task <taskId> --json
|
|
86
111
|
```
|
|
87
112
|
|
|
88
|
-
On HTTP 402 the CLI returns exact `paymentTerms`, the
|
|
113
|
+
On HTTP 402 the CLI returns exact `paymentTerms`, the opaque `challengeHandle`
|
|
114
|
+
that WarpMetal uses for merchant-side reconciliation, the pinned
|
|
89
115
|
`@x402api/agent-wallet-cli` package contract, and argv arrays under
|
|
90
116
|
`paymentWorkflow`. It also writes an owner-only request envelope that contains
|
|
91
|
-
the exact checkout URL and body but no WarpMetal credential
|
|
117
|
+
the exact checkout URL and body but no WarpMetal credential or challenge
|
|
118
|
+
handle. The handle is not a buyer payment identifier and is never a wallet
|
|
119
|
+
signing input. The published
|
|
92
120
|
launch wallet accepts sponsored Base USDC and sponsored Solana USDC/USDT only;
|
|
93
121
|
the returned terms identify compatible alternatives and confirm that the buyer
|
|
94
|
-
does not need ETH or SOL.
|
|
122
|
+
does not need ETH or SOL. x402api pays the actual network fee from its platform
|
|
123
|
+
treasury; the merchant tenant's active allowance controls sponsorship
|
|
124
|
+
admission but is not charged actual gas. Payment authority depends on execution
|
|
125
|
+
context:
|
|
95
126
|
|
|
96
127
|
- When a human is actively chatting with the agent, show the exact live terms
|
|
97
128
|
and ask for confirmation immediately before authorizing and submitting.
|
|
@@ -141,11 +172,13 @@ after authorization.
|
|
|
141
172
|
|
|
142
173
|
If funding is short in an interactive conversation, tell the human the exact
|
|
143
174
|
top-up in normal and atomic units, the network, stablecoin and contract/mint,
|
|
144
|
-
and the payer wallet's public receiving address. The
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
175
|
+
and the payer wallet's public receiving address. The returned
|
|
176
|
+
`paymentWorkflow.fundingWorkflow` provides safe address and balance argv plus a
|
|
177
|
+
presentation contract: render that public address as both a QR code and
|
|
178
|
+
copyable text. The human sends the token to that wallet address, never to the
|
|
179
|
+
token contract/mint or WarpMetal's payment recipient, and never sends ETH or
|
|
180
|
+
SOL for a sponsored payment. In an unattended run, use a preconfigured refill
|
|
181
|
+
or escalation mechanism or stop with `funding_required`.
|
|
149
182
|
|
|
150
183
|
## Autonomous renewal and refill
|
|
151
184
|
|
|
@@ -168,7 +201,11 @@ warpmetal renewal configure \
|
|
|
168
201
|
```
|
|
169
202
|
|
|
170
203
|
The recipient must follow the one-time verification link before lifecycle or
|
|
171
|
-
refill mail is sent.
|
|
204
|
+
refill mail is sent. If no verified notification email exists, `renewal
|
|
205
|
+
configure` returns `email_required` before changing policy. Supply `--email`,
|
|
206
|
+
or deliberately continue with `--without-email-notifications`; the latter does
|
|
207
|
+
not enable signed refill-email workflows. A recurring unattended agent can then
|
|
208
|
+
run:
|
|
172
209
|
|
|
173
210
|
```sh
|
|
174
211
|
warpmetal renewal due --all --json
|
|
@@ -176,14 +213,16 @@ warpmetal renewal run --all-due --json
|
|
|
176
213
|
```
|
|
177
214
|
|
|
178
215
|
Inside policy, the CLI returns the exact Agent Wallet authorization and submit
|
|
179
|
-
argv. If balance is insufficient,
|
|
180
|
-
|
|
216
|
+
argv. If balance is insufficient, `refillWorkflow` is returned only when the
|
|
217
|
+
server has an active verified notification subscription. Run its argv with the
|
|
218
|
+
returned `X402API_NOTIFICATION_URL` environment value. `x402api wallet
|
|
181
219
|
notify-refill` signs an opaque subscription reference and wallet-produced
|
|
182
220
|
balance fields; it cannot choose an email address. WarpMetal verifies the
|
|
183
221
|
wallet signature and current on-chain balance before emailing the verified
|
|
184
|
-
human the network, stablecoin, public wallet address,
|
|
185
|
-
|
|
186
|
-
|
|
222
|
+
human the network, stablecoin, public wallet address, required minimum top-up,
|
|
223
|
+
and a locally generated QR encoding only that wallet address. The address is
|
|
224
|
+
also repeated as copyable text. The human may transfer more than that minimum;
|
|
225
|
+
the renewal policy—not the refill target—remains the spending authority.
|
|
187
226
|
|
|
188
227
|
The agent never sends a partial x402 payment. If no verified refill path
|
|
189
228
|
exists, it reports `funding_required`. If a previous payment is pending or
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "warpmetal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Agent-safe CLI and skill for purchasing, renewing, and managing WarpMetal VPS servers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"check": "node --check bin/warpmetal.js && node --check src/api.js && node --check src/args.js && node --check src/cli.js && node --check src/connection.js && node --check src/errors.js && node --check src/install-skill.js && node --check src/installer.js && node --check src/payment.js && node --check src/runtime.js && node --check src/ssh.js && node --check src/state.js && node --check src/version.js",
|
|
20
|
+
"plugin:check": "node --test test/plugin.test.js",
|
|
20
21
|
"test": "node --test",
|
|
21
22
|
"prepack": "npm run check && npm test"
|
|
22
23
|
},
|
|
@@ -11,9 +11,10 @@ with ad hoc HTTP commands.
|
|
|
11
11
|
|
|
12
12
|
## Start safely
|
|
13
13
|
|
|
14
|
-
1. Run `warpmetal --version
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
1. Run `warpmetal --version` and require version `0.7.1` or newer for this
|
|
15
|
+
plugin. If it is missing or older, explain the compatibility requirement,
|
|
16
|
+
ask before installing or upgrading software, and use only the official npm
|
|
17
|
+
package from `https://www.npmjs.com/package/warpmetal`.
|
|
17
18
|
3. Use `--json` for every agent-driven command.
|
|
18
19
|
4. Read [references/safety.md](references/safety.md) before preparing an order,
|
|
19
20
|
authorizing payment, using an SSH identity, or changing a server.
|
|
@@ -111,7 +112,9 @@ task or operator limit.
|
|
|
111
112
|
After interactive confirmation or autonomous policy validation, invoke the
|
|
112
113
|
returned authorize argv once. The separate
|
|
113
114
|
`x402api` executable owns the wallet, validates the envelope, and writes the
|
|
114
|
-
payment artifact.
|
|
115
|
+
payment artifact. The returned `challengeHandle` is opaque merchant
|
|
116
|
+
reconciliation metadata; never copy it into the request envelope or treat it as
|
|
117
|
+
the buyer payment identifier. Then invoke the returned submit argv, equivalent to:
|
|
115
118
|
|
|
116
119
|
```sh
|
|
117
120
|
warpmetal checkout submit \
|
|
@@ -152,9 +155,12 @@ price, asset, network, count, horizon, or total-budget mismatch.
|
|
|
152
155
|
Run `warpmetal renewal prepare --server <serverId> --json`, then invoke only
|
|
153
156
|
the returned `paymentWorkflow.authorize.argv` and
|
|
154
157
|
`paymentWorkflow.submit.argv`. If authorization reports insufficient balance,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
use `paymentWorkflow.fundingWorkflow` to obtain the public address and balance,
|
|
159
|
+
then show the address as both a QR code and copyable text. Only when
|
|
160
|
+
`refillNotification.available` is true, set the returned
|
|
161
|
+
`refillWorkflow.environment`, invoke its exact argv once, and stop until
|
|
162
|
+
funding arrives. The signed refill intent resolves to a verified human contact;
|
|
163
|
+
never add an email address to it. Never make a partial payment.
|
|
158
164
|
|
|
159
165
|
After funding, prepare again, authorize exactly once, submit with WarpMetal,
|
|
160
166
|
and confirm the returned `termEndsAt`. On `reconcile_pending` or
|
|
@@ -53,19 +53,25 @@ warpmetal order status \
|
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
On HTTP 402, `checkout challenge` validates and displays exact payment terms,
|
|
56
|
-
writes
|
|
56
|
+
writes the opaque x402api `challengeHandle` to private WarpMetal state for
|
|
57
|
+
merchant reconciliation, and writes a credential-free x402api V1 request envelope with owner-only
|
|
57
58
|
permissions, and returns the exact pinned wallet package, V1 contract probe,
|
|
58
59
|
matching wallet-skill install, authorization, and WarpMetal submission argv
|
|
59
60
|
arrays. The default envelope and suggested artifact paths live under the
|
|
60
61
|
private WarpMetal state directory. An explicit output path must not already
|
|
61
|
-
contain different content.
|
|
62
|
+
contain different content. `challengeHandle` is intentionally absent from the
|
|
63
|
+
wallet envelope: it is neither a buyer payment identifier nor signing input.
|
|
62
64
|
|
|
63
|
-
The current integration targets `@x402api/agent-wallet-cli@0.2.
|
|
65
|
+
The current integration targets `@x402api/agent-wallet-cli@0.2.3`. A compatible
|
|
64
66
|
live term is marked `agentWalletSupported: true` and must use the sponsored
|
|
65
67
|
Base USDC or Solana USDC/USDT launch profile with buyer native fees disabled.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
For the current declaration, x402api pays actual gas from its platform treasury
|
|
69
|
+
while the merchant tenant's allowance controls sponsorship admission. WarpMetal
|
|
70
|
+
retains the matched legacy tenant-credit declaration during rollout and rejects
|
|
71
|
+
mixed policy pairs. WarpMetal rejects a challenge with no compatible sponsored
|
|
72
|
+
term. Because the checkout is authenticated, do not substitute `x402api pay`,
|
|
73
|
+
`payment submit`, or `payment reconcile` for the returned WarpMetal submission
|
|
74
|
+
command.
|
|
69
75
|
|
|
70
76
|
`--payment-artifact` accepts the owner-only JSON artifact produced by a
|
|
71
77
|
compatible pinned x402api Agent Wallet release. WarpMetal validates its request
|
|
@@ -101,7 +107,8 @@ warpmetal renewal configure \
|
|
|
101
107
|
(--maximum-renewals <n> | --renew-through <UTC>) \
|
|
102
108
|
[--maximum-total-spend-atomic <amount>] \
|
|
103
109
|
--allowed-network <network> --allowed-asset <asset> \
|
|
104
|
-
--wallet <name> [--refill-target-atomic <amount>]
|
|
110
|
+
--wallet <name> [--refill-target-atomic <amount>] \
|
|
111
|
+
[--email <address> | --without-email-notifications] --json
|
|
105
112
|
warpmetal renewal status --server <serverId> --json
|
|
106
113
|
warpmetal renewal due (--server <serverId> | --all) --json
|
|
107
114
|
warpmetal renewal prepare --server <serverId> --json
|
|
@@ -113,9 +120,12 @@ warpmetal notifications configure --server <serverId> --email <address> \
|
|
|
113
120
|
warpmetal notifications status --server <serverId> --json
|
|
114
121
|
```
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
Without a verified notification email, renewal configuration asks for an email
|
|
124
|
+
before mutating policy unless `--without-email-notifications` explicitly opts
|
|
125
|
+
out. `renewal prepare` always returns safe funding address/balance argv and
|
|
126
|
+
returns signed refill-email argv only for a verified active subscription.
|
|
127
|
+
`renewal run` is an agent-facing state machine, not a wallet-signing daemon.
|
|
128
|
+
See [renewals.md](renewals.md).
|
|
119
129
|
|
|
120
130
|
## Server management
|
|
121
131
|
|
|
@@ -19,7 +19,7 @@ files or pass a WarpMetal credential to `x402api`.
|
|
|
19
19
|
WarpMetal runs on Node.js 20 or 22. The x402api Agent Wallet currently requires
|
|
20
20
|
Node.js 22. Use the exact published package reported by
|
|
21
21
|
`paymentWorkflow.signerPackage.spec`; the current contract is
|
|
22
|
-
`@x402api/agent-wallet-cli@0.2.
|
|
22
|
+
`@x402api/agent-wallet-cli@0.2.3`. Do not add it as a WarpMetal dependency,
|
|
23
23
|
install executable wallet code from an unpinned repository URL, or substitute
|
|
24
24
|
a similarly named package.
|
|
25
25
|
|
|
@@ -66,10 +66,12 @@ skill automatically.
|
|
|
66
66
|
|
|
67
67
|
6. If funding is short in an interactive conversation, show the exact deficit
|
|
68
68
|
in normal token units and atomic units, the exact network, token symbol and
|
|
69
|
-
contract/mint, and the payer wallet's public receiving address.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
contract/mint, and the payer wallet's public receiving address. Render only
|
|
70
|
+
that public address as a QR code and repeat the full address as copyable
|
|
71
|
+
text. Tell the human to transfer the token to the payer wallet address,
|
|
72
|
+
never to the token contract/mint or `paymentTerms.recipient`. Sponsored
|
|
73
|
+
launch payments never ask the buyer to fund ETH or SOL. Recheck the balance
|
|
74
|
+
after the transfer.
|
|
73
75
|
7. If funding is short in an unattended run, use only a preconfigured refill
|
|
74
76
|
mechanism returned by WarpMetal. For a configured renewal, set the returned
|
|
75
77
|
`refillWorkflow.environment`, invoke `refillWorkflow.argv` once, and stop
|
|
@@ -89,7 +91,7 @@ Use this structure when asking a human to fund the wallet:
|
|
|
89
91
|
> wallet `<payer-wallet-address>`. Token contract/mint: `<asset-identifier>`.
|
|
90
92
|
> Send the token to the payer wallet address, not to the token contract/mint and
|
|
91
93
|
> not to WarpMetal's payment recipient. Do not send ETH or SOL; the network fee
|
|
92
|
-
> is
|
|
94
|
+
> is paid by x402api from its platform treasury.
|
|
93
95
|
|
|
94
96
|
## Exact purchase workflow
|
|
95
97
|
|
|
@@ -114,7 +116,11 @@ paymentWorkflow.submit.argv
|
|
|
114
116
|
Replace only `<wallet-name>` after selecting the wallet for the advertised
|
|
115
117
|
network. Do not parse, rewrite, copy, or display the request envelope or payment
|
|
116
118
|
artifact. The envelope excludes the WarpMetal owner token; the artifact holds
|
|
117
|
-
the complete payment signature and remains owner-only.
|
|
119
|
+
the complete payment signature and remains owner-only. The safe challenge JSON
|
|
120
|
+
also contains `challengeHandle`, an opaque value retained by WarpMetal for
|
|
121
|
+
merchant reconciliation. It is deliberately excluded from the x402api V1
|
|
122
|
+
request envelope and must not be confused with the wallet-created buyer payment
|
|
123
|
+
identifier.
|
|
118
124
|
|
|
119
125
|
Choose only a term marked `agentWalletSupported: true`,
|
|
120
126
|
`sponsoredNetworkFee: true`, and `buyerNativeFeeRequired: false`. The supported
|
|
@@ -122,6 +128,12 @@ launch profiles are sponsored Base USDC and sponsored Solana USDC/USDT, bound
|
|
|
122
128
|
by the strict `com.x402api.gas-sponsorship` extension. Stop on an expired gas
|
|
123
129
|
reservation or any buyer-funded, unsupported, or unbound alternative.
|
|
124
130
|
|
|
131
|
+
x402api pays actual gas from its platform treasury. The merchant tenant's
|
|
132
|
+
active sponsorship allowance controls admission, but actual gas is not a
|
|
133
|
+
tenant debit. During the coordinated rollout, accept only the exact 0.2.3
|
|
134
|
+
platform-treasury declaration or the matched legacy tenant-credit declaration;
|
|
135
|
+
never accept a mixed billing and final-charge policy.
|
|
136
|
+
|
|
125
137
|
Among valid sponsored terms, honor an explicit task or operator network and
|
|
126
138
|
asset preference. Otherwise prefer a compatible wallet that is already
|
|
127
139
|
sufficiently funded. If several terms remain, choose the first compatible term
|
|
@@ -162,6 +174,12 @@ current `termEndsAt` generation are different.
|
|
|
162
174
|
challenge flow again, inspect the replacement terms, and authorize one new
|
|
163
175
|
artifact only when the protocol and current interactive or autonomous
|
|
164
176
|
payment authority allow it.
|
|
177
|
+
- `sponsorship_allowance_unavailable`, `sponsorship_payment_cap_exceeded`,
|
|
178
|
+
`sponsorship_payment_allowance_exhausted`,
|
|
179
|
+
`sponsorship_volume_allowance_exhausted`, or
|
|
180
|
+
`sponsorship_gas_budget_exhausted`: the wallet treats the authorization as
|
|
181
|
+
terminal. The merchant tenant must restore or change its allowance and issue
|
|
182
|
+
a fresh challenge; never retry the artifact or fall back to buyer-funded gas.
|
|
165
183
|
- `manual_review`: payment or fulfillment may be final. Stop all payment and
|
|
166
184
|
mutation retries.
|
|
167
185
|
- Expired or corrupt artifact, changed request digest, unexpected recipient,
|
|
@@ -23,15 +23,18 @@ warpmetal renewal configure \
|
|
|
23
23
|
--allowed-asset <exact-contract-or-mint> \
|
|
24
24
|
--wallet <agent-wallet-name> \
|
|
25
25
|
[--refill-target-atomic <target>] \
|
|
26
|
-
[--email <human-address>] \
|
|
26
|
+
[--email <human-address> | --without-email-notifications] \
|
|
27
27
|
--json
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
Without an active verified notification email, configuration asks for one
|
|
31
|
+
before changing policy. `--email` queues verification; refill and expanded
|
|
32
|
+
lifecycle notices remain disabled until the human opens the verification link.
|
|
33
|
+
Use `--without-email-notifications` only as an explicit opt-out; no signed
|
|
34
|
+
refill-email workflow is returned in that state. A refill target is a funding
|
|
35
|
+
target, not spend permission, and cannot exceed the per-renewal cap. The human
|
|
36
|
+
may transfer more than the displayed deficit. The agent may spend only the
|
|
37
|
+
exact renewal amount allowed by policy.
|
|
35
38
|
|
|
36
39
|
The WarpMetal CLI stores only the local wallet name. The backend stores
|
|
37
40
|
non-secret limits, counters, payment rail, time horizon, and an opaque
|
|
@@ -70,8 +73,10 @@ one policy-compatible rail, and returns:
|
|
|
70
73
|
```text
|
|
71
74
|
paymentWorkflow.authorize.argv
|
|
72
75
|
paymentWorkflow.submit.argv
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
paymentWorkflow.fundingWorkflow
|
|
77
|
+
refillNotification
|
|
78
|
+
refillWorkflow.environment (verified email only)
|
|
79
|
+
refillWorkflow.argv (verified email only)
|
|
75
80
|
```
|
|
76
81
|
|
|
77
82
|
Invoke the authorize argv once. It writes an owner-only artifact. Then invoke
|
|
@@ -112,10 +117,12 @@ authoritative balance before queuing email. The intent cannot name a recipient
|
|
|
112
117
|
email, tenant, or product.
|
|
113
118
|
|
|
114
119
|
In a human conversation, also display the exact deficit, network, token symbol,
|
|
115
|
-
contract or mint, and payer wallet address.
|
|
120
|
+
contract or mint, and payer wallet address. Render only that public address as
|
|
121
|
+
a QR code and repeat it as copyable text. Tell the human to fund the payer
|
|
116
122
|
wallet address—not the token contract/mint and not WarpMetal's merchant
|
|
117
123
|
recipient. Do not ask for ETH or SOL because supported network fees are
|
|
118
|
-
sponsored.
|
|
124
|
+
sponsored. A refill email repeats the address as text and includes a locally
|
|
125
|
+
generated QR encoding only that address.
|
|
119
126
|
|
|
120
127
|
## Stop and recovery states
|
|
121
128
|
|
|
@@ -110,6 +110,13 @@ Confirm the plan, hostname, OS, and public key before preparing it.
|
|
|
110
110
|
- A signed HTTP 402 rejects that signature. Use the new live challenge for one
|
|
111
111
|
replacement authorization after checking the new terms against the current
|
|
112
112
|
interactive or autonomous payment authority.
|
|
113
|
+
- Treat `sponsorship_allowance_unavailable`,
|
|
114
|
+
`sponsorship_payment_cap_exceeded`,
|
|
115
|
+
`sponsorship_payment_allowance_exhausted`,
|
|
116
|
+
`sponsorship_volume_allowance_exhausted`, and
|
|
117
|
+
`sponsorship_gas_budget_exhausted` as terminal for the current authorization.
|
|
118
|
+
Require the merchant tenant to restore or change its sponsorship allowance
|
|
119
|
+
and issue a fresh challenge; never switch to buyer-funded gas.
|
|
113
120
|
- On timeout or restart after wallet authorization, reuse the saved x402api
|
|
114
121
|
attempt and artifact through WarpMetal. Never authorize again merely because
|
|
115
122
|
submission or fulfillment is uncertain, and never send the private owner
|
package/src/api.js
CHANGED
|
@@ -216,6 +216,14 @@ export class WarpMetalClient {
|
|
|
216
216
|
);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
deleteNotifications(serverId, token) {
|
|
220
|
+
return this.request(
|
|
221
|
+
"DELETE",
|
|
222
|
+
`/servers/${encodeURIComponent(serverId)}/notifications`,
|
|
223
|
+
{ token },
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
219
227
|
renewalCheckout(planId, { bodyText, token, paymentSignature }) {
|
|
220
228
|
return this.checkout(`/checkout/${encodeURIComponent(planId)}/renew`, {
|
|
221
229
|
bodyText,
|
package/src/cli.js
CHANGED
|
@@ -84,6 +84,7 @@ Usage:
|
|
|
84
84
|
warpmetal renewal configure --server <serverId> --renew-before-days <n>
|
|
85
85
|
--maximum-payment-atomic <amount> (--maximum-renewals <n> | --renew-through <UTC>)
|
|
86
86
|
--allowed-network <CAIP-2> --allowed-asset <asset> --wallet <name>
|
|
87
|
+
[--email <address> | --without-email-notifications]
|
|
87
88
|
warpmetal renewal status|prepare|submit|run ...
|
|
88
89
|
warpmetal renewal due (--server <serverId> | --all)
|
|
89
90
|
warpmetal notifications configure --server <serverId> --email <address>
|
|
@@ -364,9 +365,15 @@ async function pollGrant(
|
|
|
364
365
|
|
|
365
366
|
function challengeResult(taskId, checkoutBody, response) {
|
|
366
367
|
const paymentRequired = response.headers["payment-required"];
|
|
368
|
+
const challengeHandle = response.headers["x-x402api-challenge-handle"];
|
|
367
369
|
if (response.status === 402 && !paymentRequired) {
|
|
368
370
|
throw new CliError("WarpMetal returned HTTP 402 without PAYMENT-REQUIRED.");
|
|
369
371
|
}
|
|
372
|
+
if (response.status === 402 && !challengeHandle) {
|
|
373
|
+
throw new CliError(
|
|
374
|
+
"WarpMetal returned HTTP 402 without X-X402API-Challenge-Handle.",
|
|
375
|
+
);
|
|
376
|
+
}
|
|
370
377
|
return {
|
|
371
378
|
status: response.data?.status,
|
|
372
379
|
taskId,
|
|
@@ -374,6 +381,7 @@ function challengeResult(taskId, checkoutBody, response) {
|
|
|
374
381
|
response.data?.paymentAttemptId ||
|
|
375
382
|
response.headers["x-warpmetal-payment-attempt"],
|
|
376
383
|
paymentRequired,
|
|
384
|
+
challengeHandle,
|
|
377
385
|
checkoutBodySha256: createHash("sha256").update(checkoutBody).digest("hex"),
|
|
378
386
|
};
|
|
379
387
|
}
|
|
@@ -645,7 +653,7 @@ async function handleCheckoutChallenge(client, store, options, context) {
|
|
|
645
653
|
stringOption(options, "request-envelope-out"),
|
|
646
654
|
);
|
|
647
655
|
const paymentInstructions = safe.paymentWorkflow
|
|
648
|
-
? `\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)}\nSubmit with WarpMetal: ${shellCommand(safe.paymentWorkflow.submit.argv)}`
|
|
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)}`
|
|
649
657
|
: "";
|
|
650
658
|
emit(
|
|
651
659
|
context.stdout,
|
|
@@ -775,6 +783,44 @@ export function refillRenewBy(termEndsAt, currentTime = Date.now()) {
|
|
|
775
783
|
return new Date(Number.isFinite(parsed) && parsed > minimum ? parsed : minimum).toISOString();
|
|
776
784
|
}
|
|
777
785
|
|
|
786
|
+
function activeNotificationSubscription(notifications) {
|
|
787
|
+
const subscription = notifications?.subscription;
|
|
788
|
+
return Boolean(
|
|
789
|
+
notifications?.configured &&
|
|
790
|
+
subscription?.verified &&
|
|
791
|
+
!subscription?.disabled,
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function refillNotificationState(notifications, policy) {
|
|
796
|
+
const subscription = notifications?.subscription;
|
|
797
|
+
if (!notifications?.configured || !subscription || subscription.disabled) {
|
|
798
|
+
return {
|
|
799
|
+
available: false,
|
|
800
|
+
reason: "verified_email_required",
|
|
801
|
+
subscription: subscription || null,
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
if (!subscription.verified) {
|
|
805
|
+
return {
|
|
806
|
+
available: false,
|
|
807
|
+
reason: "email_verification_required",
|
|
808
|
+
subscription,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
if (
|
|
812
|
+
policy?.notificationReference &&
|
|
813
|
+
subscription.reference !== policy.notificationReference
|
|
814
|
+
) {
|
|
815
|
+
return {
|
|
816
|
+
available: false,
|
|
817
|
+
reason: "notification_reference_mismatch",
|
|
818
|
+
subscription,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
return { available: true, reason: null, subscription };
|
|
822
|
+
}
|
|
823
|
+
|
|
778
824
|
async function handleRenewalConfigure(client, store, options, context) {
|
|
779
825
|
const serverId = stringOption(options, "server", { required: true });
|
|
780
826
|
const wallet = stringOption(options, "wallet", { required: true });
|
|
@@ -818,7 +864,72 @@ async function handleRenewalConfigure(client, store, options, context) {
|
|
|
818
864
|
for (const [key, value] of Object.entries(body)) {
|
|
819
865
|
if (value === undefined) delete body[key];
|
|
820
866
|
}
|
|
867
|
+
const email = stringOption(options, "email");
|
|
868
|
+
const withoutEmailNotifications = booleanOption(
|
|
869
|
+
options,
|
|
870
|
+
"without-email-notifications",
|
|
871
|
+
);
|
|
872
|
+
if (email && withoutEmailNotifications) {
|
|
873
|
+
throw new CliError(
|
|
874
|
+
"Use either --email or --without-email-notifications, not both.",
|
|
875
|
+
{ exitCode: 2 },
|
|
876
|
+
);
|
|
877
|
+
}
|
|
821
878
|
const token = await requireServerToken(store, serverId, options, context.env);
|
|
879
|
+
let notifications = (await client.getNotifications(serverId, token)).data;
|
|
880
|
+
if (
|
|
881
|
+
body.enabled &&
|
|
882
|
+
!email &&
|
|
883
|
+
!withoutEmailNotifications &&
|
|
884
|
+
!activeNotificationSubscription(notifications)
|
|
885
|
+
) {
|
|
886
|
+
const verificationPending = Boolean(
|
|
887
|
+
notifications.configured &&
|
|
888
|
+
notifications.subscription &&
|
|
889
|
+
!notifications.subscription.disabled,
|
|
890
|
+
);
|
|
891
|
+
const action = verificationPending
|
|
892
|
+
? "email_verification_required"
|
|
893
|
+
: "email_required";
|
|
894
|
+
const output = {
|
|
895
|
+
action,
|
|
896
|
+
serverId,
|
|
897
|
+
reason: verificationPending
|
|
898
|
+
? "Verify the existing notification email or resend verification with --email."
|
|
899
|
+
: "A verified email enables renewal and wallet-refill notifications.",
|
|
900
|
+
notifications,
|
|
901
|
+
next: {
|
|
902
|
+
configureEmail: `warpmetal renewal configure ... --email <address>`,
|
|
903
|
+
continueWithoutEmail:
|
|
904
|
+
"warpmetal renewal configure ... --without-email-notifications",
|
|
905
|
+
},
|
|
906
|
+
};
|
|
907
|
+
emit(
|
|
908
|
+
context.stdout,
|
|
909
|
+
output,
|
|
910
|
+
context.json,
|
|
911
|
+
verificationPending
|
|
912
|
+
? `Verify the notification email for ${serverId}, or rerun with --email to resend verification. To opt out explicitly, rerun with --without-email-notifications.`
|
|
913
|
+
: `An email is needed for renewal and wallet-refill notifications. Rerun with --email <address>, or opt out explicitly with --without-email-notifications.`,
|
|
914
|
+
);
|
|
915
|
+
return 6;
|
|
916
|
+
}
|
|
917
|
+
if (email) {
|
|
918
|
+
notifications = (
|
|
919
|
+
await client.putNotifications(serverId, { email }, token)
|
|
920
|
+
).data;
|
|
921
|
+
} else if (
|
|
922
|
+
withoutEmailNotifications &&
|
|
923
|
+
notifications.configured &&
|
|
924
|
+
notifications.subscription &&
|
|
925
|
+
!notifications.subscription.disabled
|
|
926
|
+
) {
|
|
927
|
+
await client.deleteNotifications(serverId, token);
|
|
928
|
+
notifications = {
|
|
929
|
+
...notifications,
|
|
930
|
+
subscription: { ...notifications.subscription, disabled: true },
|
|
931
|
+
};
|
|
932
|
+
}
|
|
822
933
|
const result = await client.putRenewalPolicy(serverId, body, token);
|
|
823
934
|
await store.saveRenewalPolicy(
|
|
824
935
|
serverId,
|
|
@@ -826,19 +937,27 @@ async function handleRenewalConfigure(client, store, options, context) {
|
|
|
826
937
|
wallet,
|
|
827
938
|
refillTargetAtomic,
|
|
828
939
|
);
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
940
|
+
const notificationState = {
|
|
941
|
+
optedOut: withoutEmailNotifications,
|
|
942
|
+
refillAvailable: activeNotificationSubscription(notifications),
|
|
943
|
+
status: withoutEmailNotifications
|
|
944
|
+
? "opted_out"
|
|
945
|
+
: activeNotificationSubscription(notifications)
|
|
946
|
+
? "verified"
|
|
947
|
+
: "verification_required",
|
|
948
|
+
};
|
|
949
|
+
const output = {
|
|
950
|
+
...result.data,
|
|
951
|
+
wallet,
|
|
952
|
+
refillTargetAtomic,
|
|
953
|
+
notifications,
|
|
954
|
+
notificationState,
|
|
955
|
+
};
|
|
837
956
|
emit(
|
|
838
957
|
context.stdout,
|
|
839
958
|
output,
|
|
840
959
|
context.json,
|
|
841
|
-
`Configured bounded renewal for ${serverId} with wallet ${wallet}.${email ? " Check the email verification link." : ""}`,
|
|
960
|
+
`Configured bounded renewal for ${serverId} with wallet ${wallet}.${email && !notificationState.refillAvailable ? " Check the email verification link." : withoutEmailNotifications ? " Email and wallet-refill notifications were explicitly skipped." : ""}`,
|
|
842
961
|
);
|
|
843
962
|
return 0;
|
|
844
963
|
}
|
|
@@ -866,6 +985,7 @@ async function attachRenewalPaymentWorkflow(
|
|
|
866
985
|
serverId,
|
|
867
986
|
server,
|
|
868
987
|
renewal,
|
|
988
|
+
notifications,
|
|
869
989
|
response,
|
|
870
990
|
requestedEnvelopePath,
|
|
871
991
|
) {
|
|
@@ -924,6 +1044,7 @@ async function attachRenewalPaymentWorkflow(
|
|
|
924
1044
|
taskId: server.id,
|
|
925
1045
|
serverId,
|
|
926
1046
|
kind: "renewal",
|
|
1047
|
+
wallet: renewal.wallet,
|
|
927
1048
|
requestEnvelopePath,
|
|
928
1049
|
paymentArtifactPath: defaults.paymentArtifactPath,
|
|
929
1050
|
});
|
|
@@ -932,27 +1053,39 @@ async function attachRenewalPaymentWorkflow(
|
|
|
932
1053
|
BigInt(renewal.refillTargetAtomic) >= BigInt(term.amountAtomic)
|
|
933
1054
|
? renewal.refillTargetAtomic
|
|
934
1055
|
: term.amountAtomic;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1056
|
+
Object.assign(workflow.fundingWorkflow, {
|
|
1057
|
+
network: term.network,
|
|
1058
|
+
asset: term.asset,
|
|
1059
|
+
requiredPaymentAtomic: term.amountAtomic,
|
|
1060
|
+
targetBalanceAtomic: refillTarget,
|
|
1061
|
+
});
|
|
1062
|
+
const refillNotification = refillNotificationState(
|
|
1063
|
+
notifications,
|
|
1064
|
+
renewal.policy,
|
|
1065
|
+
);
|
|
1066
|
+
const refillWorkflow = refillNotification.available
|
|
1067
|
+
? {
|
|
1068
|
+
environment: {
|
|
1069
|
+
X402API_NOTIFICATION_URL: `${client.baseUrl}/notifications/x402api/refill`,
|
|
1070
|
+
},
|
|
1071
|
+
argv: [
|
|
1072
|
+
"x402api",
|
|
1073
|
+
"wallet",
|
|
1074
|
+
"notify-refill",
|
|
1075
|
+
"--wallet",
|
|
1076
|
+
renewal.wallet,
|
|
1077
|
+
"--subscription-reference",
|
|
1078
|
+
refillNotification.subscription.reference,
|
|
1079
|
+
"--renew-by",
|
|
1080
|
+
refillRenewBy(server.termEndsAt),
|
|
1081
|
+
"--target-balance-atomic",
|
|
1082
|
+
refillTarget,
|
|
1083
|
+
"--reason",
|
|
1084
|
+
"renewal",
|
|
1085
|
+
"--json",
|
|
1086
|
+
],
|
|
1087
|
+
}
|
|
1088
|
+
: undefined;
|
|
956
1089
|
Object.assign(safe, {
|
|
957
1090
|
serverId,
|
|
958
1091
|
paymentTerms: request.terms,
|
|
@@ -960,6 +1093,7 @@ async function attachRenewalPaymentWorkflow(
|
|
|
960
1093
|
paymentRequestDigest: request.requestDigest,
|
|
961
1094
|
paymentChallengeDigest: request.challengeDigest,
|
|
962
1095
|
paymentWorkflow: workflow,
|
|
1096
|
+
refillNotification,
|
|
963
1097
|
refillWorkflow,
|
|
964
1098
|
});
|
|
965
1099
|
await store.saveRenewalChallenge(serverId, {
|
|
@@ -968,6 +1102,7 @@ async function attachRenewalPaymentWorkflow(
|
|
|
968
1102
|
termEndsAt: server.termEndsAt,
|
|
969
1103
|
paymentRequired: safe.paymentRequired,
|
|
970
1104
|
paymentAttemptId: safe.paymentAttemptId,
|
|
1105
|
+
challengeHandle: safe.challengeHandle,
|
|
971
1106
|
paymentRequestDigest: safe.paymentRequestDigest,
|
|
972
1107
|
paymentChallengeDigest: safe.paymentChallengeDigest,
|
|
973
1108
|
paymentRequestEnvelopePath: requestEnvelopePath,
|
|
@@ -988,9 +1123,10 @@ function renewalActionExitCode(action) {
|
|
|
988
1123
|
|
|
989
1124
|
async function prepareRenewal(client, store, serverId, options, context) {
|
|
990
1125
|
const token = await requireServerToken(store, serverId, options, context.env);
|
|
991
|
-
const [serverResult, policyResult] = await Promise.all([
|
|
1126
|
+
const [serverResult, policyResult, notificationsResult] = await Promise.all([
|
|
992
1127
|
client.getServer(serverId, token),
|
|
993
1128
|
client.getRenewalPolicy(serverId, token),
|
|
1129
|
+
client.getNotifications(serverId, token),
|
|
994
1130
|
]);
|
|
995
1131
|
if (!policyResult.data.configured) {
|
|
996
1132
|
return {
|
|
@@ -1032,6 +1168,7 @@ async function prepareRenewal(client, store, serverId, options, context) {
|
|
|
1032
1168
|
serverId,
|
|
1033
1169
|
serverResult.data.task,
|
|
1034
1170
|
local,
|
|
1171
|
+
notificationsResult.data,
|
|
1035
1172
|
response,
|
|
1036
1173
|
stringOption(options, "request-envelope-out"),
|
|
1037
1174
|
);
|
|
@@ -1053,8 +1190,16 @@ async function prepareRenewal(client, store, serverId, options, context) {
|
|
|
1053
1190
|
async function handleRenewalPrepare(client, store, options, context) {
|
|
1054
1191
|
const serverId = stringOption(options, "server", { required: true });
|
|
1055
1192
|
const safe = await prepareRenewal(client, store, serverId, options, context);
|
|
1193
|
+
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.`
|
|
1195
|
+
: "";
|
|
1196
|
+
const refillInstructions = safe.refillWorkflow
|
|
1197
|
+
? `\nFor verified email refill, set ${Object.entries(safe.refillWorkflow.environment).map(([key, value]) => `${key}=${value}`).join(" ")} and run: ${shellCommand(safe.refillWorkflow.argv)}`
|
|
1198
|
+
: safe.paymentWorkflow
|
|
1199
|
+
? `\nVerified email refill is unavailable (${safe.refillNotification.reason}). Configure and verify notifications, or fund the displayed payer address directly.`
|
|
1200
|
+
: "";
|
|
1056
1201
|
const instructions = safe.paymentWorkflow
|
|
1057
|
-
? `\nAuthorize: ${shellCommand(safe.paymentWorkflow.authorize.argv)}
|
|
1202
|
+
? `\nAuthorize: ${shellCommand(safe.paymentWorkflow.authorize.argv)}${fundingInstructions}${refillInstructions}\nSubmit: ${shellCommand(safe.paymentWorkflow.submit.argv)}`
|
|
1058
1203
|
: "";
|
|
1059
1204
|
emit(
|
|
1060
1205
|
context.stdout,
|
|
@@ -2325,6 +2470,7 @@ async function dispatch(positionals, options, passthrough, context) {
|
|
|
2325
2470
|
"allowed-asset",
|
|
2326
2471
|
"refill-target-atomic",
|
|
2327
2472
|
"email",
|
|
2473
|
+
"without-email-notifications",
|
|
2328
2474
|
"disabled",
|
|
2329
2475
|
]);
|
|
2330
2476
|
return handleRenewalConfigure(client, store, options, context);
|
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.
|
|
18
|
+
export const AGENT_WALLET_VERSION = "0.2.3";
|
|
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";
|
|
@@ -183,6 +183,14 @@ function paymentResource(value, label) {
|
|
|
183
183
|
function validateGasSponsorship(declaration) {
|
|
184
184
|
const info = declaration.info;
|
|
185
185
|
const schema = declaration.schema;
|
|
186
|
+
const platformTreasuryPolicy =
|
|
187
|
+
isObject(info) &&
|
|
188
|
+
info.billingParty === "platform_treasury" &&
|
|
189
|
+
info.finalChargePolicy === "platform_treasury_actual_cost";
|
|
190
|
+
const legacyTenantCreditPolicy =
|
|
191
|
+
isObject(info) &&
|
|
192
|
+
info.billingParty === "tenant_service_credit" &&
|
|
193
|
+
info.finalChargePolicy === "canonical_actual_gas_capped_by_reservation";
|
|
186
194
|
const infoKeys = [
|
|
187
195
|
"billingParty",
|
|
188
196
|
"buyerNativeFeeRequired",
|
|
@@ -199,8 +207,7 @@ function validateGasSponsorship(declaration) {
|
|
|
199
207
|
info.version !== 1 ||
|
|
200
208
|
info.mode !== "facilitator_pays" ||
|
|
201
209
|
info.buyerNativeFeeRequired !== false ||
|
|
202
|
-
|
|
203
|
-
info.finalChargePolicy !== "canonical_actual_gas_capped_by_reservation" ||
|
|
210
|
+
(!platformTreasuryPolicy && !legacyTenantCreditPolicy) ||
|
|
204
211
|
typeof info.maximumReservationEvidenceDigest !== "string" ||
|
|
205
212
|
!SHA256.test(info.maximumReservationEvidenceDigest) ||
|
|
206
213
|
typeof info.expiresAt !== "string" ||
|
|
@@ -696,6 +703,7 @@ export function paymentWorkflow({
|
|
|
696
703
|
taskId,
|
|
697
704
|
serverId,
|
|
698
705
|
kind = "checkout",
|
|
706
|
+
wallet = "<wallet-name>",
|
|
699
707
|
requestEnvelopePath,
|
|
700
708
|
paymentArtifactPath,
|
|
701
709
|
}) {
|
|
@@ -739,7 +747,7 @@ export function paymentWorkflow({
|
|
|
739
747
|
"payment",
|
|
740
748
|
"authorize",
|
|
741
749
|
"--wallet",
|
|
742
|
-
|
|
750
|
+
wallet,
|
|
743
751
|
"--request-envelope",
|
|
744
752
|
requestEnvelopePath,
|
|
745
753
|
"--artifact-out",
|
|
@@ -747,6 +755,27 @@ export function paymentWorkflow({
|
|
|
747
755
|
"--json",
|
|
748
756
|
],
|
|
749
757
|
},
|
|
758
|
+
fundingWorkflow: {
|
|
759
|
+
action: "fund_wallet",
|
|
760
|
+
trigger: "insufficient_asset_balance",
|
|
761
|
+
wallet,
|
|
762
|
+
address: {
|
|
763
|
+
argv: ["x402api", "wallet", "address", "--wallet", wallet, "--json"],
|
|
764
|
+
},
|
|
765
|
+
balance: {
|
|
766
|
+
argv: ["x402api", "wallet", "balance", "--wallet", wallet, "--json"],
|
|
767
|
+
},
|
|
768
|
+
presentation: {
|
|
769
|
+
qrPayloadField: "address",
|
|
770
|
+
textPayloadField: "address",
|
|
771
|
+
showQr: true,
|
|
772
|
+
showAddressString: true,
|
|
773
|
+
showExactDeficit: true,
|
|
774
|
+
showNetworkAndAsset: true,
|
|
775
|
+
},
|
|
776
|
+
safety:
|
|
777
|
+
"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.",
|
|
778
|
+
},
|
|
750
779
|
submit: {
|
|
751
780
|
argv:
|
|
752
781
|
kind === "renewal"
|
package/src/state.js
CHANGED
|
@@ -280,6 +280,7 @@ export class StateStore {
|
|
|
280
280
|
{
|
|
281
281
|
paymentRequired,
|
|
282
282
|
paymentAttemptId,
|
|
283
|
+
challengeHandle,
|
|
283
284
|
paymentRequestDigest,
|
|
284
285
|
paymentChallengeDigest,
|
|
285
286
|
paymentWorkflow,
|
|
@@ -293,6 +294,7 @@ export class StateStore {
|
|
|
293
294
|
});
|
|
294
295
|
order.paymentRequired = paymentRequired;
|
|
295
296
|
order.paymentAttemptId = paymentAttemptId;
|
|
297
|
+
order.challengeHandle = challengeHandle;
|
|
296
298
|
order.paymentRequestDigest = paymentRequestDigest;
|
|
297
299
|
order.paymentChallengeDigest = paymentChallengeDigest;
|
|
298
300
|
order.paymentRequestEnvelopePath = paymentWorkflow?.requestEnvelopePath;
|
|
@@ -465,6 +467,7 @@ export class StateStore {
|
|
|
465
467
|
serverId: order.serverId,
|
|
466
468
|
planId: order.planId,
|
|
467
469
|
paymentAttemptId: order.paymentAttemptId,
|
|
470
|
+
challengeHandle: order.challengeHandle,
|
|
468
471
|
walletPaymentAttemptId: order.walletPaymentAttemptId,
|
|
469
472
|
walletName: order.walletName,
|
|
470
473
|
payerAddress: order.walletPayerAddress,
|
|
@@ -499,6 +502,7 @@ export class StateStore {
|
|
|
499
502
|
refillTargetAtomic: renewal.refillTargetAtomic,
|
|
500
503
|
policy: renewal.policy,
|
|
501
504
|
paymentAttemptId: renewal.paymentAttemptId,
|
|
505
|
+
challengeHandle: renewal.challengeHandle,
|
|
502
506
|
paymentArtifactExpiresAt: renewal.paymentArtifactExpiresAt,
|
|
503
507
|
})),
|
|
504
508
|
};
|