@daski/pay 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +18 -11
  2. package/dist/bin/daski.js +26 -11
  3. package/dist/bin/daski.js.map +1 -1
  4. package/dist/chain/reader.d.ts +92 -0
  5. package/dist/chain/reader.d.ts.map +1 -0
  6. package/dist/chain/reader.js +130 -0
  7. package/dist/chain/reader.js.map +1 -0
  8. package/dist/commands/approval.js +2 -2
  9. package/dist/commands/approval.js.map +1 -1
  10. package/dist/commands/buy.d.ts +0 -1
  11. package/dist/commands/buy.d.ts.map +1 -1
  12. package/dist/commands/buy.js +15 -22
  13. package/dist/commands/buy.js.map +1 -1
  14. package/dist/commands/confirmation.d.ts +322 -4
  15. package/dist/commands/confirmation.d.ts.map +1 -1
  16. package/dist/commands/confirmation.js +771 -60
  17. package/dist/commands/confirmation.js.map +1 -1
  18. package/dist/commands/doctor.d.ts +20 -0
  19. package/dist/commands/doctor.d.ts.map +1 -1
  20. package/dist/commands/doctor.js +204 -59
  21. package/dist/commands/doctor.js.map +1 -1
  22. package/dist/commands/order.d.ts +37 -6
  23. package/dist/commands/order.d.ts.map +1 -1
  24. package/dist/commands/order.js +181 -103
  25. package/dist/commands/order.js.map +1 -1
  26. package/dist/commands/signPayment.d.ts.map +1 -1
  27. package/dist/commands/signPayment.js +8 -3
  28. package/dist/commands/signPayment.js.map +1 -1
  29. package/dist/commands/wallet.d.ts +3 -0
  30. package/dist/commands/wallet.d.ts.map +1 -1
  31. package/dist/commands/wallet.js +72 -16
  32. package/dist/commands/wallet.js.map +1 -1
  33. package/dist/config.d.ts +10 -2
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/config.js +41 -1
  36. package/dist/config.js.map +1 -1
  37. package/dist/context.d.ts +28 -3
  38. package/dist/context.d.ts.map +1 -1
  39. package/dist/context.js +72 -3
  40. package/dist/context.js.map +1 -1
  41. package/dist/gateway/client.d.ts +20 -17
  42. package/dist/gateway/client.d.ts.map +1 -1
  43. package/dist/gateway/client.js +62 -43
  44. package/dist/gateway/client.js.map +1 -1
  45. package/dist/gateway/lifecycle.d.ts +4 -1
  46. package/dist/gateway/lifecycle.d.ts.map +1 -1
  47. package/dist/gateway/lifecycle.js +10 -5
  48. package/dist/gateway/lifecycle.js.map +1 -1
  49. package/dist/gateway/metadata.d.ts +66 -0
  50. package/dist/gateway/metadata.d.ts.map +1 -0
  51. package/dist/gateway/metadata.js +152 -0
  52. package/dist/gateway/metadata.js.map +1 -0
  53. package/dist/gateway/purchase.d.ts +51 -67
  54. package/dist/gateway/purchase.d.ts.map +1 -1
  55. package/dist/gateway/purchase.js +153 -125
  56. package/dist/gateway/purchase.js.map +1 -1
  57. package/dist/host.d.ts +41 -0
  58. package/dist/host.d.ts.map +1 -0
  59. package/dist/host.js +112 -0
  60. package/dist/host.js.map +1 -0
  61. package/dist/index.d.ts +12 -6
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +10 -4
  64. package/dist/index.js.map +1 -1
  65. package/dist/signers/circleAgent.d.ts +37 -0
  66. package/dist/signers/circleAgent.d.ts.map +1 -0
  67. package/dist/signers/circleAgent.js +244 -0
  68. package/dist/signers/circleAgent.js.map +1 -0
  69. package/dist/signers/contract.d.ts +34 -0
  70. package/dist/signers/contract.d.ts.map +1 -0
  71. package/dist/signers/contract.js +47 -0
  72. package/dist/signers/contract.js.map +1 -0
  73. package/dist/signers/index.d.ts +16 -7
  74. package/dist/signers/index.d.ts.map +1 -1
  75. package/dist/signers/index.js +7 -3
  76. package/dist/signers/index.js.map +1 -1
  77. package/dist/signers/local.d.ts +5 -1
  78. package/dist/signers/local.d.ts.map +1 -1
  79. package/dist/signers/local.js +16 -3
  80. package/dist/signers/local.js.map +1 -1
  81. package/dist/signers/selfTest.d.ts +32 -12
  82. package/dist/signers/selfTest.d.ts.map +1 -1
  83. package/dist/signers/selfTest.js +85 -12
  84. package/dist/signers/selfTest.js.map +1 -1
  85. package/dist/signers/signature.d.ts +10 -0
  86. package/dist/signers/signature.d.ts.map +1 -0
  87. package/dist/signers/signature.js +11 -0
  88. package/dist/signers/signature.js.map +1 -0
  89. package/dist/store/keystore.d.ts +48 -6
  90. package/dist/store/keystore.d.ts.map +1 -1
  91. package/dist/store/keystore.js +414 -110
  92. package/dist/store/keystore.js.map +1 -1
  93. package/dist/store/lock.d.ts +59 -0
  94. package/dist/store/lock.d.ts.map +1 -0
  95. package/dist/store/lock.js +269 -0
  96. package/dist/store/lock.js.map +1 -0
  97. package/dist/store/orders.d.ts +49 -9
  98. package/dist/store/orders.d.ts.map +1 -1
  99. package/dist/store/orders.js +173 -37
  100. package/dist/store/orders.js.map +1 -1
  101. package/package.json +2 -2
