@daski/pay 0.2.0 → 0.3.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.
- package/README.md +35 -80
- package/dist/bin/daski.js +28 -11
- package/dist/bin/daski.js.map +1 -1
- package/dist/commands/approval.d.ts +52 -0
- package/dist/commands/approval.d.ts.map +1 -0
- package/dist/commands/approval.js +59 -0
- package/dist/commands/approval.js.map +1 -0
- package/dist/commands/buy.d.ts +4 -4
- package/dist/commands/buy.d.ts.map +1 -1
- package/dist/commands/buy.js +38 -57
- package/dist/commands/buy.js.map +1 -1
- package/dist/commands/confirmation.d.ts +29 -0
- package/dist/commands/confirmation.d.ts.map +1 -0
- package/dist/commands/confirmation.js +123 -0
- package/dist/commands/confirmation.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +4 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/order.d.ts +17 -2
- package/dist/commands/order.d.ts.map +1 -1
- package/dist/commands/order.js +15 -11
- package/dist/commands/order.js.map +1 -1
- package/dist/commands/signPayment.d.ts +1 -0
- package/dist/commands/signPayment.d.ts.map +1 -1
- package/dist/commands/signPayment.js +7 -2
- package/dist/commands/signPayment.js.map +1 -1
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +5 -17
- package/dist/commands/wallet.js.map +1 -1
- package/dist/config.d.ts +15 -11
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +59 -33
- package/dist/config.js.map +1 -1
- package/dist/gateway/purchase.d.ts +2 -5
- package/dist/gateway/purchase.d.ts.map +1 -1
- package/dist/gateway/purchase.js +19 -62
- package/dist/gateway/purchase.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/store/orders.d.ts +11 -0
- package/dist/store/orders.d.ts.map +1 -1
- package/dist/store/orders.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,101 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @daski/pay
|
|
2
2
|
|
|
3
|
-
The Daski buyer
|
|
4
|
-
|
|
3
|
+
The Daski buyer CLI obtains a quote, validates and signs an approved payment, and tracks the order across processes. Wallet keys stay in their protected store.
|
|
4
|
+
|
|
5
|
+
## Setup and purchase
|
|
5
6
|
|
|
6
7
|
```bash
|
|
7
|
-
|
|
8
|
+
npm install -g @daski/pay@0.3.0
|
|
9
|
+
daski doctor --json
|
|
8
10
|
```
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
> recomputes; wallet signs; the gateway never sees the key.
|
|
12
|
-
|
|
13
|
-
## Commands
|
|
14
|
-
|
|
15
|
-
| Command | What it does |
|
|
16
|
-
|---|---|
|
|
17
|
-
| `daski doctor` | Readiness report; exits 0 only when nothing blocks, including one read-only MCP round trip that proves this CLI can read the gateway's results. Every issue carries a remediation |
|
|
18
|
-
| `daski wallet create` | Generate a local EOA — interactive human confirmation required |
|
|
19
|
-
| `daski wallet address` / `balance` | The active payer address; native + USDC balances |
|
|
20
|
-
| `daski buy --provider <id> --outcome <id> --request <file.json>` | Challenge → approve → validate → sign → submit → record |
|
|
21
|
-
| `daski order status <handle>` | Read an order's state |
|
|
22
|
-
| `daski order artifact <handle>` | Write the result **to a file**, never to stdout |
|
|
23
|
-
| `daski order confirm/input/cancel <handle>` | Mutating lifecycle actions |
|
|
24
|
-
| `daski sign-payment --challenge <file.json>` | Validate, recompute, sign; print the exact `paymentPayload` |
|
|
25
|
-
|
|
26
|
-
All support `--json`. Global flags: `--profile`, `--signer <local|cdp|circle>`,
|
|
27
|
-
`--cdp-account`, `--circle-wallet`, `--max-per-order`, `--session-cap`.
|
|
28
|
-
|
|
29
|
-
## Spend caps are yours, not the agent's
|
|
30
|
-
|
|
31
|
-
`maxPerOrderUsdc`, `sessionCapUsdc` and `requireApprovalAboveUsdc` live only
|
|
32
|
-
in `~/.daski/config.json`. **No flag and no environment variable can raise
|
|
33
|
-
them.** `--max-per-order` and `--session-cap` may lower a cap for a single
|
|
34
|
-
invocation; a value above the configured one is refused.
|
|
35
|
-
|
|
36
|
-
Above `requireApprovalAboveUsdc`, `daski buy` shows the approval summary and
|
|
37
|
-
waits for a human. In `--json` or non-TTY mode it does not quietly proceed —
|
|
38
|
-
it exits `2` and explains how a human can approve.
|
|
12
|
+
Doctor reports the configured signer, its self-test, native state paths, network, balances, spending settings, and gateway compatibility. Reuse a healthy signer. Create a wallet only if one is missing: `daski wallet create` prompts interactively; authorized agent setup uses `daski wallet create --yes-human-approved`.
|
|
39
13
|
|
|
40
|
-
|
|
41
|
-
holds across processes rather than resetting whenever an agent restarts.
|
|
14
|
+
Complete the request using gateway discovery and `daski_get_outcome_requirements`, then run:
|
|
42
15
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
provider/outcome, amount, state, read capability and expiry. `daski order
|
|
47
|
-
status` works from a cold start with nothing but the handle.
|
|
48
|
-
|
|
49
|
-
The intent is recorded **before** the signature goes out, which is what makes
|
|
50
|
-
an interrupted purchase recoverable.
|
|
51
|
-
|
|
52
|
-
## Interrupted purchases are reconciled, never re-signed
|
|
53
|
-
|
|
54
|
-
On a timeout, a `PAYMENT_PENDING_RECONCILIATION`, or a transport drop after
|
|
55
|
-
submit, the CLI replays the *identical* signed authorization — idempotent by
|
|
56
|
-
the gateway's own published retry policy — and corroborates against the
|
|
57
|
-
payer's own order history. Only when both agree no order exists does it report
|
|
58
|
-
the purchase as safely retryable.
|
|
59
|
-
|
|
60
|
-
A second signature over a fresh challenge is a second order, and a second
|
|
61
|
-
charge. See [reconciliation](../../docs/policy.md#reconciliation).
|
|
16
|
+
```bash
|
|
17
|
+
daski buy --provider <id> --outcome <id> --request <file.json> --json
|
|
18
|
+
```
|
|
62
19
|
|
|
63
|
-
|
|
20
|
+
The command returns the actual quote and an approval identifier. After the user approves, repeat with `--approve <approval.id>`. Interactive use prompts directly. New profiles approve each paid quote and have no extra default budget. Existing settings remain in place on upgrade.
|
|
64
21
|
|
|
65
|
-
|
|
66
|
-
the envelope metadata. Provider results are validated but untrusted, and both
|
|
67
|
-
a terminal and an agent's context window are interpreters: dumping untrusted
|
|
68
|
-
content into one is how "here is your result" becomes "here are your
|
|
69
|
-
instructions".
|
|
22
|
+
## Commands
|
|
70
23
|
|
|
71
|
-
|
|
24
|
+
| Command | Behavior |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `doctor --json` | Diagnose signer, paths, settings, funds, and gateway |
|
|
27
|
+
| `wallet create` | Create a local signer when missing |
|
|
28
|
+
| `wallet address` / `wallet balance` | Read the active payer and balances |
|
|
29
|
+
| `budget [--per-order <usdc\|none>] [--total <usdc\|none>] [--approval-above <usdc>]` | View or explicitly change spending settings |
|
|
30
|
+
| `buy --provider <id> --outcome <id> --request <file.json>` | Quote, approve, validate, sign, submit, and record |
|
|
31
|
+
| `order status <handle>` | Read order state using cached read access or a fresh authorization |
|
|
32
|
+
| `order artifact <handle> [--output <file>]` | Save the provider result to a file |
|
|
33
|
+
| `order input <handle> --request <file.json>` | Submit customer input |
|
|
34
|
+
| `order cancel <handle>` | Request cancellation |
|
|
35
|
+
| `order confirm <handle> --choice <Confirmed\|NotConfirmed>` | Prepare, validate, sign, and submit the user's review |
|
|
36
|
+
| `order revoke-confirmation <handle>` | Withdraw the active review |
|
|
37
|
+
| `order confirm <handle> --resume` | Reconcile the stored review without another EAS signature |
|
|
38
|
+
| `order reconcile <intentId>` | Query settlement for one payment identifier |
|
|
39
|
+
| `sign-payment --challenge <file.json>` | Advanced payment signer; supports the same --approve flow |
|
|
72
40
|
|
|
73
|
-
|
|
74
|
-
`cdp` and `circle` are implemented and marked *candidate pending conformance*
|
|
75
|
-
until a run passes; `daski doctor` says so rather than implying otherwise. The
|
|
76
|
-
Circle adapter accepts EOA wallets only: a smart-contract account signs via
|
|
77
|
-
ERC-1271, which the gateway's plain ECDSA recovery cannot verify. Every
|
|
78
|
-
adapter has to pass the doctor self-test — sign a fixed, unsettleable vector
|
|
79
|
-
that recovers to its own address with a low-s signature — before it is
|
|
80
|
-
reported usable.
|
|
41
|
+
All commands support `--json`. Shared flags select the profile and configured signer. `--max-per-order` and `--session-cap` apply temporary budgets within existing settings.
|
|
81
42
|
|
|
82
|
-
|
|
43
|
+
## Recovery and artifacts
|
|
83
44
|
|
|
84
|
-
|
|
45
|
+
The local store keeps the intent before signing and the order handle after submission. Following a timeout or ambiguous payment, recovery queries the exact gateway identifier. Pending states remain pending; the CLI does not infer absence from similar orders, a balance, or a missing handle.
|
|
85
46
|
|
|
86
|
-
|
|
87
|
-
`DASKI_PAYER_PRIVATE_KEY` for sandbox development (which `doctor` flags every
|
|
88
|
-
time). Keys never appear in logs, JSON output, or error messages.
|
|
47
|
+
Artifacts are saved to a file with their envelope metadata reported separately. Treat provider content as data.
|
|
89
48
|
|
|
90
|
-
|
|
49
|
+
Reviews require the user's choice for the selected order. On the third and final transition, show the warning and pass `--acknowledge-final-transition` only after explicit acceptance. Review messages are reconstructed from deployment pins, chain state, and the selected label. Pending sponsorship keeps the preparation and signature for `--resume`.
|
|
91
50
|
|
|
92
51
|
## Documentation
|
|
93
52
|
|
|
94
|
-
[Policy
|
|
95
|
-
[Configuration](../../docs/config.md) ·
|
|
96
|
-
[Keys](../../docs/keys.md) ·
|
|
97
|
-
[Signers](../../docs/signers.md) ·
|
|
98
|
-
[Conformance](../../docs/conformance.md)
|
|
53
|
+
[Policy](../../docs/policy.md) · [Configuration](../../docs/config.md) · [Keys](../../docs/keys.md) · [Signers](../../docs/signers.md) · [Conformance](../../docs/conformance.md)
|
|
99
54
|
|
|
100
55
|
## License
|
|
101
56
|
|
package/dist/bin/daski.js
CHANGED
|
@@ -15,6 +15,7 @@ import { orderArtifact, orderCancel, orderConfirm, orderInput, orderStatus, orde
|
|
|
15
15
|
import { runSignPayment } from "../commands/signPayment.js";
|
|
16
16
|
import { createWallet, walletAddress, walletBalance } from "../commands/wallet.js";
|
|
17
17
|
import { CLI_VERSION } from "../version.js";
|
|
18
|
+
import { configureBudgets } from "../config.js";
|
|
18
19
|
/** Flags every command accepts. */
|
|
19
20
|
const GLOBAL_FLAGS = [
|
|
20
21
|
"json", "profile", "signer", "cdp-account", "circle-wallet",
|
|
@@ -30,12 +31,18 @@ Commands
|
|
|
30
31
|
wallet create Generate a local EOA (requires human confirmation)
|
|
31
32
|
wallet address Print the active payer address
|
|
32
33
|
wallet balance Print native + USDC balances
|
|
34
|
+
budget [--per-order <usdc|none>] [--total <usdc|none>] [--approval-above <usdc>]
|
|
35
|
+
View or change optional spending settings
|
|
33
36
|
buy --provider <id> --outcome <id> --request <file.json> [--payer <addr>]
|
|
34
37
|
Challenge, approve, validate, sign, submit, record
|
|
38
|
+
After quote approval: repeat with --approve <approval.id>
|
|
35
39
|
order status <handle> Read an order's state
|
|
36
40
|
order artifact <handle> [--output <file>]
|
|
37
41
|
Write the result bytes to a file (never to stdout)
|
|
38
|
-
order confirm <handle>
|
|
42
|
+
order confirm <handle> --choice <Confirmed|NotConfirmed>
|
|
43
|
+
Record the user's delivery review
|
|
44
|
+
order revoke-confirmation <handle> Withdraw the active review
|
|
45
|
+
order confirm <handle> --resume Reconcile a pending review submission
|
|
39
46
|
order input <handle> --request <file.json>
|
|
40
47
|
Submit requested customer input
|
|
41
48
|
order cancel <handle> Request cancellation
|
|
@@ -49,11 +56,11 @@ Global flags
|
|
|
49
56
|
--signer <local|cdp|circle> Override the profile's signer
|
|
50
57
|
--cdp-account <name> CDP account for --signer cdp (or DASKI_CDP_ACCOUNT)
|
|
51
58
|
--circle-wallet <id> Circle wallet id for --signer circle (or DASKI_CIRCLE_WALLET)
|
|
52
|
-
--max-per-order <usdc>
|
|
53
|
-
--session-cap <usdc>
|
|
59
|
+
--max-per-order <usdc> Apply a temporary per-order budget
|
|
60
|
+
--session-cap <usdc> Apply a temporary total budget
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
New profiles use quote approval, with optional budgets. Doctor reports the
|
|
63
|
+
active configuration path. See https://github.com/daski-io/buyer#readme
|
|
57
64
|
`;
|
|
58
65
|
async function main(argv) {
|
|
59
66
|
const { command, flags } = parseArgs(argv);
|
|
@@ -76,6 +83,12 @@ async function main(argv) {
|
|
|
76
83
|
sessionCapUsdc: stringFlag(flags, "session-cap"),
|
|
77
84
|
};
|
|
78
85
|
switch (command[0]) {
|
|
86
|
+
case "budget": {
|
|
87
|
+
assertKnownFlags(flags, ["json", "profile", "per-order", "total", "approval-above"]);
|
|
88
|
+
emit(configureBudgets({ profile: shared.profile, perOrder: stringFlag(flags, "per-order"),
|
|
89
|
+
total: stringFlag(flags, "total"), approvalAbove: stringFlag(flags, "approval-above") }), output);
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
79
92
|
case "doctor": {
|
|
80
93
|
assertKnownFlags(flags, GLOBAL_FLAGS);
|
|
81
94
|
const report = await runDoctor(shared);
|
|
@@ -106,7 +119,7 @@ async function main(argv) {
|
|
|
106
119
|
}
|
|
107
120
|
}
|
|
108
121
|
case "buy": {
|
|
109
|
-
assertKnownFlags(flags, [...GLOBAL_FLAGS, "provider", "outcome", "request", "payer", "legacy-arg", "
|
|
122
|
+
assertKnownFlags(flags, [...GLOBAL_FLAGS, "provider", "outcome", "request", "payer", "legacy-arg", "approve"]);
|
|
110
123
|
const result = await runBuy({
|
|
111
124
|
...shared,
|
|
112
125
|
providerAgentId: requireFlag(flags, "provider"),
|
|
@@ -114,7 +127,7 @@ async function main(argv) {
|
|
|
114
127
|
requestFile: requireFlag(flags, "request"),
|
|
115
128
|
payer: stringFlag(flags, "payer"),
|
|
116
129
|
legacyArg: boolFlag(flags, "legacy-arg"),
|
|
117
|
-
|
|
130
|
+
approved: stringFlag(flags, "approve"),
|
|
118
131
|
json,
|
|
119
132
|
});
|
|
120
133
|
emit(result, output);
|
|
@@ -140,8 +153,11 @@ async function main(argv) {
|
|
|
140
153
|
emit(await orderArtifact({ ...base, output: stringFlag(flags, "output") }), output);
|
|
141
154
|
return 0;
|
|
142
155
|
case "confirm":
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
case "revoke-confirmation":
|
|
157
|
+
assertKnownFlags(flags, [...GLOBAL_FLAGS, "choice", "resume", "acknowledge-final-transition"]);
|
|
158
|
+
emit(await orderConfirm({ ...base, confirmation: stringFlag(flags, "choice"),
|
|
159
|
+
revoke: command[1] === "revoke-confirmation", resume: boolFlag(flags, "resume"),
|
|
160
|
+
acknowledgeFinalTransition: boolFlag(flags, "acknowledge-final-transition") }), output);
|
|
145
161
|
return 0;
|
|
146
162
|
case "cancel":
|
|
147
163
|
assertKnownFlags(flags, GLOBAL_FLAGS);
|
|
@@ -156,16 +172,17 @@ async function main(argv) {
|
|
|
156
172
|
emit(await orderReconcile(base), output);
|
|
157
173
|
return 0;
|
|
158
174
|
default:
|
|
159
|
-
throw unknownSubcommand("order", command[1], ["status", "artifact", "confirm", "input", "cancel", "reconcile"]);
|
|
175
|
+
throw unknownSubcommand("order", command[1], ["status", "artifact", "confirm", "revoke-confirmation", "input", "cancel", "reconcile"]);
|
|
160
176
|
}
|
|
161
177
|
}
|
|
162
178
|
case "sign-payment": {
|
|
163
|
-
assertKnownFlags(flags, [...GLOBAL_FLAGS, "challenge", "provider", "outcome"]);
|
|
179
|
+
assertKnownFlags(flags, [...GLOBAL_FLAGS, "challenge", "provider", "outcome", "approve"]);
|
|
164
180
|
emit(await runSignPayment({
|
|
165
181
|
...shared,
|
|
166
182
|
challengeFile: requireFlag(flags, "challenge"),
|
|
167
183
|
providerAgentId: stringFlag(flags, "provider"),
|
|
168
184
|
outcomeId: stringFlag(flags, "outcome"),
|
|
185
|
+
approved: stringFlag(flags, "approve"),
|
|
169
186
|
json,
|
|
170
187
|
}), output);
|
|
171
188
|
return 0;
|
package/dist/bin/daski.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daski.js","sourceRoot":"","sources":["../../src/bin/daski.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AACH,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EACL,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EACjE,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"daski.js","sourceRoot":"","sources":["../../src/bin/daski.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type OrderBinding } from "@daski/x402-scheme";
|
|
2
|
+
import type { PaymentRequirement } from "../gateway/client.js";
|
|
3
|
+
/** Approval survives quote expiry only while all purchase terms stay the same. */
|
|
4
|
+
export declare function purchaseApproval(args: {
|
|
5
|
+
gatewayUrl: string;
|
|
6
|
+
payer: string;
|
|
7
|
+
providerAgentId: string;
|
|
8
|
+
outcomeId: string;
|
|
9
|
+
requirement: PaymentRequirement;
|
|
10
|
+
binding: OrderBinding;
|
|
11
|
+
purchaseNumber?: number;
|
|
12
|
+
}): {
|
|
13
|
+
gateway: string;
|
|
14
|
+
payer: `0x${string}`;
|
|
15
|
+
providerAgentId: string;
|
|
16
|
+
outcomeId: string;
|
|
17
|
+
network: string;
|
|
18
|
+
token: `0x${string}`;
|
|
19
|
+
payTo: `0x${string}`;
|
|
20
|
+
amount: string;
|
|
21
|
+
requestHash: `0x${string}`;
|
|
22
|
+
listing: `0x${string}`;
|
|
23
|
+
providerTerms: `0x${string}`;
|
|
24
|
+
id: `0x${string}`;
|
|
25
|
+
termsHash: `0x${string}`;
|
|
26
|
+
purchaseNumber: number;
|
|
27
|
+
};
|
|
28
|
+
/** A consumed approval cannot authorize another purchase with identical terms. */
|
|
29
|
+
export declare function nextPurchaseApproval(args: Parameters<typeof purchaseApproval>[0], profile: string): {
|
|
30
|
+
gateway: string;
|
|
31
|
+
payer: `0x${string}`;
|
|
32
|
+
providerAgentId: string;
|
|
33
|
+
outcomeId: string;
|
|
34
|
+
network: string;
|
|
35
|
+
token: `0x${string}`;
|
|
36
|
+
payTo: `0x${string}`;
|
|
37
|
+
amount: string;
|
|
38
|
+
requestHash: `0x${string}`;
|
|
39
|
+
listing: `0x${string}`;
|
|
40
|
+
providerTerms: `0x${string}`;
|
|
41
|
+
id: `0x${string}`;
|
|
42
|
+
termsHash: `0x${string}`;
|
|
43
|
+
purchaseNumber: number;
|
|
44
|
+
};
|
|
45
|
+
export declare function approvePurchase(args: {
|
|
46
|
+
approval: ReturnType<typeof purchaseApproval>;
|
|
47
|
+
approved?: string | undefined;
|
|
48
|
+
threshold: string;
|
|
49
|
+
json: boolean;
|
|
50
|
+
summary?: unknown;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
//# sourceMappingURL=approval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../src/commands/approval.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAMlF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAC9E,WAAW,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,YAAY,CAAC;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;;;;;;;;;;;;;;;EAcA;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;EASjG;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7E,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CACrD,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBhB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { canonicalHash, formatUsdc } from "@daski/x402-scheme";
|
|
2
|
+
import { getAddress } from "viem";
|
|
3
|
+
import { atomicUsdc } from "../config.js";
|
|
4
|
+
import { CliError } from "../cli/errors.js";
|
|
5
|
+
import { confirm, isInteractive } from "../cli/prompt.js";
|
|
6
|
+
import { note } from "../cli/output.js";
|
|
7
|
+
import { isUnspent, listOrders } from "../store/orders.js";
|
|
8
|
+
/** Approval survives quote expiry only while all purchase terms stay the same. */
|
|
9
|
+
export function purchaseApproval(args) {
|
|
10
|
+
const binding = args.binding;
|
|
11
|
+
const terms = {
|
|
12
|
+
gateway: args.gatewayUrl.replace(/\/$/, ""), payer: getAddress(args.payer),
|
|
13
|
+
providerAgentId: args.providerAgentId, outcomeId: args.outcomeId,
|
|
14
|
+
network: args.requirement.network, token: getAddress(args.requirement.asset),
|
|
15
|
+
payTo: getAddress(args.requirement.payTo), amount: BigInt(args.requirement.amount).toString(),
|
|
16
|
+
requestHash: binding.canonicalRequestHash,
|
|
17
|
+
listing: binding.version === 2 ? binding.runtimeCommitmentHash : binding.listingManifestHash,
|
|
18
|
+
providerTerms: binding.version === 2 ? binding.providerIntentHash : binding.providerOfferHash,
|
|
19
|
+
};
|
|
20
|
+
const termsHash = canonicalHash(terms);
|
|
21
|
+
const purchaseNumber = args.purchaseNumber ?? 1;
|
|
22
|
+
return { id: canonicalHash({ termsHash, purchaseNumber }), termsHash, purchaseNumber, ...terms };
|
|
23
|
+
}
|
|
24
|
+
/** A consumed approval cannot authorize another purchase with identical terms. */
|
|
25
|
+
export function nextPurchaseApproval(args, profile) {
|
|
26
|
+
const first = purchaseApproval(args);
|
|
27
|
+
const prior = listOrders(profile).filter((order) => order.approvalTermsHash === first.termsHash && !isUnspent(order));
|
|
28
|
+
const pending = prior.find((order) => ["AUTHORIZED", "SUBMITTED", "PENDING_RECONCILIATION"].includes(order.state));
|
|
29
|
+
if (pending)
|
|
30
|
+
throw new CliError({ code: "DASKI_PAYMENT_PENDING_RECONCILIATION",
|
|
31
|
+
message: "This purchase already has an authorized or pending order.",
|
|
32
|
+
remediation: `Continue with daski order reconcile ${pending.intentId}.`,
|
|
33
|
+
details: { intentId: pending.intentId, orderHandle: pending.handle ?? null } });
|
|
34
|
+
return purchaseApproval({ ...args, purchaseNumber: prior.length + 1 });
|
|
35
|
+
}
|
|
36
|
+
export async function approvePurchase(args) {
|
|
37
|
+
if (args.approved !== undefined) {
|
|
38
|
+
if (args.approved === args.approval.id)
|
|
39
|
+
return;
|
|
40
|
+
throw new CliError({ code: "DASKI_QUOTE_CHANGED",
|
|
41
|
+
message: "The quote or purchase differs from the approved one.",
|
|
42
|
+
remediation: "Show the new quote to the user, then pass its approval.id with --approve.",
|
|
43
|
+
details: { approval: args.approval, approvalSummary: args.summary }, exitCode: 2 });
|
|
44
|
+
}
|
|
45
|
+
if (BigInt(args.approval.amount) <= atomicUsdc(args.threshold))
|
|
46
|
+
return;
|
|
47
|
+
if (args.json || !isInteractive()) {
|
|
48
|
+
throw new CliError({ code: "DASKI_HUMAN_APPROVAL_REQUIRED",
|
|
49
|
+
message: `Approve this purchase for ${formatUsdc(BigInt(args.approval.amount))}.`,
|
|
50
|
+
remediation: "Show the quote to the user. After approval, repeat the command with --approve <approval.id>.",
|
|
51
|
+
details: { approval: args.approval, approvalSummary: args.summary }, exitCode: 2 });
|
|
52
|
+
}
|
|
53
|
+
note(JSON.stringify(args.summary ?? args.approval, null, 2));
|
|
54
|
+
if (!await confirm(`Pay ${formatUsdc(BigInt(args.approval.amount))} on ${args.approval.network}?`)) {
|
|
55
|
+
throw new CliError({ code: "DASKI_PURCHASE_DECLINED", message: "The purchase was declined.",
|
|
56
|
+
remediation: "Run the command again when ready.", exitCode: 2 });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/commands/approval.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/buy.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PolicyRefusal } from "@daski/x402-scheme";
|
|
2
|
-
import { type ContextOptions } from "../context.js";
|
|
2
|
+
import { type ContextOptions, type CommandContext } from "../context.js";
|
|
3
3
|
export interface BuyOptions extends ContextOptions {
|
|
4
4
|
providerAgentId: string;
|
|
5
5
|
outcomeId: string;
|
|
@@ -7,9 +7,9 @@ export interface BuyOptions extends ContextOptions {
|
|
|
7
7
|
payer?: string | undefined;
|
|
8
8
|
json: boolean;
|
|
9
9
|
legacyArg?: boolean;
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/** The identifier of the quote the user approved. */
|
|
11
|
+
approved?: string | undefined;
|
|
12
12
|
}
|
|
13
|
-
export declare function runBuy(options: BuyOptions): Promise<Record<string, unknown>>;
|
|
13
|
+
export declare function runBuy(options: BuyOptions, contextFactory?: (options: ContextOptions) => Promise<CommandContext>): Promise<Record<string, unknown>>;
|
|
14
14
|
export { PolicyRefusal };
|
|
15
15
|
//# sourceMappingURL=buy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buy.d.ts","sourceRoot":"","sources":["../../src/commands/buy.ts"],"names":[],"mappings":"AAQA,OAAO,
|
|
1
|
+
{"version":3,"file":"buy.d.ts","sourceRoot":"","sources":["../../src/commands/buy.ts"],"names":[],"mappings":"AAQA,OAAO,EAA6B,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAG9E,OAAO,EAAiB,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAcxF,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,cAAc,GAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,cAAc,CAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAwKxK;AA+HD,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
package/dist/commands/buy.js
CHANGED
|
@@ -6,18 +6,17 @@
|
|
|
6
6
|
* and any re-sign.
|
|
7
7
|
*/
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
9
|
-
import { formatUsdc, PolicyRefusal } from "@daski/x402-scheme";
|
|
9
|
+
import { canonicalHash, formatUsdc, PolicyRefusal } from "@daski/x402-scheme";
|
|
10
10
|
import { CliError } from "../cli/errors.js";
|
|
11
|
-
import { confirm, isInteractive } from "../cli/prompt.js";
|
|
12
11
|
import { note } from "../cli/output.js";
|
|
13
|
-
import { atomicUsdc } from "../config.js";
|
|
14
12
|
import { createContext } from "../context.js";
|
|
13
|
+
import { approvePurchase, nextPurchaseApproval } from "./approval.js";
|
|
15
14
|
import { GatewayClient } from "../gateway/client.js";
|
|
16
|
-
import { authorizePayment, challengeIntentId, isAmbiguousPurchaseAnswer,
|
|
15
|
+
import { authorizePayment, challengeIntentId, isAmbiguousPurchaseAnswer, reconcileByIdentifier, recordIntent, requestChallenge, submitPayment, } from "../gateway/purchase.js";
|
|
17
16
|
import { updateOrder } from "../store/orders.js";
|
|
18
|
-
export async function runBuy(options) {
|
|
17
|
+
export async function runBuy(options, contextFactory = createContext) {
|
|
19
18
|
const request = readRequestFile(options.requestFile);
|
|
20
|
-
const context = await
|
|
19
|
+
const context = await contextFactory(options);
|
|
21
20
|
try {
|
|
22
21
|
if (options.payer && options.payer.toLowerCase() !== context.payerAddress.toLowerCase()) {
|
|
23
22
|
throw new CliError({
|
|
@@ -38,51 +37,40 @@ export async function runBuy(options) {
|
|
|
38
37
|
});
|
|
39
38
|
const amountAtomic = BigInt(challenge.requirement.amount);
|
|
40
39
|
const preflight = challenge.preflight;
|
|
40
|
+
const requestHash = canonicalHash({ method: "POST", resource: challenge.challenge.resource?.url,
|
|
41
|
+
providerAgentId: options.providerAgentId, outcomeId: options.outcomeId, body: request });
|
|
42
|
+
if (!challenge.binding || challenge.binding.canonicalRequestHash !== requestHash) {
|
|
43
|
+
throw new CliError({ code: "DASKI_REQUEST_BINDING_MISMATCH",
|
|
44
|
+
message: "The challenge does not match the requested purchase.",
|
|
45
|
+
remediation: "Obtain a challenge for the same provider, outcome, and request." });
|
|
46
|
+
}
|
|
47
|
+
const approval = nextPurchaseApproval({ gatewayUrl: context.profile.gatewayUrl,
|
|
48
|
+
payer: context.payerAddress, providerAgentId: options.providerAgentId, outcomeId: options.outcomeId,
|
|
49
|
+
requirement: challenge.requirement, binding: challenge.binding }, context.profileName);
|
|
50
|
+
const outcome = await context.catalog.getOutcome(options.providerAgentId, options.outcomeId);
|
|
51
|
+
const summary = approvalSummary(challenge.challenge.extensions, outcome, amountAtomic, preflight);
|
|
41
52
|
if (preflight?.sufficient === false) {
|
|
42
53
|
// The gateway already read the payer's balance: a signature now would be
|
|
43
54
|
// a doomed one. Say what is needed and stop; where the USDC comes from
|
|
44
55
|
// is the operator's business.
|
|
45
56
|
const network = String(preflight.network ?? context.profile.network);
|
|
46
57
|
const balance = typeof preflight.usdcBalance === "string"
|
|
47
|
-
?
|
|
58
|
+
? formatUsdc(BigInt(preflight.usdcBalance))
|
|
48
59
|
: "an unknown USDC balance";
|
|
49
60
|
throw new CliError({
|
|
50
61
|
code: "DASKI_INSUFFICIENT_USDC",
|
|
51
62
|
message: `${context.payerAddress} holds ${balance} on ${network}; this purchase ` +
|
|
52
63
|
`needs ${formatUsdc(amountAtomic)}.`,
|
|
53
64
|
remediation: `Fund ${context.payerAddress} with USDC on ${network}, then re-run. Nothing was signed.`,
|
|
54
|
-
details: { preflight, priceUsdc: formatUsdc(amountAtomic)
|
|
65
|
+
details: { preflight, priceUsdc: formatUsdc(amountAtomic), approval, approvalSummary: summary,
|
|
66
|
+
shortfallUsdc: typeof preflight.usdcBalance === "string"
|
|
67
|
+
? formatUsdc(amountAtomic > BigInt(preflight.usdcBalance) ? amountAtomic - BigInt(preflight.usdcBalance) : 0n) : null },
|
|
55
68
|
exitCode: 2,
|
|
56
69
|
});
|
|
57
70
|
}
|
|
58
71
|
// -- 2. approval -------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const threshold = atomicUsdc(context.profile.requireApprovalAboveUsdc);
|
|
62
|
-
if (amountAtomic > threshold && !options.yes) {
|
|
63
|
-
if (options.json || !isInteractive()) {
|
|
64
|
-
throw new CliError({
|
|
65
|
-
code: "DASKI_HUMAN_APPROVAL_REQUIRED",
|
|
66
|
-
message: `${formatUsdc(amountAtomic)} is above this profile's ` +
|
|
67
|
-
`requireApprovalAboveUsdc of ${context.profile.requireApprovalAboveUsdc} USDC, ` +
|
|
68
|
-
"and this session cannot ask a human.",
|
|
69
|
-
remediation: "Have a human run this command in an interactive terminal and confirm " +
|
|
70
|
-
"the prompt, or — if a human has already approved this exact purchase " +
|
|
71
|
-
"— re-run with --yes. Raising the threshold is a manual config edit.",
|
|
72
|
-
details: { approvalSummary: summary, priceUsdc: formatUsdc(amountAtomic) },
|
|
73
|
-
exitCode: 2,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
note(renderApproval(summary, amountAtomic));
|
|
77
|
-
if (!await confirm("Authorize this payment?")) {
|
|
78
|
-
throw new CliError({
|
|
79
|
-
code: "DASKI_PURCHASE_DECLINED",
|
|
80
|
-
message: "The purchase was not approved.",
|
|
81
|
-
remediation: "Nothing was signed and no money moved. Re-run when ready.",
|
|
82
|
-
exitCode: 2,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
72
|
+
await approvePurchase({ approval, approved: options.approved,
|
|
73
|
+
threshold: context.profile.requireApprovalAboveUsdc, json: options.json, summary });
|
|
86
74
|
// -- 3 & 4. validate, recompute, sign ----------------------------------
|
|
87
75
|
// The gateway bound its own payment identifier to the challenge; the
|
|
88
76
|
// submission must carry that one, so it is the ledger key as well.
|
|
@@ -95,6 +83,7 @@ export async function runBuy(options) {
|
|
|
95
83
|
outcomeId: options.outcomeId,
|
|
96
84
|
payer: context.payerAddress,
|
|
97
85
|
amount: amountAtomic.toString(),
|
|
86
|
+
approvalTermsHash: approval.termsHash,
|
|
98
87
|
state: "INTENT_RECORDED",
|
|
99
88
|
request,
|
|
100
89
|
});
|
|
@@ -127,7 +116,7 @@ export async function runBuy(options) {
|
|
|
127
116
|
// The signature left the process and the answer did not come back. This
|
|
128
117
|
// is the ambiguous case: reconcile, never re-sign.
|
|
129
118
|
return await reconcile(context, {
|
|
130
|
-
intentId,
|
|
119
|
+
intentId,
|
|
131
120
|
cause: `transport failure after submit: ${error.message}`,
|
|
132
121
|
});
|
|
133
122
|
}
|
|
@@ -135,7 +124,7 @@ export async function runBuy(options) {
|
|
|
135
124
|
const code = typeof body?.code === "string" ? body.code : undefined;
|
|
136
125
|
if (result.isError && isAmbiguousPurchaseAnswer(code, body)) {
|
|
137
126
|
return await reconcile(context, {
|
|
138
|
-
intentId,
|
|
127
|
+
intentId,
|
|
139
128
|
cause: `gateway reported ${code ?? "an error"} with the payment outcome unknown`,
|
|
140
129
|
});
|
|
141
130
|
}
|
|
@@ -144,7 +133,7 @@ export async function runBuy(options) {
|
|
|
144
133
|
// payload. The signature is out and the outcome is unknown: that is the
|
|
145
134
|
// ambiguous case, so reconcile — never report failure, never re-sign.
|
|
146
135
|
return await reconcile(context, {
|
|
147
|
-
intentId,
|
|
136
|
+
intentId,
|
|
148
137
|
cause: "the paid result carried no payload this CLI could read",
|
|
149
138
|
});
|
|
150
139
|
}
|
|
@@ -189,21 +178,18 @@ async function reconcile(context, args) {
|
|
|
189
178
|
updateOrder(args.intentId, { state: "PENDING_RECONCILIATION" });
|
|
190
179
|
note(`payment outcome unclear (${args.cause}); reconciling before any re-sign...`);
|
|
191
180
|
const record = updateOrder(args.intentId, {});
|
|
192
|
-
const outcome = await
|
|
181
|
+
const outcome = await reconcileByIdentifier({
|
|
193
182
|
client: context.client,
|
|
194
183
|
signer: context.signer,
|
|
195
|
-
|
|
184
|
+
intentId: args.intentId,
|
|
196
185
|
payer: context.payerAddress,
|
|
197
186
|
chainId: context.profile.chainId,
|
|
198
187
|
gatewayUrl: context.profile.gatewayUrl,
|
|
199
|
-
submission: args.authorized.submission,
|
|
200
|
-
request: args.request,
|
|
201
|
-
...(args.options.legacyArg === undefined ? {} : { legacyArg: args.options.legacyArg }),
|
|
202
188
|
});
|
|
203
189
|
if (outcome.status === "settled") {
|
|
204
190
|
const settled = updateOrder(args.intentId, {
|
|
205
191
|
handle: outcome.orderHandle,
|
|
206
|
-
state: normalizeState(outcome.
|
|
192
|
+
state: normalizeState(outcome.gatewayState),
|
|
207
193
|
});
|
|
208
194
|
return {
|
|
209
195
|
purchased: true,
|
|
@@ -217,7 +203,13 @@ async function reconcile(context, args) {
|
|
|
217
203
|
gatewayCalls: context.client.callCount,
|
|
218
204
|
};
|
|
219
205
|
}
|
|
220
|
-
|
|
206
|
+
if (outcome.status !== "absent") {
|
|
207
|
+
throw new CliError({ code: "DASKI_PAYMENT_PENDING_RECONCILIATION",
|
|
208
|
+
message: "The gateway is still resolving this payment.",
|
|
209
|
+
remediation: `Check again with daski order reconcile ${args.intentId}.`,
|
|
210
|
+
details: { intentId: args.intentId, status: outcome.status, reconciliation: outcome.evidence }, exitCode: 3 });
|
|
211
|
+
}
|
|
212
|
+
updateOrder(args.intentId, { state: "NOT_SETTLED" });
|
|
221
213
|
throw new CliError({
|
|
222
214
|
code: "DASKI_PAYMENT_UNRESOLVED_NO_ORDER",
|
|
223
215
|
message: `The purchase was interrupted (${args.cause}) and no order exists for it: ` +
|
|
@@ -248,7 +240,7 @@ function approvalSummary(extensions, outcome, amountAtomic, preflight) {
|
|
|
248
240
|
const supplied = preflight?.approvalSummary ?? extensions?.approvalSummary;
|
|
249
241
|
if (supplied && typeof supplied === "object")
|
|
250
242
|
return supplied;
|
|
251
|
-
const terms = extensions?.["daski-order-terms"];
|
|
243
|
+
const terms = extensions?.["daski-order-terms"] ?? outcome.terms;
|
|
252
244
|
return {
|
|
253
245
|
...(typeof supplied === "string" ? { gateway: supplied } : {}),
|
|
254
246
|
what: [outcome.serviceName, outcome.skillName].filter(Boolean).join(" / ") || "Daski outcome",
|
|
@@ -260,17 +252,6 @@ function approvalSummary(extensions, outcome, amountAtomic, preflight) {
|
|
|
260
252
|
marketplaceTerms: terms?.marketplaceTermsUrl ?? null,
|
|
261
253
|
};
|
|
262
254
|
}
|
|
263
|
-
function renderApproval(summary, amountAtomic) {
|
|
264
|
-
const lines = ["", " ─ Purchase approval ─────────────────────────────"];
|
|
265
|
-
for (const [key, value] of Object.entries(summary)) {
|
|
266
|
-
if (value === null || value === undefined)
|
|
267
|
-
continue;
|
|
268
|
-
lines.push(` ${key.padEnd(18)} ${String(value)}`);
|
|
269
|
-
}
|
|
270
|
-
lines.push(` ${"you will pay".padEnd(18)} ${formatUsdc(amountAtomic)}`);
|
|
271
|
-
lines.push(" ─────────────────────────────────────────────────", "");
|
|
272
|
-
return lines.join("\n");
|
|
273
|
-
}
|
|
274
255
|
function readRequestFile(path) {
|
|
275
256
|
let raw;
|
|
276
257
|
try {
|