@zkp2p/cash 0.4.11-rc.1 → 0.4.11-rc.2

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/AGENTS.md CHANGED
@@ -26,7 +26,8 @@ can withdraw an unmatched deposit.
26
26
  persist the returned `depositId`. If the original plan set
27
27
  a non-empty `accessPolicyPaymentMethods`, submit and confirm
28
28
  `prepareAccessPolicy(depositId, paymentMethod)` with the depositor for each
29
- returned method.
29
+ returned method. Do the same for `disputeProtectionPaymentMethods` with
30
+ `prepareDisputeProtection(depositId, paymentMethod)`.
30
31
  3. **You are a tool-use host** (MCP server, CLI) → import the manifest from
31
32
  `@zkp2p/cash/tools` and map the tool names to the verbs above. Base-USDC
32
33
  mutating tools return unsigned transactions. `cash_source_quote` is a quote,
@@ -45,14 +46,13 @@ deposit-level integration share instead of applying maker L1/L2.
45
46
 
46
47
  **Platform caveats:**
47
48
 
48
- - **Venmo, Cash App, and PayPal restrict who can signal intents by default.**
49
- Signed `cashout()` confirms `createDeposit`, then submits and confirms a
50
- method-scoped Peer Pay merchant policy for every restricted payout leg using
51
- the same viem wallet. This is a deliberate non-atomic follow-up with a brief
52
- unprotected interval. For `prepare()`, read `accessPolicyPaymentMethods`;
53
- after `createDeposit` confirms, finalize its receipt and submit
54
- `prepareAccessPolicy(depositId, paymentMethod)` for every returned method.
55
- Any viem EOA works; Privy is not required.
49
+ - **Venmo, Cash App, and PayPal use both restricted-rail defaults.** Signed
50
+ `cashout()` proves dispute protection is ready, confirms `createDeposit`,
51
+ then submits and confirms a method-scoped Peer Pay merchant policy and
52
+ dispute-protection opt-in for every restricted payout leg using the same
53
+ viem wallet. For `prepare()`, finalize the confirmed receipt, then iterate
54
+ both `accessPolicyPaymentMethods` and `disputeProtectionPaymentMethods` with
55
+ their matching prepare methods. Any viem EOA works; Privy is not required.
56
56
 
57
57
  - **Wise and PayPal** carry `requiresIdentityAttestation: true`. A new curator
58
58
  registration needs a signed maker identity attestation this SDK cannot mint