@@ -1,58 +1,51 @@
1
+ /** Purchase authorization, submission, and exact-identifier reconciliation. */
1
2
  import { type OrderBinding, type PolicyConfig, type SignerAdapter } from "@daski/x402-scheme";
2
3
  import { type Address } from "viem";
3
4
  import { CliError } from "../cli/errors.js";
4
- import { updateOrder, type OrderRecord } from "../store/orders.js";
5
+ import { updateOrder, type OrderRecord, type OrderState } from "../store/orders.js";
5
6
  import { GatewayClient, type McpToolResult, type PaymentChallenge, type PaymentRequirement, type PaymentSubmission } from "./client.js";
6
- /** Prepares pricing and a draft without payment. Older gateways use an unpaid buy call. */
7
+ /** Prepares pricing and a draft without payment; the only source of a challenge. */
7
8
  export declare const CHALLENGE_TOOL = "daski_get_payment_challenge";
8
9
  export declare const BUY_TOOL = "daski_buy_outcome";
9
10
  export interface ChallengeResult {
10
11
  challenge: PaymentChallenge;
11
12
  requirement: PaymentRequirement;
12
13
  binding: OrderBinding | undefined;
13
- /** True when the spec-01 challenge tool served this. */
14
- viaChallengeTool: boolean;
15
14
  /** The prepare tool's balance and eligibility preflight, when it served one. */
16
15
  preflight?: Record<string, unknown> | undefined;
17
16
  }
18
- /** A fresh reconciliation key. Also the idempotency key the gateway echoes. */
19
- export declare function newIntentId(): string;
20
17
  /** The identifier the gateway pinned in a challenge's `payment-identifier` extension, if any. */
21
18
  export declare function issuedPaymentIdentifier(extensions: Record<string, unknown> | undefined): string | undefined;
22
19
  /**
23
- * The payment identifier a submission must carry. The gateway looks a paid
24
- * submission up by this identifier, so it has to be the one the gateway bound
25
- * to the challenge: when we proposed one at challenge time (`buy`), the
26
- * gateway echoes it; when the challenge came from elsewhere (`sign-payment`
27
- * on a challenge the agent obtained itself), the gateway's own identifier is
28
- * the only one that exists server-side. 0.1.1 minted a fresh identifier in
29
- * that second case and every such submission was refused with
30
- * `PAYMENT_IDENTIFIER_CONFLICT` before settlement (2026-09-03).
20
+ * The payment identifier a submission must carry: the one the gateway bound
21
+ * to the challenge, since the gateway looks a paid submission up by it and
22
+ * nothing else exists server-side. A challenge that carries none is refused,
23
+ * never given a minted identifier (0.1.1 minted one and every such
24
+ * submission was refused with `PAYMENT_IDENTIFIER_CONFLICT` before
25
+ * settlement, 2026-09-03); a challenge bound to a different identifier than
26
+ * the one a purchase recorded is refused too.
31
27
  */
32
- export declare function resolvePaymentIdentifier(extensions: Record<string, unknown> | undefined, proposed: string | undefined): string | undefined;
28
+ export declare function resolvePaymentIdentifier(extensions: Record<string, unknown> | undefined, proposed: string | undefined): string;
33
29
  /**
34
30
  * The reconciliation key for a purchase: the identifier the gateway bound to
35
31
  * the challenge, which is also what the submission must carry, so the local
36
- * ledger and the gateway's `daski_list_my_orders` filter agree. A fresh one
37
- * is minted only for a challenge that carries none. `buy` in 0.1.2 minted a
38
- * fresh identifier after the challenge and then refused its own challenge with
39
- * DASKI_PAYMENT_IDENTIFIER_MISMATCH: the gateway never accepted a proposal, so
40
- * there is nothing to propose (2026-09-04).
32
+ * ledger and the gateway's `daski_list_my_orders` filter agree. The gateway
33
+ * never accepted a proposal, so there is nothing to propose; a challenge
34
+ * without an identifier is refused.
41
35
  */
42
36
  export declare function challengeIntentId(extensions: Record<string, unknown> | undefined): string;
43
37
  /**
44
38
  * Whether a paid submission's error answer leaves the payment outcome unknown.
45
- * The gateway says so itself with `paymentMayHaveSettled`; the code list is
46
- * the fallback for gateways that predate the flag. A code-only list missed
47
- * PAYMENT_IDENTIFIER_CONFLICT (flagged settled-maybe) on 2026-09-04, so the
48
- * CLI marked the intent PENDING_RECONCILIATION without reconciling, and the
49
- * operator's agent re-signed on the strength of a local ledger message.
39
+ * Only the gateway's own `paymentMayHaveSettled: false` says nothing settled;
40
+ * `true`, a refusal without the flag, and a body this CLI cannot read are all
41
+ * ambiguous, so the identifier is reconciled rather than signed for again. On
42
+ * 2026-09-04 a code list missed PAYMENT_IDENTIFIER_CONFLICT and an agent
43
+ * re-signed on the strength of a local ledger message.
50
44
  */
51
- export declare function isAmbiguousPurchaseAnswer(code: string | undefined, body: Record<string, unknown> | undefined): boolean;
45
+ export declare function isAmbiguousPurchaseAnswer(body: Record<string, unknown> | undefined): boolean;
52
46
  /**
53
- * Step 1: obtain a payment challenge. Prefers `daski_get_payment_challenge`
54
- * and falls back to an unpaid `daski_buy_outcome`, which is the same challenge
55
- * by a longer road.
47
+ * Step 1: obtain a payment challenge from `daski_get_payment_challenge`, the
48
+ * only tool that issues one. A gateway without it is unsupported.
56
49
  */