@@ -234,6 +234,10 @@ const route = await cash.nearIntentsStatus({
234
234
  already exists. If `recovery.transactionHash` is present, inspect that policy
235
235
  transaction first; prepare another policy transaction only when the previous
236
236
  one is absent or confirmed reverted.
237
+ - **Fail closed on dispute protection.** `DISPUTE_PROTECTION_UNAVAILABLE`
238
+ occurs before deposit creation. `DISPUTE_PROTECTION_CONFIGURATION_FAILED`
239
+ means the deposit exists; inspect its recovery hash and use
240
+ `prepareDisputeProtection` only when resubmission is safe.
237
241
  - **`ORDER_NOT_FOUND` seconds after `cashout()` is indexer lag**, not a lost
238
242
  deposit. The tx receipt you hold is the truth. Retry; `watch()` absorbs
239
243
  this automatically.
@@ -258,51 +262,53 @@ const route = await cash.nearIntentsStatus({
258
262
 
259
263
  Every `CashError` carries `code`, `retryable`, `remediation`. Behavior:
260
264
 
261
- | Code | Retryable | Agent action |
262
- | --------------------------------------- | --------- | ------------------------------------------------------------------------------------------ |
263
- | `ORACLE_UNSUPPORTED_CURRENCY` | no | Re-pick currency from `capabilities()` |
264
- | `ORACLE_READ_FAILED` | yes | Retry the read through a healthy Base RPC; do not present a cached value as live |
265
- | `UNSUPPORTED_PLATFORM` | no | Re-pick platform from `capabilities()` |
266
- | `UNSUPPORTED_PLATFORM_CURRENCY` | no | Use a currency listed for that platform |
267
- | `AMOUNT_BELOW_MINIMUM` | no | Raise amount (hard floor $0.01, recommended at least 1 USDC) |
268
- | `INVALID_INTENT_AMOUNT_RANGE` | no | Use a positive min, max at least min, and max no greater than amount |
269
- | `INVALID_PAYOUT_CURRENCIES` | no | Pass one or more unique currencies listed for the platform |
270
- | `INVALID_PAYOUT_PLATFORMS` | no | Pass one leg or an array of legs, using each platform at most once |
271
- | `PAYEE_VERIFICATION_REQUIRED` | no | Use Peer web + TEE extension for new Wise/PayPal; reuse registered handles |
272
- | `PAYEE_REGISTRATION_FAILED` | yes | Validate against `payeeHint`, then retry |
273
- | `ATOMIC_ACCESS_POLICY_REQUIRED` | no | Deprecated compatibility code; current SDK flows never emit it |
274
- | `ACCESS_POLICY_CONFIGURATION_FAILED` | no | Deposit exists; inspect policy tx first, attach only if needed; never repeat the cash-out. |
275
- | `SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE` | no | Execute Relay with a signer first, then prepare a Base-USDC cashout |
276
- | `SOURCE_RECIPIENT_MISMATCH` | no | Route Base USDC to the cashout depositor |
277
- | `SOURCE_CAPABILITIES_FAILED` | yes | Retry discovery or fall back to Base USDC |
278
- | `SOURCE_QUOTE_FAILED` | yes | Refresh capabilities and request a new canonical Base-USDC quote |
279
- | `SOURCE_NONCE_MANAGER_REQUIRED` | no | Preflight; recreate the source signer with viem's `nonceManager`, then quote again |
280
- | `SOURCE_EXECUTION_FAILED` | no | Inspect source transactions and Relay status before any retry |
281
- | `SOURCE_DEPOSIT_SUBMISSION_FAILED` | yes | Retry only the 1Click notification; never resend source funds |
282
- | `SOURCE_STATUS_FAILED` | yes | Retry only the status read |
283
- | `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED` | no | Do not route again; retry Base-only with `recovery.amount` |
284
- | `SOURCE_CASHOUT_SUBMISSION_UNKNOWN` | no | Inspect Base activity and orders; prove no deposit exists before retrying |
285
- | `SOURCE_CASHOUT_STATUS_UNKNOWN` | no | Inspect `recovery.depositTxHash`; do not resubmit while its receipt is unknown |
286
- | `INSUFFICIENT_TOKEN_BALANCE` | no | Fund the required token amount, then retry |
287
- | `ALLOWANCE_NOT_VISIBLE` | yes | Approval mined but a stale RPC hid it; retry after it becomes visible |
288
- | `TRANSACTION_REJECTED` | yes | Retry when ready and approve the wallet request |
289
- | `TRANSACTION_FAILED` | no | Inspect the failed/reverted call before another action |
290
- | `TRANSACTION_SUBMISSION_UNKNOWN` | no | Inspect Base wallet/protocol state and the recovery action before any resubmission |
291
- | `TRANSACTION_STATUS_UNKNOWN` | no | Inspect `recovery.transactionHash` before resubmitting |
292
- | `DEPOSIT_RESOLUTION_FAILED` | no | Inspect the confirmed Base receipt and recover the id from `DepositReceived` |
293
- | `INVALID_DEPOSIT_ID` | no | Use the exact id returned by `cashout()` |
294
- | `ORDER_NOT_FOUND` | yes | Retry through immediate indexer lag; otherwise verify the id |
295
- | `INDEXER_LAG` | yes | Retry after a few seconds |
296
- | `INDEXER_UNAVAILABLE` | yes | Retry only the failed read; keep the id/owner and never repeat a transaction |
297
- | `ACTIVE_INTENT_BLOCKS_WITHDRAWAL` | yes | Wait for fill/expiry, or withdraw only the unlocked amount |
298
- | `INSUFFICIENT_AVAILABLE_FUNDS` | yes | Lower the partial withdrawal amount |
299
- | `NOTHING_TO_WITHDRAW` | no | Order is terminal; reconcile records |
300
- | `ORDER_NOT_ACTIVE` | no | Start a new cashout instead of topping up |
301
- | `SIGNER_REQUIRED` | no | Provide a signer or use a Base-USDC prepare path |
302
- | `SIGNER_CHAIN_MISMATCH` | no | Switch to the required chain and refresh any Relay quote before retrying |
303
- | `SIGNER_CHAIN_UNAVAILABLE` | yes | Reconnect the wallet and prove its chain before retrying |
304
- | `WATCH_TIMEOUT` | yes | Resume `watch(depositId)` later |
305
- | `ESCROW_PAUSED` | yes | Back off; existing funds remain withdrawable |
265
+ | Code | Retryable | Agent action |
266
+ | ----------------------------------------- | --------- | ------------------------------------------------------------------------------------------ |
267
+ | `ORACLE_UNSUPPORTED_CURRENCY` | no | Re-pick currency from `capabilities()` |
268
+ | `ORACLE_READ_FAILED` | yes | Retry the read through a healthy Base RPC; do not present a cached value as live |
269
+ | `UNSUPPORTED_PLATFORM` | no | Re-pick platform from `capabilities()` |
270
+ | `UNSUPPORTED_PLATFORM_CURRENCY` | no | Use a currency listed for that platform |
271
+ | `AMOUNT_BELOW_MINIMUM` | no | Raise amount (hard floor $0.01, recommended at least 1 USDC) |
272
+ | `INVALID_INTENT_AMOUNT_RANGE` | no | Use a positive min, max at least min, and max no greater than amount |
273
+ | `INVALID_PAYOUT_CURRENCIES` | no | Pass one or more unique currencies listed for the platform |
274
+ | `INVALID_PAYOUT_PLATFORMS` | no | Pass one leg or an array of legs, using each platform at most once |
275
+ | `PAYEE_VERIFICATION_REQUIRED` | no | Use Peer web + TEE extension for new Wise/PayPal; reuse registered handles |
276
+ | `PAYEE_REGISTRATION_FAILED` | yes | Validate against `payeeHint`, then retry |
277
+ | `ATOMIC_ACCESS_POLICY_REQUIRED` | no | Deprecated compatibility code; current SDK flows never emit it |
278
+ | `ACCESS_POLICY_CONFIGURATION_FAILED` | no | Deposit exists; inspect policy tx first, attach only if needed; never repeat the cash-out. |
279
+ | `DISPUTE_PROTECTION_UNAVAILABLE` | yes | No deposit exists; wait for the protected stack or choose an unrestricted rail. |
280
+ | `DISPUTE_PROTECTION_CONFIGURATION_FAILED` | no | Deposit exists; inspect the protection tx and recover it without repeating the cash-out. |
281
+ | `SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE` | no | Execute Relay with a signer first, then prepare a Base-USDC cashout |
282
+ | `SOURCE_RECIPIENT_MISMATCH` | no | Route Base USDC to the cashout depositor |
283
+ | `SOURCE_CAPABILITIES_FAILED` | yes | Retry discovery or fall back to Base USDC |
284
+ | `SOURCE_QUOTE_FAILED` | yes | Refresh capabilities and request a new canonical Base-USDC quote |
285
+ | `SOURCE_NONCE_MANAGER_REQUIRED` | no | Preflight; recreate the source signer with viem's `nonceManager`, then quote again |
286
+ | `SOURCE_EXECUTION_FAILED` | no | Inspect source transactions and Relay status before any retry |
287
+ | `SOURCE_DEPOSIT_SUBMISSION_FAILED` | yes | Retry only the 1Click notification; never resend source funds |
288
+ | `SOURCE_STATUS_FAILED` | yes | Retry only the status read |
289
+ | `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED` | no | Do not route again; retry Base-only with `recovery.amount` |
290
+ | `SOURCE_CASHOUT_SUBMISSION_UNKNOWN` | no | Inspect Base activity and orders; prove no deposit exists before retrying |
291
+ | `SOURCE_CASHOUT_STATUS_UNKNOWN` | no | Inspect `recovery.depositTxHash`; do not resubmit while its receipt is unknown |
292
+ | `INSUFFICIENT_TOKEN_BALANCE` | no | Fund the required token amount, then retry |
293
+ | `ALLOWANCE_NOT_VISIBLE` | yes | Approval mined but a stale RPC hid it; retry after it becomes visible |
294
+ | `TRANSACTION_REJECTED` | yes | Retry when ready and approve the wallet request |
295
+ | `TRANSACTION_FAILED` | no | Inspect the failed/reverted call before another action |
296
+ | `TRANSACTION_SUBMISSION_UNKNOWN` | no | Inspect Base wallet/protocol state and the recovery action before any resubmission |
297
+ | `TRANSACTION_STATUS_UNKNOWN` | no | Inspect `recovery.transactionHash` before resubmitting |
298
+ | `DEPOSIT_RESOLUTION_FAILED` | no | Inspect the confirmed Base receipt and recover the id from `DepositReceived` |
299
+ | `INVALID_DEPOSIT_ID` | no | Use the exact id returned by `cashout()` |
300
+ | `ORDER_NOT_FOUND` | yes | Retry through immediate indexer lag; otherwise verify the id |
301
+ | `INDEXER_LAG` | yes | Retry after a few seconds |
302
+ | `INDEXER_UNAVAILABLE` | yes | Retry only the failed read; keep the id/owner and never repeat a transaction |
303
+ | `ACTIVE_INTENT_BLOCKS_WITHDRAWAL` | yes | Wait for fill/expiry, or withdraw only the unlocked amount |
304
+ | `INSUFFICIENT_AVAILABLE_FUNDS` | yes | Lower the partial withdrawal amount |
305
+ | `NOTHING_TO_WITHDRAW` | no | Order is terminal; reconcile records |
306
+ | `ORDER_NOT_ACTIVE` | no | Start a new cashout instead of topping up |
307
+ | `SIGNER_REQUIRED` | no | Provide a signer or use a Base-USDC prepare path |
308
+ | `SIGNER_CHAIN_MISMATCH` | no | Switch to the required chain and refresh any Relay quote before retrying |
309
+ | `SIGNER_CHAIN_UNAVAILABLE` | yes | Reconnect the wallet and prove its chain before retrying |
310
+ | `WATCH_TIMEOUT` | yes | Resume `watch(depositId)` later |
311
+ | `ESCROW_PAUSED` | yes | Back off; existing funds remain withdrawable |
306
312
 
307
313
  `isCashError(err)` narrows unknown errors; `err.toJSON()` is safe for logs
308
314
  and tool results.
@@ -313,7 +319,8 @@ Prove your integration against `environment: 'staging'` with a funded test
313
319
  wallet. Never wait on a buyer - buyer-side is out of your scope:
314
320
 
315
321
  1. `cashout()` a small amount (1–2 USDC) → capture `depositId` and, for a
316
- restricted payout, every entry in `accessPolicyTxHashes`.
322
+ restricted payout, every entry in `accessPolicyTxHashes` and
323
+ `disputeProtectionTxHashes`.
317
324
  2. `order(depositId)` shows `awaiting-buyer` (retry through indexer lag).
318
325
  3. `orders(owner)` includes the deposit.
319
326
  4. `withdraw(depositId)` → transaction succeeds.
package/README.md CHANGED
@@ -39,15 +39,15 @@ const fillStats = await cash.fillStats();
39
39
  const pairStats = fillStats['venmo:USD'];
40
40
  const multiCurrencyStats = fillStats['revolut:EUR+GBP+USD'];
41
41
 
42
- const { depositId, accessPolicyTxHashes } = await cash.cashout(
42
+ const { depositId, accessPolicyTxHashes, disputeProtectionTxHashes } = await cash.cashout(
43
43
  {
44
44
  amount: usdc(1000),
45
45
  receive: { platform: 'venmo', currency: 'USD', payee: '@you' },
46
46
  },
47
47
  { signer }, // any viem WalletClient on Base, including an EOA
48
48
  );
49
- // Venmo, Cash App, and PayPal return only after their access policy confirms.
50
- console.log(depositId, accessPolicyTxHashes);
49
+ // Restricted rails return only after both defaults confirm.
50
+ console.log(depositId, accessPolicyTxHashes, disputeProtectionTxHashes);
51
51
 
52
52
  // One method can offer several currencies. The buyer chooses the fill
53
53
  // currency, and each option resolves at its own live oracle rate.
@@ -92,8 +92,9 @@ Peer Cash and the general ZKP2P SDK serve different integration depths:
92
92
  | `@zkp2p/sdk` | You are composing directly with the Peer protocol | General maker and taker operations, deposits, intents, proofs, quotes, vaults, rate managers, referrals, hooks, and API helpers. Your application owns the workflow and protocol choices. |
93
93
 
94
94
  Peer Cash is a narrow facade over `@zkp2p/sdk`, not a replacement for it. It
95
- cannot express custom spreads, buyer-side proof flows, vaults, disputes, or
96
- arbitrary protocol operations.
95
+ cannot express custom spreads, buyer-side proof flows, vaults, dispute
96
+ management, or arbitrary protocol operations. The protected restricted-rail
97
+ default is fixed rather than a configurable dispute surface.
97
98
 
98
99
  ## The core verbs
99
100
 
@@ -108,9 +109,10 @@ arbitrary protocol operations.
108
109
  | `quoteNearIntentsSource(input)` | Signed 1Click quote with an origin-chain deposit address and optional memo |
109
110
  | `submitNearIntentsDeposit(input)` / `nearIntentsStatus(input)` | Optionally register an origin tx, then track 1Click delivery/refund evidence |
110
111
  | `estimate({ amount, currency }, { includeEta? })` | Base USDC oracle estimate; optionally skip the historical ETA for progressive rendering |
111
- | `cashout(input, { signer })` | Creates the order with any viem wallet; restricted methods then attach the Peer Pay merchant policy |
112
- | `prepare(input)` / `finalizePreparedCashout(receipt)` | Prepare external signing, resolve the deposit, then iterate `accessPolicyPaymentMethods` for follow-ups |
112
+ | `cashout(input, { signer })` | Creates the order; restricted methods then attach the Peer Pay merchant policy and enable dispute protection |
113
+ | `prepare(input)` / `finalizePreparedCashout(receipt)` | Prepare external signing, resolve the deposit, then iterate both restricted-method follow-up lists |
113
114
  | `prepareAccessPolicy(depositId, paymentMethod)` | Prepare one post-deposit, method-scoped Peer Pay merchant policy transaction |
115
+ | `prepareDisputeProtection(depositId, paymentMethod)` | Prepare one post-deposit, method-scoped dispute-protection transaction |
114
116
  | `order(depositId)` / `orders(owner)` | Resume any order from its id alone; list all orders for a wallet |
115
117
  | `watch(depositId)` | Async iterator: yields on every state change until terminal, abort, or timeout |
116
118
  | `withdraw(depositId, { signer, amount? })` | The ONE unwind verb - partial with an `amount` (live intents don't block it), full close without (prunes expired intents first) |
@@ -139,12 +141,12 @@ mixed historical deposit.
139
141
 
140
142
  ## Payout rails and access policies
141
143
 
142
- | Payout rail | Access-policy behavior | New payee registration |
143
- | --------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
144
- | Venmo / Cash App | Peer Pay merchant policy attaches for that payment method | Curator validates the live handle |
145
- | PayPal | Same method-scoped Peer Pay follow-up | Requires a Peer TEE browser-extension identity attestation |
146
- | Wise | No access-policy follow-up | Requires a Peer TEE browser-extension identity attestation |
147
- | Other supported rails | No access-policy follow-up; use `capabilities()` for currencies and format | Follow the `payeeHint`; live-validation behavior is described in the integration guide |
144
+ | Payout rail | Restricted-rail defaults | New payee registration |
145
+ | --------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
146
+ | Venmo / Cash App | Method-scoped Peer Pay merchant policy plus dispute protection | Curator validates the live handle |
147
+ | PayPal | Same method-scoped merchant-group and dispute-protection follow-ups | Requires a Peer TEE browser-extension identity attestation |
148
+ | Wise | No restricted-rail follow-up | Requires a Peer TEE browser-extension identity attestation |
149
+ | Other supported rails | No restricted-rail follow-up; use `capabilities()` for currencies and format | Follow the `payeeHint`; live-validation behavior is described in the integration guide |
148
150
 
149
151
  No platform requires an atomic access-policy flow. `cashout()` and `prepare()`
150
152
  work with any viem `WalletClient`, including a local or externally connected
@@ -153,14 +155,15 @@ EOA; no Privy wallet or signer API is required. The deprecated
153
155
  always `false`.
154
156
 
155
157
  Venmo, Cash App, and PayPal cash-outs restrict intent signaling to the Peer Pay
156
- merchant group by default. Each restricted payout method gets its own policy.
157
- Signed `cashout()` creates the deposit first, then uses the same wallet to
158
- submit and confirm every required policy transaction; this intentionally
159
- leaves a brief non-atomic interval. Prepared integrations receive
160
- `accessPolicyPaymentMethods`; after confirming `createDeposit`, call
161
- `finalizePreparedCashout(receipt)`, then submit
162
- `prepareAccessPolicy(depositId, paymentMethod)` once for every returned method.
163
- Other platforms do not need the follow-up.
158
+ merchant group and enable dispute protection by default. Both defaults are
159
+ method-scoped. Signed `cashout()` first proves the dispute-protection stack is
160
+ ready, then creates the deposit and uses the same wallet to submit and confirm
161
+ both follow-ups. Prepared integrations receive matching
162
+ `accessPolicyPaymentMethods` and `disputeProtectionPaymentMethods`; after
163
+ confirming `createDeposit`, call `finalizePreparedCashout(receipt)`, then submit
164
+ `prepareAccessPolicy(depositId, paymentMethod)` and
165
+ `prepareDisputeProtection(depositId, paymentMethod)` for every returned method.
166
+ Other platforms do not need either follow-up.
164
167
 
165
168
  If policy attachment fails, `ACCESS_POLICY_CONFIGURATION_FAILED.recovery`
166
169
  identifies the existing deposit and any submitted policy transaction. Never
@@ -168,6 +171,12 @@ create another cash-out. When `recovery.transactionHash` is present, inspect
168
171
  that transaction before resubmitting; otherwise prepare the policy again with
169
172
  the same depositor wallet.
170
173
 
174
+ If protection is not ready, `DISPUTE_PROTECTION_UNAVAILABLE` fails before the
175
+ deposit is created. If its post-deposit transaction fails,
176
+ `DISPUTE_PROTECTION_CONFIGURATION_FAILED.recovery` identifies the existing
177
+ deposit and submitted hash, if any; inspect it before using
178
+ `prepareDisputeProtection` for recovery and never repeat the cash-out.
179
+
171
180
  `capabilities()` presents Zelle as one platform. A cashout with
172
181
  `receive.platform: 'zelle'` attaches only the generic Zelle payment method to
173
182
  the deposit. Bank-specific capture routing is outside this maker-side SDK and
@@ -309,6 +318,11 @@ they are available. A source-routed result includes both a flat
309
318
  `prepareAccessPolicy(error.recovery.depositId, error.recovery.paymentMethod)`
310
319
  only if the prior policy
311
320
  transaction did not succeed.
321
+ - `DISPUTE_PROTECTION_UNAVAILABLE`: the protected contract stack is not ready;
322
+ no deposit was created. Retry later or use an unrestricted payout platform.
323
+ - `DISPUTE_PROTECTION_CONFIGURATION_FAILED`: the deposit exists, but one
324
+ required protection transaction was not confirmed. Inspect its recovery hash
325
+ before calling `prepareDisputeProtection`; never create another cash-out.
312
326
 
313
327
  Wallet clients pinned to the wrong chain fail with `SIGNER_CHAIN_MISMATCH`
314
328
  before a quote or transaction is submitted. Chainless wallets are checked
@@ -390,7 +404,9 @@ analytics-only ERC-8021 codes such as `acme-app`.
390
404
  - After a prepared restricted cash-out confirms, the host adapter must call
391
405
  `finalizePreparedCashout(receipt)` and submit
392
406
  `prepareAccessPolicy(depositId, paymentMethod)` for every value in
393
- `accessPolicyPaymentMethods`; these receipt/signing operations are
407
+ `accessPolicyPaymentMethods`, plus
408
+ `prepareDisputeProtection(depositId, paymentMethod)` for every value in
409
+ `disputeProtectionPaymentMethods`; these receipt/signing operations are
394
410
  `CashClient` methods, not built-in tool calls.
395
411
  - Every error carries `code`, `retryable`, and a `remediation` sentence.
396
412
  - Every order carries `nextActions: ('wait' | 'withdraw')[]` - no heuristics.
@@ -424,6 +424,38 @@ var errors = {
424
424
  },
425
425
  { cause: context.cause }
426
426
  ),
427
+ disputeProtectionUnavailable: (state, cause) => new CashError(
428
+ {
429
+ code: "DISPUTE_PROTECTION_UNAVAILABLE",
430
+ message: `Dispute protection is not ready for restricted Peer Cash payouts${state ? ` (${state})` : ""}.`,
431
+ retryable: true,
432
+ remediation: `Retry after the dispute-protection contracts are active and accepting protected deposits, or choose an unrestricted payout platform. No cash-out deposit was created.`
433
+ },
434
+ { cause }
435
+ ),
436
+ disputeProtectionConfigurationFailed: (depositId, paymentMethod, context = {}) => new CashError(
437
+ {
438
+ code: "DISPUTE_PROTECTION_CONFIGURATION_FAILED",
439
+ message: `Cash-out deposit ${depositId} was created, but dispute protection could not be confirmed for ${paymentMethod}.`,
440
+ retryable: false,
441
+ remediation: `Do not create another cash-out. If recovery.transactionHash is present, inspect that dispute-protection transaction first. Otherwise, or if it is confirmed reverted, submit and confirm prepareDisputeProtection(recovery.depositId, recovery.paymentMethod) with the same depositor wallet.`,
442
+ recovery: {
443
+ kind: "configure-cashout-dispute-protection",
444
+ depositId,
445
+ paymentMethod,
446
+ ...context.transactionHash ? { transactionHash: context.transactionHash } : {},
447
+ ...context.source ? {
448
+ source: {
449
+ amount: context.source.amount.toString(),
450
+ ...context.source.requestId ? { requestId: context.source.requestId } : {},
451
+ txHashes: context.source.txHashes,
452
+ ...context.source.transactions ? { transactions: context.source.transactions } : {}
453
+ }
454
+ } : {}
455
+ }
456
+ },
457
+ { cause: context.cause }
458
+ ),
427
459
  escrowPaused: () => new CashError({
428
460
  code: "ESCROW_PAUSED",
429
461
  message: `The escrow contract is paused; deposits are temporarily disabled.`,
@@ -486,8 +486,8 @@ interface CashPlatformCapability {
486
486
  requiresIdentityAttestation: boolean;
487
487
  /**
488
488
  * @deprecated Always false. This does not report the sequential restricted-
489
- * platform policy; prepared hosts must inspect
490
- * `PrepareResult.accessPolicyPaymentMethods`.
489
+ * platform defaults; prepared hosts must inspect both method lists on
490
+ * `PrepareResult`.
491
491
  */
492
492
  requiresAtomicAccessPolicy: boolean;
493
493
  }
@@ -769,6 +769,8 @@ interface CashoutResult {
769
769
  accessPolicyTxHash?: Hash;
770
770
  /** Confirmed method-scoped policy transactions for restricted payout legs. */
771
771
  accessPolicyTxHashes?: Hash[];
772
+ /** Confirmed method-scoped dispute-protection transactions for restricted payout legs. */
773
+ disputeProtectionTxHashes?: Hash[];
772
774
  /** Present when `cashout()` first routed a source asset through Relay. */
773
775
  source?: {
774
776
  /** Conservative Base USDC amount deposited (Relay's guaranteed minimum output). */
@@ -799,6 +801,10 @@ interface PrepareResult {
799
801
  accessPolicyRequired: boolean;
800
802
  /** Method hashes that each require a post-deposit Peer Pay policy transaction. */
801
803
  accessPolicyPaymentMethods: Hex[];
804
+ /** Whether the host must enable dispute protection after `createDeposit` confirms. */
805
+ disputeProtectionRequired: boolean;
806
+ /** Method hashes that each require a post-deposit dispute-protection transaction. */
807
+ disputeProtectionPaymentMethods: Hex[];
802
808
  }
803
809
  /** Confirmed createDeposit receipt from an externally executed prepare() plan. */
804
810
  interface PreparedCashoutReceipt {
@@ -877,6 +883,8 @@ interface CashClient {
877
883
  finalizePreparedCashout(receipt: PreparedCashoutReceipt): CashoutResult;
878
884
  /** Prepare one method-scoped Peer Pay follow-up for a restricted cash-out. */
879
885
  prepareAccessPolicy(depositId: string, paymentMethod: Hex): PreparedTransaction;
886
+ /** Prepare one method-scoped dispute-protection follow-up for a restricted cash-out. */
887
+ prepareDisputeProtection(depositId: string, paymentMethod: Hex): Promise<PreparedTransaction>;
880
888
  /** 3 - Observe: resumable from `depositId` alone; no session state anywhere. */
881
889
  order(depositId: string): Promise<CashOrder>;
882
890
  /**
@@ -486,8 +486,8 @@ interface CashPlatformCapability {
486
486
  requiresIdentityAttestation: boolean;
487
487
  /**
488
488
  * @deprecated Always false. This does not report the sequential restricted-
489
- * platform policy; prepared hosts must inspect
490
- * `PrepareResult.accessPolicyPaymentMethods`.
489
+ * platform defaults; prepared hosts must inspect both method lists on
490
+ * `PrepareResult`.
491
491
  */
492
492
  requiresAtomicAccessPolicy: boolean;
493
493
  }
@@ -769,6 +769,8 @@ interface CashoutResult {
769
769
  accessPolicyTxHash?: Hash;
770
770
  /** Confirmed method-scoped policy transactions for restricted payout legs. */
771
771
  accessPolicyTxHashes?: Hash[];
772
+ /** Confirmed method-scoped dispute-protection transactions for restricted payout legs. */
773
+ disputeProtectionTxHashes?: Hash[];
772
774
  /** Present when `cashout()` first routed a source asset through Relay. */
773
775
  source?: {
774
776
  /** Conservative Base USDC amount deposited (Relay's guaranteed minimum output). */
@@ -799,6 +801,10 @@ interface PrepareResult {
799
801
  accessPolicyRequired: boolean;
800
802
  /** Method hashes that each require a post-deposit Peer Pay policy transaction. */
801
803
  accessPolicyPaymentMethods: Hex[];
804
+ /** Whether the host must enable dispute protection after `createDeposit` confirms. */
805
+ disputeProtectionRequired: boolean;
806
+ /** Method hashes that each require a post-deposit dispute-protection transaction. */
807
+ disputeProtectionPaymentMethods: Hex[];
802
808
  }
803
809
  /** Confirmed createDeposit receipt from an externally executed prepare() plan. */
804
810
  interface PreparedCashoutReceipt {
@@ -877,6 +883,8 @@ interface CashClient {
877
883
  finalizePreparedCashout(receipt: PreparedCashoutReceipt): CashoutResult;
878
884
  /** Prepare one method-scoped Peer Pay follow-up for a restricted cash-out. */
879
885
  prepareAccessPolicy(depositId: string, paymentMethod: Hex): PreparedTransaction;
886
+ /** Prepare one method-scoped dispute-protection follow-up for a restricted cash-out. */
887
+ prepareDisputeProtection(depositId: string, paymentMethod: Hex): Promise<PreparedTransaction>;
880
888
  /** 3 - Observe: resumable from `depositId` alone; no session state anywhere. */
881
889
  order(depositId: string): Promise<CashOrder>;
882
890
  /**