57
50
  export declare function requestChallenge(options: {
58
51
  client: GatewayClient;
@@ -88,50 +81,34 @@ export interface SubmitOptions {
88
81
  outcomeId: string;
89
82
  request: Record<string, unknown>;
90
83
  submission: PaymentSubmission;
91
- /** Pass the payload as a tool argument instead of `_meta`. */
92
- legacyArg?: boolean;
93
84
  timeoutMs?: number;
94
85
  }
95
- /**
96
- * Step 5: the paid retry. The payload rides in `_meta["x402/payment"]` unless
97
- * `--legacy-arg` asks for the argument form.
98
- */
86
+ /** Step 5: the paid retry. The payload rides in `_meta["x402/payment"]`. */
99
87
  export declare function submitPayment(options: SubmitOptions): Promise<McpToolResult>;
100
- export interface ReconcileOptions {
101
- client: GatewayClient;
102
- signer: SignerAdapter;
103
- record: OrderRecord;
104
- payer: Address;
105
- chainId: number;
106
- gatewayUrl: string;
107
- submission: PaymentSubmission;
108
- request: Record<string, unknown>;
109
- legacyArg?: boolean;
110
- }
111
- export interface ReconcileOutcome {
112
- /** `settled` when an order exists; `absent` when provably none does. */
113
- status: "settled" | "absent";
114
- orderHandle?: string | undefined;
115
- body?: Record<string, unknown> | undefined;
116
- /** How the conclusion was reached, for the audit log. */
117
- evidence: string;
118
- }
119
- /** Compatibility wrapper around exact-identifier reconciliation. */
120
- export declare function reconcileAmbiguousPurchase(options: ReconcileOptions): Promise<ReconcileOutcome>;
121
- interface PayerOrderRow {
88
+ /** One row of the payer's order history, as `daski_list_my_orders` states it. */
89
+ export interface PayerOrderRow {
122
90
  orderHandle: string;
91
+ /** The gateway's payment identifier: the buyer's ledger key. */
92
+ paymentIdentifier: string;
123
93
  providerAgentId: string;
124
94
  outcomeId: string;
95
+ /** Atomic USDC. */
125
96
  grossAmount: string;
97
+ /** One of the gateway's order states. */
126
98
  state: string;
127
- createdAt: string;
128
- /** Present once the gateway echoes the buyer's idempotency key. */
129
- paymentIdentifier?: string;
99
+ createdAt?: string | undefined;
130
100
  }
131
101
  /**
132
- * The payer's order history. When the gateway echoes `paymentIdentifier` on a
133
- * row, we filter by it exactly as §2 specifies; until then the rows carry no
134
- * such field and the caller matches on the intent's other invariants.
102
+ * The rows of a `daski_list_my_orders` answer. A row this CLI cannot read is
103
+ * an error, never a guess: a missing identifier used to be matched on other
104
+ * invariants and a missing amount recorded as zero, both of which put
105
+ * something in the ledger the gateway never said.
106
+ */
107
+ export declare function readPayerOrderRows(body: Record<string, unknown>): PayerOrderRow[];
108
+ /**
109
+ * The payer's order history, filtered by the payment identifier when one is
110
+ * given: the gateway filters server-side, and the rows are narrowed again
111
+ * here so a row for another identifier can never be read as this one's.
135
112
  */
136
113
  export declare function listPayerOrders(options: {
137
114
  client: GatewayClient;
@@ -144,10 +121,18 @@ export declare function listPayerOrders(options: {
144
121
  /** What a gateway order state says about the money. */
145
122
  export type SettlementReading = "not_settled" | "in_flight" | "ambiguous" | "settled";
146
123
  /**
147
- * The gateway's order states, read for one question: did the authorization
148
- * settle? Unknown states are treated as ambiguous, never as absent.
124
+ * The money question. An unknown state is ambiguous, never absent: nothing
125
+ * is signed again on the strength of a state this CLI does not understand.
149
126
  */
150
127
  export declare function readSettlement(state: string): SettlementReading;
128
+ /** A gateway order state this release does not understand; the ledger records nothing for it. */
129
+ export declare function orderStateUnreadable(state: unknown, orderHandle?: string | undefined): CliError;
130
+ /**
131
+ * The ledger question: the local state a gateway order state records as. An
132
+ * unknown state is refused, never recorded as SUBMITTED; the caller decides
133
+ * what else it has already learned (a handle, a receipt) and keeps that.
134
+ */
135
+ export declare function localOrderState(state: unknown, orderHandle?: string | undefined): OrderState;
151
136
  export interface IdentifierReconciliation {
152
137
  status: "settled" | "in_flight" | "ambiguous" | "absent";
153
138
  orderHandle?: string | undefined;
@@ -159,8 +144,7 @@ export interface IdentifierReconciliation {
159
144
  * but the read authorization: no replay, no balance reading, no local
160
145
  * ledger. This is the lookup buy.md names for every ambiguous outcome and
161
146
  * for PAYMENT_IDENTIFIER_CONFLICT. An identifier the gateway never issued
162
- * (a `daski-` one from 0.1.0/0.1.1) matches nothing, which is the truth:
163
- * nothing could have settled under it.
147
+ * matches nothing, which is the truth: nothing could have settled under it.
164
148
  */
165
149
  export declare function reconcileIdentifierRows(intentId: string, rows: readonly PayerOrderRow[]): IdentifierReconciliation;
166
150
  export declare function reconcileByIdentifier(options: {
@@ -1 +1 @@
1
- {"version":3,"file":"purchase.d.ts","sourceRoot":"","sources":["../../src/gateway/purchase.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"purchase.d.ts","sourceRoot":"","sources":["../../src/gateway/purchase.ts"],"names":[],"mappings":"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"}
@@ -1,17 +1,23 @@
1
1
  /** Purchase authorization, submission, and exact-identifier reconciliation. */
2
- import { randomBytes } from "node:crypto";
3
2
  import { bindingFromExtensions, formatUsdc, paymentEchoExtensions, transferWithAuthorizationTypedData, validatePurchaseAuthorization, withPaymentIdentifier, } from "@daski/x402-scheme";
4
3
  import { getAddress } from "viem";
5
4
  import { CliError } from "../cli/errors.js";
6
5
  import { findByIntent, updateOrder, upsertOrder, } from "../store/orders.js";
7
- import { describeResult, GatewayClient, unreadableResultError, } from "./client.js";
6
+ import { describeResult, GatewayClient, gatewayRefusalRemediation, gatewayUnsupported, isRetryableGatewayCode, unreadableResultError, } from "./client.js";
8
7
  import { callWalletQuery } from "./lifecycle.js";
9
- /** Prepares pricing and a draft without payment. Older gateways use an unpaid buy call. */
8
+ /** Prepares pricing and a draft without payment; the only source of a challenge. */
10
9
  export const CHALLENGE_TOOL = "daski_get_payment_challenge";
11
10
  export const BUY_TOOL = "daski_buy_outcome";
12
- /** A fresh reconciliation key. Also the idempotency key the gateway echoes. */
13
- export function newIntentId() {
14
- return `daski-${randomBytes(16).toString("hex")}`;
11
+ /** A challenge without the gateway's identifier cannot be paid: the gateway looks a submission up by it. */
12
+ function paymentIdentifierMissing() {
13
+ return new CliError({
14
+ code: "DASKI_PAYMENT_IDENTIFIER_MISSING",
15
+ message: "The challenge carries no payment-identifier extension, so there is no identifier the " +
16
+ "gateway would look the paid submission up by.",
17
+ remediation: "Do not sign: a submission under an identifier the gateway did not issue is refused before " +
18
+ "settlement. Request a fresh challenge from daski_get_payment_challenge; every challenge a " +
19
+ "supported gateway issues carries payment-identifier.info.id.",
20
+ });
15
21
  }
16
22
  /** The identifier the gateway pinned in a challenge's `payment-identifier` extension, if any. */
17
23
  export function issuedPaymentIdentifier(extensions) {
@@ -20,18 +26,19 @@ export function issuedPaymentIdentifier(extensions) {
20
26
  return typeof id === "string" && id.length > 0 ? id : undefined;
21
27
  }
22
28
  /**
23
- * The payment identifier a submission must carry. The gateway looks a paid
24
- * submission up by this identifier, so it has to be the one the gateway bound
25
- * to the challenge: when we proposed one at challenge time (`buy`), the
26
- * gateway echoes it; when the challenge came from elsewhere (`sign-payment`
27
- * on a challenge the agent obtained itself), the gateway's own identifier is
28
- * the only one that exists server-side. 0.1.1 minted a fresh identifier in
29
- * that second case and every such submission was refused with
30
- * `PAYMENT_IDENTIFIER_CONFLICT` before settlement (2026-09-03).
29
+ * The payment identifier a submission must carry: the one the gateway bound
30
+ * to the challenge, since the gateway looks a paid submission up by it and
31
+ * nothing else exists server-side. A challenge that carries none is refused,
32
+ * never given a minted identifier (0.1.1 minted one and every such
33
+ * submission was refused with `PAYMENT_IDENTIFIER_CONFLICT` before
34
+ * settlement, 2026-09-03); a challenge bound to a different identifier than
35
+ * the one a purchase recorded is refused too.
31
36
  */
32
37
  export function resolvePaymentIdentifier(extensions, proposed) {
33
38
  const issued = issuedPaymentIdentifier(extensions);
34
- if (issued !== undefined && proposed !== undefined && issued !== proposed) {
39
+ if (issued === undefined)
40
+ throw paymentIdentifierMissing();
41
+ if (proposed !== undefined && issued !== proposed) {
35
42
  throw new CliError({
36
43
  code: "DASKI_PAYMENT_IDENTIFIER_MISMATCH",
37
44
  message: `The gateway bound this challenge to payment identifier ${issued}, not to ` +
@@ -40,60 +47,44 @@ export function resolvePaymentIdentifier(extensions, proposed) {
40
47
  "is refused by the gateway. Request a fresh challenge and retry.",
41
48
  });
42
49
  }
43
- return issued ?? proposed;
50
+ return issued;
44
51
  }
45
52
  /**
46
53
  * The reconciliation key for a purchase: the identifier the gateway bound to
47
54
  * the challenge, which is also what the submission must carry, so the local
48
- * ledger and the gateway's `daski_list_my_orders` filter agree. A fresh one
49
- * is minted only for a challenge that carries none. `buy` in 0.1.2 minted a
50
- * fresh identifier after the challenge and then refused its own challenge with
51
- * DASKI_PAYMENT_IDENTIFIER_MISMATCH: the gateway never accepted a proposal, so
52
- * there is nothing to propose (2026-09-04).
55
+ * ledger and the gateway's `daski_list_my_orders` filter agree. The gateway
56
+ * never accepted a proposal, so there is nothing to propose; a challenge
57
+ * without an identifier is refused.
53
58
  */
54
59
  export function challengeIntentId(extensions) {
55
- return issuedPaymentIdentifier(extensions) ?? newIntentId();
60
+ return resolvePaymentIdentifier(extensions, undefined);
56
61
  }
57
- /** Gateway codes that mean "authorized, outcome unknown" on older gateways. */
58
- const AMBIGUOUS_CODES = new Set([
59
- "PAYMENT_PENDING_RECONCILIATION",
60
- "PAYMENT_OUTCOME_PENDING",
61
- ]);
62
62
  /**
63
63
  * Whether a paid submission's error answer leaves the payment outcome unknown.
64
- * The gateway says so itself with `paymentMayHaveSettled`; the code list is
65
- * the fallback for gateways that predate the flag. A code-only list missed
66
- * PAYMENT_IDENTIFIER_CONFLICT (flagged settled-maybe) on 2026-09-04, so the
67
- * CLI marked the intent PENDING_RECONCILIATION without reconciling, and the
68
- * operator's agent re-signed on the strength of a local ledger message.
64
+ * Only the gateway's own `paymentMayHaveSettled: false` says nothing settled;
65
+ * `true`, a refusal without the flag, and a body this CLI cannot read are all
66
+ * ambiguous, so the identifier is reconciled rather than signed for again. On
67
+ * 2026-09-04 a code list missed PAYMENT_IDENTIFIER_CONFLICT and an agent
68
+ * re-signed on the strength of a local ledger message.
69
69
  */
70
- export function isAmbiguousPurchaseAnswer(code, body) {
71
- if (body?.paymentMayHaveSettled === true)
72
- return true;
73
- if (body?.paymentMayHaveSettled === false)
74
- return false;
75
- return code !== undefined && AMBIGUOUS_CODES.has(code);
70
+ export function isAmbiguousPurchaseAnswer(body) {
71
+ return body?.paymentMayHaveSettled !== false;
76
72
  }
77
73
  /**
78
- * Step 1: obtain a payment challenge. Prefers `daski_get_payment_challenge`
79
- * and falls back to an unpaid `daski_buy_outcome`, which is the same challenge
80
- * by a longer road.
74
+ * Step 1: obtain a payment challenge from `daski_get_payment_challenge`, the
75
+ * only tool that issues one. A gateway without it is unsupported.
81
76
  */
82
77
  export async function requestChallenge(options) {
83
78
  const { client, providerAgentId, outcomeId, request, payerAddress } = options;
84
- const viaChallengeTool = await client.hasTool(CHALLENGE_TOOL);
85
- const result = viaChallengeTool
86
- ? await client.callTool(CHALLENGE_TOOL, {
87
- providerAgentId, outcomeId, request, payerAddress,
88
- })
89
- : await client.callTool(BUY_TOOL, { providerAgentId, outcomeId, request });
79
+ if (!(await client.hasTool(CHALLENGE_TOOL)))
80
+ throw gatewayUnsupported(client.gatewayUrl, CHALLENGE_TOOL);
81
+ const result = await client.callTool(CHALLENGE_TOOL, { providerAgentId, outcomeId, request, payerAddress });
90
82
  const challenge = GatewayClient.challenge(result);
91
83
  if (!challenge) {
92
84
  // A success answer with no payload this client can read is a protocol
93
85
  // mismatch, not a refusal; say so instead of "the gateway rejected it".
94
- if (GatewayClient.unreadable(result)) {
95
- throw unreadableResultError(viaChallengeTool ? CHALLENGE_TOOL : BUY_TOOL, result);
96
- }
86
+ if (GatewayClient.unreadable(result))
87
+ throw unreadableResultError(CHALLENGE_TOOL, result);
97
88
  throw purchaseFailure(result);
98
89
  }
99
90
  const requirement = challenge.accepts[0];
@@ -108,7 +99,6 @@ export async function requestChallenge(options) {
108
99
  challenge,
109
100
  requirement,
110
101
  binding: bindingFromExtensions(challenge.extensions),
111
- viaChallengeTool,
112
102
  preflight: GatewayClient.preflight(result),
113
103
  };
114
104
  }
@@ -184,40 +174,62 @@ export async function authorizePayment(options) {
184
174
  nonce: validated.recomputedNonce,
185
175
  };
186
176
  }
187
- /**
188
- * Step 5: the paid retry. The payload rides in `_meta["x402/payment"]` unless
189
- * `--legacy-arg` asks for the argument form.
190
- */
177
+ /** Step 5: the paid retry. The payload rides in `_meta["x402/payment"]`. */
191
178
  export async function submitPayment(options) {
192
- const args = {
179
+ return options.client.callTool(BUY_TOOL, {
193
180
  providerAgentId: options.providerAgentId,
194
181
  outcomeId: options.outcomeId,
195
182
  request: options.request,
196
- };
197
- if (options.legacyArg) {
198
- args.paymentPayload = options.submission;
199
- return options.client.callTool(BUY_TOOL, args);
200
- }
201
- return options.client.callTool(BUY_TOOL, args, {
183
+ }, {
202
184
  "x402/payment": options.submission,
203
185
  });
204
186
  }
205
- /** Compatibility wrapper around exact-identifier reconciliation. */
206
- export async function reconcileAmbiguousPurchase(options) {
207
- const outcome = await reconcileByIdentifier({ ...options, intentId: options.record.intentId });
208
- if (outcome.status === "in_flight" || outcome.status === "ambiguous") {
209
- throw new CliError({ code: "DASKI_PAYMENT_PENDING_RECONCILIATION",
210
- message: "The gateway is still resolving this payment.",
211
- remediation: `Check again with daski order reconcile ${options.record.intentId}.`,
212
- details: { intentId: options.record.intentId, evidence: outcome.evidence } });
213
- }
214
- return { status: outcome.status, orderHandle: outcome.orderHandle,
215
- body: { status: outcome.gatewayState }, evidence: outcome.evidence };
187
+ function orderHistoryUnreadable(reason) {
188
+ return new CliError({
189
+ code: "DASKI_ORDER_HISTORY_UNREADABLE",
190
+ message: `The gateway's order history cannot be read: ${reason}.`,
191
+ remediation: "Nothing was signed and nothing was recorded from the unreadable answer. Run daski doctor " +
192
+ "--json to check the gateway pin, then retry; a gateway whose history rows lack " +
193
+ "paymentIdentifier or a decimal grossAmount is not one this release supports.",
194
+ });
195
+ }
196
+ /**
197
+ * The rows of a `daski_list_my_orders` answer. A row this CLI cannot read is
198
+ * an error, never a guess: a missing identifier used to be matched on other
199
+ * invariants and a missing amount recorded as zero, both of which put
200
+ * something in the ledger the gateway never said.
201
+ */
202
+ export function readPayerOrderRows(body) {
203
+ if (!Array.isArray(body.orders))
204
+ throw orderHistoryUnreadable("the answer carries no orders array");
205
+ return body.orders.map((value, index) => {
206
+ if (!value || typeof value !== "object" || Array.isArray(value))
207
+ throw orderHistoryUnreadable(`row ${index} is not an object`);
208
+ const row = value;
209
+ const text = (field) => {
210
+ const candidate = row[field];
211
+ if (typeof candidate !== "string" || candidate.length === 0)
212
+ throw orderHistoryUnreadable(`row ${index} has no ${field}`);
213
+ return candidate;
214
+ };
215
+ const grossAmount = row.grossAmount;
216
+ if (typeof grossAmount !== "string" || !/^\d+$/.test(grossAmount))
217
+ throw orderHistoryUnreadable(`row ${index} has no decimal grossAmount`);
218
+ return {
219
+ orderHandle: text("orderHandle"),
220
+ paymentIdentifier: text("paymentIdentifier"),
221
+ providerAgentId: text("providerAgentId"),
222
+ outcomeId: text("outcomeId"),
223
+ grossAmount,
224
+ state: text("state"),
225
+ createdAt: typeof row.createdAt === "string" ? row.createdAt : undefined,
226
+ };
227
+ });
216
228
  }
217
229
  /**
218
- * The payer's order history. When the gateway echoes `paymentIdentifier` on a
219
- * row, we filter by it exactly as §2 specifies; until then the rows carry no
220
- * such field and the caller matches on the intent's other invariants.
230
+ * The payer's order history, filtered by the payment identifier when one is
231
+ * given: the gateway filters server-side, and the rows are narrowed again
232
+ * here so a row for another identifier can never be read as this one's.
221
233
  */
222
234
  export async function listPayerOrders(options) {
223
235
  const body = await callWalletQuery({
@@ -236,66 +248,78 @@ export async function listPayerOrders(options) {
236
248
  chainId: options.chainId,
237
249
  gatewayUrl: options.gatewayUrl,
238
250
  });
239
- if (!Array.isArray(body.orders))
240
- throw new CliError({ code: "DASKI_ORDER_HISTORY_UNREADABLE",
241
- message: "The gateway returned no readable order history.",
242
- remediation: "Retry order reconcile with the recorded intent identifier." });
243
- const rows = body.orders;
244
- if (!options.intentId)
245
- return rows;
246
- const filtered = rows.filter((row) => row.paymentIdentifier === options.intentId);
247
- // Only narrow when the gateway actually echoes the key; an empty result from
248
- // a field that does not exist is not evidence of absence.
249
- return filtered.length > 0 || rows.some((row) => row.paymentIdentifier !== undefined)
250
- ? filtered
251
- : rows;
251
+ const rows = readPayerOrderRows(body);
252
+ return options.intentId ? rows.filter((row) => row.paymentIdentifier === options.intentId) : rows;
252
253
  }
253
254
  /**
254
- * The gateway's order states, read for one question: did the authorization
255
- * settle? Unknown states are treated as ambiguous, never as absent.
255
+ * The gateway's closed order states, read for two questions: did the
256
+ * authorization settle, and what does the local ledger record. One table
257
+ * answers both, so a state cannot be settled for the budget and unknown for
258
+ * the record.
259
+ */
260
+ const GATEWAY_ORDER_STATES = {
261
+ DRAFT: { settlement: "not_settled", local: "NOT_SETTLED" },
262
+ CHALLENGE_ISSUED: { settlement: "not_settled", local: "NOT_SETTLED" },
263
+ VERIFY_REJECTED: { settlement: "not_settled", local: "NOT_SETTLED" },
264
+ SETTLEMENT_FAILED: { settlement: "not_settled", local: "NOT_SETTLED" },
265
+ NOT_SETTLED: { settlement: "not_settled", local: "NOT_SETTLED" },
266
+ ATTEMPT_OPENED: { settlement: "in_flight", local: "PENDING_RECONCILIATION" },
267
+ VERIFIED: { settlement: "in_flight", local: "PENDING_RECONCILIATION" },
268
+ SETTLE_INVOKED: { settlement: "in_flight", local: "PENDING_RECONCILIATION" },
269
+ SETTLEMENT_AMBIGUOUS: { settlement: "ambiguous", local: "PENDING_RECONCILIATION" },
270
+ EXTERNAL_OR_UNPROVEN_DEPOSIT: { settlement: "ambiguous", local: "PENDING_RECONCILIATION" },
271
+ // Dispatch ambiguity is not payment ambiguity: the order exists and was paid.
272
+ DISPATCH_AMBIGUOUS: { settlement: "ambiguous", local: "SUBMITTED" },
273
+ FACILITATOR_CONFIRMED: { settlement: "settled", local: "SUBMITTED" },
274
+ DEPOSIT_FINAL: { settlement: "settled", local: "SUBMITTED" },
275
+ RELEASE_FINAL: { settlement: "settled", local: "SUBMITTED" },
276
+ DISPATCH_STARTED: { settlement: "settled", local: "SUBMITTED" },
277
+ DISPATCHED: { settlement: "settled", local: "SUBMITTED" },
278
+ LEGAL_HOLD: { settlement: "settled", local: "SUBMITTED" },
279
+ FULFILLED: { settlement: "settled", local: "FULFILLED" },
280
+ PROVIDER_FAILED: { settlement: "settled", local: "PROVIDER_FAILED" },
281
+ INPUT_REQUIRED: { settlement: "settled", local: "INPUT_REQUIRED" },
282
+ };
283
+ /**
284
+ * The money question. An unknown state is ambiguous, never absent: nothing
285
+ * is signed again on the strength of a state this CLI does not understand.
256
286
  */
257
287
  export function readSettlement(state) {
258
- switch (state) {
259
- case "DRAFT":
260
- case "CHALLENGE_ISSUED":
261
- case "VERIFY_REJECTED":
262
- case "SETTLEMENT_FAILED":
263
- case "NOT_SETTLED":
264
- return "not_settled";
265
- case "ATTEMPT_OPENED":
266
- case "VERIFIED":
267
- case "SETTLE_INVOKED":
268
- return "in_flight";
269
- case "SETTLEMENT_AMBIGUOUS":
270
- case "EXTERNAL_OR_UNPROVEN_DEPOSIT":
271
- case "DISPATCH_AMBIGUOUS":
272
- return "ambiguous";
273
- case "FACILITATOR_CONFIRMED":
274
- case "DEPOSIT_FINAL":
275
- case "RELEASE_FINAL":
276
- case "DISPATCH_STARTED":
277
- case "DISPATCHED":
278
- case "FULFILLED":
279
- case "PROVIDER_FAILED":
280
- case "INPUT_REQUIRED":
281
- case "LEGAL_HOLD":
282
- return "settled";
283
- default:
284
- return "ambiguous";
285
- }
288
+ return GATEWAY_ORDER_STATES[state]?.settlement ?? "ambiguous";
289
+ }
290
+ /** A gateway order state this release does not understand; the ledger records nothing for it. */
291
+ export function orderStateUnreadable(state, orderHandle) {
292
+ return new CliError({
293
+ code: "DASKI_ORDER_STATE_UNREADABLE",
294
+ message: `The gateway reports order state ${JSON.stringify(state)}, which this release does not understand.`,
295
+ remediation: (orderHandle ? `The order exists as ${orderHandle} and the local record keeps its previous state. ` : "") +
296
+ "Upgrade to the @daski/pay release the gateway pins (daski doctor --json shows the pin), then " +
297
+ `run daski order status ${orderHandle ?? "<handle>"}.`,
298
+ details: { gatewayState: state, ...(orderHandle ? { orderHandle } : {}) },
299
+ });
300
+ }
301
+ /**
302
+ * The ledger question: the local state a gateway order state records as. An
303
+ * unknown state is refused, never recorded as SUBMITTED; the caller decides
304
+ * what else it has already learned (a handle, a receipt) and keeps that.
305
+ */
306
+ export function localOrderState(state, orderHandle) {
307
+ const known = typeof state === "string" ? GATEWAY_ORDER_STATES[state] : undefined;
308
+ if (!known)
309
+ throw orderStateUnreadable(state, orderHandle);
310
+ return known.local;
286
311
  }
287
312
  /**
288
313
  * The gateway's own answer for one payment identifier, with nothing signed
289
314
  * but the read authorization: no replay, no balance reading, no local
290
315
  * ledger. This is the lookup buy.md names for every ambiguous outcome and
291
316
  * for PAYMENT_IDENTIFIER_CONFLICT. An identifier the gateway never issued
292
- * (a `daski-` one from 0.1.0/0.1.1) matches nothing, which is the truth:
293
- * nothing could have settled under it.
317
+ * matches nothing, which is the truth: nothing could have settled under it.
294
318
  */
295
319
  export function reconcileIdentifierRows(intentId, rows) {
296
320
  const matches = rows.filter((row) => row.paymentIdentifier === intentId);
297
- if (rows.some((row) => typeof row?.paymentIdentifier !== "string") || matches.length > 1) {
298
- return { status: "ambiguous", evidence: "The gateway history does not identify one purchase reliably." };
321
+ if (matches.length > 1) {
322
+ return { status: "ambiguous", evidence: "The gateway history lists more than one order for the identifier." };
299
323
  }
300
324
  if (matches.length === 0) {
301
325
  return {
@@ -350,8 +374,12 @@ export function purchaseFailure(result, options = {}) {
350
374
  message: typeof body?.message === "string"
351
375
  ? body.message
352
376
  : "The gateway rejected the purchase.",
353
- remediation: typeof body?.next_action === "string" ? body.next_action : fallbackRemediation,
354
- details: { gateway: body ?? describeResult(result) },
377
+ remediation: gatewayRefusalRemediation(code, body) ??
378
+ (typeof body?.next_action === "string" ? body.next_action : fallbackRemediation),
379
+ details: {
380
+ gateway: body ?? describeResult(result),
381
+ ...(isRetryableGatewayCode(code) ? { retryable: true } : {}),
382
+ },
355
383
  });
356
384
  }
357
385
  /** Human-readable price, for approval prompts and receipts. */