@zkp2p/cash 0.2.0 → 0.3.0-rc.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/AGENTS.md CHANGED
@@ -16,7 +16,9 @@ can withdraw an unmatched deposit.
16
16
  human approval step) → use `prepare()` / `prepareTopUp()` /
17
17
  `prepareWithdraw()`. Each returns unsigned `txs[]`
18
18
  (`{ to, data, value, chainId }`) plus same-index `steps[]` labels; inspect
19
- the plan, submit the transactions in order, and wait for each receipt.
19
+ the plan, submit the transactions in order, and wait for each receipt. After
20
+ `createDeposit` confirms, pass its receipt to `finalizePreparedCashout()` and
21
+ persist the returned `depositId`.
20
22
  3. **You are a tool-use host** (MCP server, CLI) → import the manifest from
21
23
  `@zkp2p/cash/tools` and map the tool names to the verbs above. Base-USDC
22
24
  mutating tools return unsigned transactions. `cash_source_quote` is a quote,
@@ -57,14 +59,28 @@ const est = await cash.estimate({ amount: usdc(500), currency: 'EUR' });
57
59
  // Progressive UI: do not let indexer-backed history hold up the oracle rate.
58
60
  const rateOnly = await cash.estimate({ amount: usdc(500), currency: 'EUR' }, { includeEta: false });
59
61
 
60
- // Optional: raw demand + speed evidence per offered platform:currency pair.
62
+ // Optional: raw demand + speed evidence per offered pair or sorted currency set.
61
63
  const stats = await cash.fillStats();
64
+ console.log(stats['revolut:EUR+GBP+USD']);
62
65
 
63
66
  // 3. Execute.
64
67
  const { depositId } = await cash.cashout(
65
68
  {
66
69
  amount: usdc(500),
67
- receive: { platform: 'revolut', currency: 'EUR', payee: { offchainId: 'revtag' } },
70
+ receive: { platform: 'revolut', currency: 'EUR', payee: 'revtag' },
71
+ },
72
+ { signer },
73
+ );
74
+
75
+ // Faster matching: one Revolut method, three live-oracle currency options.
76
+ const multiCurrency = await cash.cashout(
77
+ {
78
+ amount: usdc(500),
79
+ receive: {
80
+ platform: 'revolut',
81
+ currencies: ['EUR', 'GBP', 'USD'],
82
+ payee: { offchainId: 'revtag' },
83
+ },
68
84
  },
69
85
  { signer },
70
86
  );
@@ -173,6 +189,7 @@ Every `CashError` carries `code`, `retryable`, `remediation`. Behavior:
173
189
  | `UNSUPPORTED_PLATFORM_CURRENCY` | no | Use a currency listed for that platform |
174
190
  | `AMOUNT_BELOW_MINIMUM` | no | Raise amount (hard floor $0.01, recommended at least 1 USDC) |
175
191
  | `INVALID_INTENT_AMOUNT_RANGE` | no | Use a positive min, max at least min, and max no greater than amount |
192
+ | `INVALID_PAYOUT_CURRENCIES` | no | Pass one or more unique currencies listed for the platform |
176
193
  | `PAYEE_VERIFICATION_REQUIRED` | no | Register a new Wise/PayPal payee through Peer; an existing registered handle can be reused |
177
194
  | `PAYEE_REGISTRATION_FAILED` | yes | Validate against `payeeHint`, then retry |
178
195
  | `SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE` | no | Execute Relay with a signer first, then prepare a Base-USDC cashout |
@@ -187,6 +204,7 @@ Every `CashError` carries `code`, `retryable`, `remediation`. Behavior:
187
204
  | `SOURCE_CASHOUT_STATUS_UNKNOWN` | no | Inspect `recovery.depositTxHash`; do not resubmit while its receipt is unknown |
188
205
  | `INSUFFICIENT_TOKEN_BALANCE` | no | Fund the required token amount, then retry |
189
206
  | `ALLOWANCE_NOT_VISIBLE` | yes | Approval mined but a stale RPC hid it; retry after it becomes visible |
207
+ | `TRANSACTION_REJECTED` | yes | Retry when ready and approve the wallet request |
190
208
  | `TRANSACTION_FAILED` | no | Inspect the failed/reverted call before another action |
191
209
  | `TRANSACTION_SUBMISSION_UNKNOWN` | no | Inspect Base wallet/protocol state and the recovery action before any resubmission |
192
210
  | `TRANSACTION_STATUS_UNKNOWN` | no | Inspect `recovery.transactionHash` before resubmitting |
package/README.md CHANGED
@@ -39,16 +39,32 @@ const rateOnly = await cash.estimate(
39
39
  { amount: usdc(1000), currency: 'USD' },
40
40
  { includeEta: false },
41
41
  );
42
- const pairStats = (await cash.fillStats())['venmo:USD'];
42
+ const fillStats = await cash.fillStats();
43
+ const pairStats = fillStats['venmo:USD'];
44
+ const multiCurrencyStats = fillStats['revolut:EUR+GBP+USD'];
43
45
 
44
46
  const { depositId } = await cash.cashout(
45
47
  {
46
48
  amount: usdc(1000),
47
- receive: { platform: 'venmo', currency: 'USD', payee: { offchainId: '@you' } },
49
+ receive: { platform: 'venmo', currency: 'USD', payee: '@you' },
48
50
  },
49
51
  { signer }, // any viem WalletClient on Base
50
52
  );
51
53
 
54
+ // One method can offer several currencies. The buyer chooses the fill
55
+ // currency, and each option resolves at its own live oracle rate.
56
+ const fastFill = await cash.cashout(
57
+ {
58
+ amount: usdc(1000),
59
+ receive: {
60
+ platform: 'revolut',
61
+ currencies: ['EUR', 'GBP', 'USD'],
62
+ payee: { offchainId: 'revtag' },
63
+ },
64
+ },
65
+ { signer },
66
+ );
67
+
52
68
  for await (const order of cash.watch(depositId)) {
53
69
  console.log(order.state, order.explain());
54
70
  if (order.state === 'delivered') break;
@@ -83,11 +99,12 @@ console.log(source?.transactions?.origin, source?.transactions?.destination);
83
99
  | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
84
100
  | `capabilities()` | Sync discovery: Base USDC destination/default source, platforms × currencies × payee hints × amount bounds |
85
101
  | `capabilities({ includeRelaySources: true })` | Async discovery: adds live Relay SDK EVM source chains/tokens |
86
- | `fillStats()` | Cached 30-day fill counts and median first-fill time per exact `platform:currency` pair |
102
+ | `fillStats()` | Cached 30-day fill counts and median first-fill time per exact `platform:currency` pair or sorted multi-currency set |
87
103
  | `quoteSource(input)` / `executeSourceQuote(quote, { signer })` | Relay SDK EVM source routing into Base USDC before cashout |
88
104
  | `relayStatus(requestId)` | Relay request status from the Relay SDK request path |
89
105
  | `estimate({ amount, currency }, { includeEta? })` | Base USDC oracle estimate; optionally skip the historical ETA for progressive rendering |
90
106
  | `cashout(input, { signer })` | Registers your payee, creates the protocol-held order, returns the `depositId` |
107
+ | `prepare(input)` / `finalizePreparedCashout(receipt)` | Prepare external signing, then resolve the confirmed createDeposit receipt into a resumable result |
91
108
  | `order(depositId)` / `orders(owner)` | Resume any order from its id alone; list all orders for a wallet |
92
109
  | `watch(depositId)` | Async iterator: yields on every state change until terminal, abort, or timeout |
93
110
  | `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) |
@@ -102,9 +119,36 @@ transaction does before signing. `prepare()` is Base-USDC-only and rejects a
102
119
  `source` with `SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE`. A signer-backed app can
103
120
  use `cashout({ source }, { signer, sourceSigner })`; a custody-separated host
104
121
  must execute and confirm its Relay route before preparing the Base-USDC
105
- cashout. Every Peer Cash transaction, including approves, carries ERC-8021
122
+ cashout. After externally executing a prepared `createDeposit`, pass its
123
+ confirmed receipt to `finalizePreparedCashout()` to recover the same
124
+ `CashoutResult` shape as `cashout()` without importing protocol ABIs. Every
125
+ Peer Cash transaction, including approves, carries ERC-8021
106
126
  attribution: `peer-cash` first, your own `referrer` code(s) after it.
107
127
 
128
+ ```ts
129
+ const prepared = await cash.prepare({
130
+ amount: 5_000_000n,
131
+ receive: {
132
+ platform: 'venmo',
133
+ currency: 'USD',
134
+ payee: { offchainId: '@maker' },
135
+ },
136
+ });
137
+
138
+ let createDepositReceipt;
139
+ for (const [index, transaction] of prepared.txs.entries()) {
140
+ const receipt = await externalRuntime.sendAndWait(transaction);
141
+ if (prepared.steps[index]?.kind === 'createDeposit') {
142
+ createDepositReceipt = receipt;
143
+ }
144
+ }
145
+ if (!createDepositReceipt) throw new Error('createDeposit receipt missing');
146
+
147
+ const result = cash.finalizePreparedCashout(createDepositReceipt);
148
+ await persistDepositId(result.depositId);
149
+ const liveOrder = await cash.order(result.depositId);
150
+ ```
151
+
108
152
  `capabilities()` presents Zelle as one platform. A cashout with
109
153
  `receive.platform: 'zelle'` attaches only the generic Zelle payment method to
110
154
  the deposit. Bank-specific capture routing is outside this maker-side SDK and
@@ -193,7 +237,9 @@ awaiting-buyer ──────────► matched ───────
193
237
  by the same rolling 30-day, intent-attributed pair sampler as `fillStats()`,
194
238
  measured from deposit creation to the first fulfilled fill through the pair.
195
239
  The raw snapshot is cached for 15 minutes per client and each ETA is still
196
- resolved from its exact normalized `platform:currency` key. Use
240
+ resolved from its exact normalized `platform:currency` key. Multi-currency
241
+ deposits also produce sorted keys such as `revolut:EUR+GBP+USD`, measured to
242
+ the first fill in any offered currency. Use
197
243
  `{ includeEta: false }` when rate and receive amount should render first.
198
244
  - **Availability thresholds belong to the consumer.** `fillStats()` returns raw
199
245
  evidence. A recommended gate is `fills >= 10 && medianFillSeconds <= 48h`.
@@ -81,6 +81,12 @@ var errors = {
81
81
  retryable: false,
82
82
  remediation: `Use a positive minimum no greater than the maximum, and a maximum no greater than the cash-out amount.`
83
83
  }),
84
+ invalidPayoutCurrencies: (platform, reason) => new CashError({
85
+ code: "INVALID_PAYOUT_CURRENCIES",
86
+ message: `The ${platform} payout currency set is invalid: ${reason}.`,
87
+ retryable: false,
88
+ remediation: `Pass one or more unique currencies listed for ${platform} by capabilities().`
89
+ }),
84
90
  activeIntentBlocksWithdrawal: (depositId) => new CashError({
85
91
  code: "ACTIVE_INTENT_BLOCKS_WITHDRAWAL",
86
92
  message: `Order ${depositId} has a live buyer intent; escrow blocks withdrawal while a buyer may still deliver.`,
@@ -284,7 +290,12 @@ var errors = {
284
290
  code: "DEPOSIT_RESOLUTION_FAILED",
285
291
  message: `Deposit transaction ${txHash} succeeded but no DepositReceived event was found in the receipt.`,
286
292
  retryable: false,
287
- remediation: `Inspect the transaction on Basescan; recover the depositId from the DepositReceived log manually, then resume with order(depositId).`
293
+ remediation: `Inspect the transaction on Basescan; recover the depositId from the DepositReceived log manually, then resume with order(depositId).`,
294
+ recovery: {
295
+ kind: "inspect-base-transaction",
296
+ transactionHash: txHash,
297
+ operation: "cashout"
298
+ }
288
299
  }),
289
300
  signerRequired: (verb) => new CashError({
290
301
  code: "SIGNER_REQUIRED",
@@ -322,6 +333,15 @@ var errors = {
322
333
  },
323
334
  { cause }
324
335
  ),
336
+ transactionRejected: (verb, cause) => new CashError(
337
+ {
338
+ code: "TRANSACTION_REJECTED",
339
+ message: `The ${verb} wallet request was cancelled.`,
340
+ retryable: true,
341
+ remediation: `Retry the original Peer Cash action and approve the wallet request when you are ready.`
342
+ },
343
+ { cause }
344
+ ),
325
345
  transactionSubmissionUnknown: (operation, cause, recovery) => new CashError(
326
346
  {
327
347
  code: "TRANSACTION_SUBMISSION_UNKNOWN",
@@ -365,6 +385,7 @@ var errors = {
365
385
  };
366
386
  function mapChainError(verb, err, context = {}) {
367
387
  if (isCashError(err)) return err;
388
+ if (isUserRejectedError(err)) return errors.transactionRejected(verb, err);
368
389
  const message = err instanceof Error ? err.message : String(err);
369
390
  if (/\bpaused\b/i.test(message)) return errors.escrowPaused();
370
391
  if (/exceeds balance|insufficient token balance/i.test(message)) {
@@ -375,5 +396,41 @@ function mapChainError(verb, err, context = {}) {
375
396
  }
376
397
  return errors.chainCallFailed(verb, err);
377
398
  }
399
+ function hasUserRejectionText(value) {
400
+ const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "");
401
+ return normalized.includes("userrejected") || normalized.includes("userdenied") || normalized.includes("requestrejected") || normalized.includes("rejectedrequest") || /(^|[^a-z0-9])action[_ -]?rejected(?:error)?($|[^a-z0-9])/i.test(value) || normalized === "actionrejected" || normalized === "actionrejectederror";
402
+ }
403
+ function isUserRejectedError(value) {
404
+ const seen = /* @__PURE__ */ new Set();
405
+ const text = [];
406
+ let current = value;
407
+ while (current !== null && !seen.has(current)) {
408
+ seen.add(current);
409
+ if (current === -32003 || current === "-32003") return false;
410
+ if (current === 4001 || current === "4001" || current === 5e3 || current === "5000") {
411
+ return true;
412
+ }
413
+ if (typeof current === "string") {
414
+ text.push(current);
415
+ break;
416
+ }
417
+ if (typeof current !== "object" && typeof current !== "function") break;
418
+ const detail = current;
419
+ if (detail.code === -32003 || detail.code === "-32003" || detail.name === "TransactionRejectedRpcError") {
420
+ return false;
421
+ }
422
+ if (detail.code === 4001 || detail.code === "4001" || detail.code === 5e3 || detail.code === "5000" || detail.code === "ACTION_REJECTED" || detail.name === "UserRejectedRequestError") {
423
+ return true;
424
+ }
425
+ text.push(
426
+ ...[detail.name, detail.message, detail.code].filter(
427
+ (part) => typeof part === "string"
428
+ )
429
+ );
430
+ if (detail.cause === void 0) break;
431
+ current = detail.cause;
432
+ }
433
+ return text.some(hasUserRejectionText);
434
+ }
378
435
 
379
- export { BASE_CHAIN_ID, BASE_USDC_ADDRESS, CASH_ORDER_POLL_INTERVAL_MS, CASH_ORDER_STATUSES, CASH_RETAIN_ON_EMPTY, CashError, MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, USDC_DECIMALS, errors, isCashError, mapChainError };
436
+ export { BASE_CHAIN_ID, BASE_USDC_ADDRESS, CASH_ORDER_POLL_INTERVAL_MS, CASH_ORDER_STATUSES, CASH_RETAIN_ON_EMPTY, CashError, MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, USDC_DECIMALS, errors, isCashError, isUserRejectedError, mapChainError };
@@ -1,4 +1,4 @@
1
- import { Address, WalletClient, Hash, Transport } from 'viem';
1
+ import { Address, WalletClient, Hash, Log, Transport } from 'viem';
2
2
  import { IndexerIntentStatus, Zkp2pClient, IndexerIntent, CurrencyType, PreparedTransaction, RuntimeEnv } from '@zkp2p/sdk';
3
3
  import { Execute, RelayClient, RelayChain, ProgressData } from '@relayprotocol/relay-sdk';
4
4
 
@@ -172,15 +172,20 @@ interface CashBuyerProfile {
172
172
  firstSeenAt?: number;
173
173
  lastSeenAt?: number;
174
174
  }
175
- /** A single payout leg of a cash-out (one platform + currency + payee). */
176
- interface CashPayout {
175
+ interface CashPayoutBase {
177
176
  /** Payment platform / processor name, e.g. `'venmo'`, `'revolut'`, `'wise'`. */
178
177
  processorName: string;
179
- /** Fiat currency the user wants to receive. */
180
- currency: CurrencyType;
181
178
  /** The user's payee handle for that platform (e.g. a Venmo username, Wisetag). */
182
179
  payeeData: CuratorPayeeDataInput;
183
180
  }
181
+ /** One payment method offering either one currency or a non-empty currency set. */
182
+ type CashPayout = CashPayoutBase & ({
183
+ currency: CurrencyType;
184
+ currencies?: never;
185
+ } | {
186
+ currency?: never;
187
+ currencies: readonly [CurrencyType, ...CurrencyType[]];
188
+ });
184
189
  /**
185
190
  * Input to create a market-rate (0% spread) cash-out deposit.
186
191
  *
@@ -193,7 +198,7 @@ interface CashDepositInput {
193
198
  token?: Address;
194
199
  /** Total amount to cash out, in USDC base units (6 decimals). */
195
200
  amount: bigint;
196
- /** One or more payout legs (platform + currency + payee). */
201
+ /** One or more payout legs (platform + currency choice + payee). */
197
202
  payouts: CashPayout[];
198
203
  /** Per-order min/max in USDC base units. Defaults derive from {@link buildIntentAmountRange}. */
199
204
  intentAmountRange?: {
@@ -358,12 +363,15 @@ interface CashCapabilities {
358
363
  declare function buildCapabilities(environment: RuntimeEnv): CashCapabilities;
359
364
 
360
365
  interface CashPairFillStats {
361
- /** Fulfilled intents through this pair inside the rolling 30-day window. */
366
+ /** Fulfilled intents through this pair or currency set inside the rolling 30-day window. */
362
367
  fills: number;
363
- /** Median deposit-to-first-fill seconds, sampled once per deposit for this pair. */
368
+ /** Median deposit-to-first-fill seconds, sampled once per deposit for this pair or set. */
364
369
  medianFillSeconds?: number;
365
370
  }
366
- /** Raw demand and speed evidence keyed by `basePlatform:currencyCode`. */
371
+ /**
372
+ * Raw demand and speed evidence keyed by `basePlatform:currencyCode` or a
373
+ * sorted multi-currency set such as `revolut:EUR+GBP+USD`.
374
+ */
367
375
  type CashFillStats = Record<string, CashPairFillStats>;
368
376
  interface CashFillEta {
369
377
  /** Simple headline ETA from recent deposits. Undefined when no recent sample exists. */
@@ -435,8 +443,12 @@ interface CashEstimate {
435
443
  eta?: CashFillEta;
436
444
  }
437
445
 
446
+ type CashPayeeInput = string | CuratorPayeeDataInput;
447
+ /** Convert user-entered handles into the curator form for a payment platform. */
448
+ declare function normalizeCashPayee(platform: string, payee: CashPayeeInput): CuratorPayeeDataInput;
449
+
438
450
  /**
439
- * `createCashClient` - the eight-verb facade over a read-only `Zkp2pClient`.
451
+ * `createCashClient` - the cash lifecycle facade over a read-only `Zkp2pClient`.
440
452
  *
441
453
  * The facade keeps the outward surface tiny (capabilities / estimate / cashout
442
454
  * / order / orders / watch / withdraw / topUp) while reusing the published
@@ -481,8 +493,18 @@ interface CashLeg {
481
493
  platform: string;
482
494
  /** Fiat currency to receive. */
483
495
  currency: CurrencyType;
484
- /** Payee details, e.g. `{ offchainId: '@andrew' }`. */
485
- payee: CuratorPayeeDataInput;
496
+ /** Raw handle or prepared curator data (needed for identity attestations). */
497
+ payee: CashPayeeInput;
498
+ currencies?: never;
499
+ }
500
+ interface CashMultiCurrencyLeg {
501
+ /** Platform id from `capabilities()`, e.g. `'revolut'`. */
502
+ platform: string;
503
+ /** Fiat currencies a buyer may use to fill this cash-out. */
504
+ currencies: readonly [CurrencyType, ...CurrencyType[]];
505
+ /** Raw handle or prepared curator data shared by every offered currency. */
506
+ payee: CashPayeeInput;
507
+ currency?: never;
486
508
  }
487
509
  interface CashoutInput {
488
510
  /**
@@ -498,8 +520,8 @@ interface CashoutInput {
498
520
  /** Relay amount mode. Omit for the recommended exact source-input flow. */
499
521
  tradeType?: 'EXACT_INPUT' | 'EXACT_OUTPUT' | 'EXPECTED_OUTPUT';
500
522
  };
501
- /** Where the fiat should arrive. Multi-payout is a deliberate v1 cut. */
502
- receive: CashLeg;
523
+ /** Where the fiat should arrive. One method may offer multiple currencies. */
524
+ receive: CashLeg | CashMultiCurrencyLeg;
503
525
  /** Per-order min/max override (USDC base units). */
504
526
  intentAmountRange?: {
505
527
  min: bigint;
@@ -572,6 +594,12 @@ interface PrepareResult {
572
594
  hashedOnchainIds: string[];
573
595
  };
574
596
  }
597
+ /** Confirmed createDeposit receipt from an externally executed prepare() plan. */
598
+ interface PreparedCashoutReceipt {
599
+ transactionHash: Hash;
600
+ status: 'success' | 'reverted';
601
+ logs: readonly Log[];
602
+ }
575
603
  interface WithdrawResult {
576
604
  depositId: string;
577
605
  /** Present when expired intents had to be pruned before withdrawal. */
@@ -597,10 +625,11 @@ interface CashClient {
597
625
  includeRelaySources: true;
598
626
  }): Promise<CashCapabilities>;
599
627
  /**
600
- * 0c - Raw 30-day demand and first-fill speed evidence keyed by
601
- * `platform:currency`. A recommended consumer gate is `fills >= 10 &&
602
- * medianFillSeconds <= 48h`; fail open to the full capability catalog when
603
- * stats are unavailable or the gate would remove every pair.
628
+ * 0c - Raw 30-day demand and first-fill speed evidence keyed by an exact
629
+ * `platform:currency` pair or sorted multi-currency set. A recommended
630
+ * consumer gate is `fills >= 10 && medianFillSeconds <= 48h`; fail open to
631
+ * the full capability catalog when stats are unavailable or the gate would
632
+ * remove every pair.
604
633
  */
605
634
  fillStats(): Promise<CashFillStats>;
606
635
  /** Relay-only source discovery helper. */
@@ -624,6 +653,8 @@ interface CashClient {
624
653
  cashout(input: CashoutInput, opts: CashoutOptions): Promise<CashoutResult>;
625
654
  /** 2b - Unsigned path: `txs[]` for agent wallets, AA, server keys, policy layers. */
626
655
  prepare(input: CashoutInput): Promise<PrepareResult>;
656
+ /** Resolve an externally executed createDeposit receipt into resumable cash-out state. */
657
+ finalizePreparedCashout(receipt: PreparedCashoutReceipt): CashoutResult;
627
658
  /** 3 - Observe: resumable from `depositId` alone; no session state anywhere. */
628
659
  order(depositId: string): Promise<CashOrder>;
629
660
  /**
@@ -662,4 +693,4 @@ interface CashClient {
662
693
  }
663
694
  declare function createCashClient(options: CashClientOptions): CashClient;
664
695
 
665
- export { type CashPlatformCapability as A, type CashPreparedStepKind as B, type CashPayoutInfo as C, type CashoutInput as D, type CashoutOptions as E, type CuratorPayeeDataInput as F, type EstimateInput as G, type EstimateOptions as H, type IntentStatus as I, RECOMMENDED_MIN_CASHOUT_AMOUNT as J, type RelayOptions as K, type RelayQuoteInput as L, MIN_CASHOUT_AMOUNT as M, type RelaySourceInput as N, type OrdersOptions as O, type PrepareResult as P, type RelayTransaction as Q, type RelayExecutionResult as R, type SignerOptions as S, type TopUpResult as T, type WatchOptions as U, type WithdrawOptions as V, type WithdrawResult as W, buildCapabilities as X, createCashClient as Y, type IntentEntity as a, type CashBuyerProfile as b, type CashDepositInput as c, type CreateDepositParamsArg as d, type CashOrder as e, type CashFill as f, type CashCapabilities as g, type CashoutResult as h, type CashEstimate as i, type CashFillStats as j, type CashPreparedStep as k, type RelayQuote as l, type RelayStatus as m, type CashSourceCapabilities as n, CASH_ATTRIBUTION_CODE as o, type CashAsset as p, type CashChain as q, type CashClient as r, type CashClientOptions as s, type CashFillEta as t, type CashLeg as u, type CashNextAction as v, type CashOrderState as w, type CashPairFillStats as x, type CashPayout as y, type CashPayoutPricing as z };
696
+ export { createCashClient as $, type CashPayout as A, type CashPayoutPricing as B, type CashPayoutInfo as C, type CashPlatformCapability as D, type CashPreparedStepKind as E, type CashoutInput as F, type CashoutOptions as G, type CuratorPayeeDataInput as H, type IntentStatus as I, type EstimateInput as J, type EstimateOptions as K, type PreparedCashoutReceipt as L, MIN_CASHOUT_AMOUNT as M, RECOMMENDED_MIN_CASHOUT_AMOUNT as N, type OrdersOptions as O, type PrepareResult as P, type RelayOptions as Q, type RelayExecutionResult as R, type RelayQuoteInput as S, type TopUpResult as T, type RelaySourceInput as U, type RelayTransaction as V, type WithdrawResult as W, type SignerOptions as X, type WatchOptions as Y, type WithdrawOptions as Z, buildCapabilities as _, type IntentEntity as a, normalizeCashPayee as a0, type CashBuyerProfile as b, type CashDepositInput as c, type CreateDepositParamsArg as d, type CashOrder as e, type CashFill as f, type CashCapabilities as g, type CashoutResult as h, type CashEstimate as i, type CashFillStats as j, type CashPreparedStep as k, type RelayQuote as l, type RelayStatus as m, type CashSourceCapabilities as n, CASH_ATTRIBUTION_CODE as o, type CashAsset as p, type CashChain as q, type CashClient as r, type CashClientOptions as s, type CashFillEta as t, type CashLeg as u, type CashMultiCurrencyLeg as v, type CashNextAction as w, type CashOrderState as x, type CashPairFillStats as y, type CashPayeeInput as z };
@@ -1,4 +1,4 @@
1
- import { Address, WalletClient, Hash, Transport } from 'viem';
1
+ import { Address, WalletClient, Hash, Log, Transport } from 'viem';
2
2
  import { IndexerIntentStatus, Zkp2pClient, IndexerIntent, CurrencyType, PreparedTransaction, RuntimeEnv } from '@zkp2p/sdk';
3
3
  import { Execute, RelayClient, RelayChain, ProgressData } from '@relayprotocol/relay-sdk';
4
4
 
@@ -172,15 +172,20 @@ interface CashBuyerProfile {
172
172
  firstSeenAt?: number;
173
173
  lastSeenAt?: number;
174
174
  }
175
- /** A single payout leg of a cash-out (one platform + currency + payee). */
176
- interface CashPayout {
175
+ interface CashPayoutBase {
177
176
  /** Payment platform / processor name, e.g. `'venmo'`, `'revolut'`, `'wise'`. */
178
177
  processorName: string;
179
- /** Fiat currency the user wants to receive. */
180
- currency: CurrencyType;
181
178
  /** The user's payee handle for that platform (e.g. a Venmo username, Wisetag). */
182
179
  payeeData: CuratorPayeeDataInput;
183
180
  }
181
+ /** One payment method offering either one currency or a non-empty currency set. */
182
+ type CashPayout = CashPayoutBase & ({
183
+ currency: CurrencyType;
184
+ currencies?: never;
185
+ } | {
186
+ currency?: never;
187
+ currencies: readonly [CurrencyType, ...CurrencyType[]];
188
+ });
184
189
  /**
185
190
  * Input to create a market-rate (0% spread) cash-out deposit.
186
191
  *
@@ -193,7 +198,7 @@ interface CashDepositInput {
193
198
  token?: Address;
194
199
  /** Total amount to cash out, in USDC base units (6 decimals). */
195
200
  amount: bigint;
196
- /** One or more payout legs (platform + currency + payee). */
201
+ /** One or more payout legs (platform + currency choice + payee). */
197
202
  payouts: CashPayout[];
198
203
  /** Per-order min/max in USDC base units. Defaults derive from {@link buildIntentAmountRange}. */
199
204
  intentAmountRange?: {
@@ -358,12 +363,15 @@ interface CashCapabilities {
358
363
  declare function buildCapabilities(environment: RuntimeEnv): CashCapabilities;
359
364
 
360
365
  interface CashPairFillStats {
361
- /** Fulfilled intents through this pair inside the rolling 30-day window. */
366
+ /** Fulfilled intents through this pair or currency set inside the rolling 30-day window. */
362
367
  fills: number;
363
- /** Median deposit-to-first-fill seconds, sampled once per deposit for this pair. */
368
+ /** Median deposit-to-first-fill seconds, sampled once per deposit for this pair or set. */
364
369
  medianFillSeconds?: number;
365
370
  }
366
- /** Raw demand and speed evidence keyed by `basePlatform:currencyCode`. */
371
+ /**
372
+ * Raw demand and speed evidence keyed by `basePlatform:currencyCode` or a
373
+ * sorted multi-currency set such as `revolut:EUR+GBP+USD`.
374
+ */
367
375
  type CashFillStats = Record<string, CashPairFillStats>;
368
376
  interface CashFillEta {
369
377
  /** Simple headline ETA from recent deposits. Undefined when no recent sample exists. */
@@ -435,8 +443,12 @@ interface CashEstimate {
435
443
  eta?: CashFillEta;
436
444
  }
437
445
 
446
+ type CashPayeeInput = string | CuratorPayeeDataInput;
447
+ /** Convert user-entered handles into the curator form for a payment platform. */
448
+ declare function normalizeCashPayee(platform: string, payee: CashPayeeInput): CuratorPayeeDataInput;
449
+
438
450
  /**
439
- * `createCashClient` - the eight-verb facade over a read-only `Zkp2pClient`.
451
+ * `createCashClient` - the cash lifecycle facade over a read-only `Zkp2pClient`.
440
452
  *
441
453
  * The facade keeps the outward surface tiny (capabilities / estimate / cashout
442
454
  * / order / orders / watch / withdraw / topUp) while reusing the published
@@ -481,8 +493,18 @@ interface CashLeg {
481
493
  platform: string;
482
494
  /** Fiat currency to receive. */
483
495
  currency: CurrencyType;
484
- /** Payee details, e.g. `{ offchainId: '@andrew' }`. */
485
- payee: CuratorPayeeDataInput;
496
+ /** Raw handle or prepared curator data (needed for identity attestations). */
497
+ payee: CashPayeeInput;
498
+ currencies?: never;
499
+ }
500
+ interface CashMultiCurrencyLeg {
501
+ /** Platform id from `capabilities()`, e.g. `'revolut'`. */
502
+ platform: string;
503
+ /** Fiat currencies a buyer may use to fill this cash-out. */
504
+ currencies: readonly [CurrencyType, ...CurrencyType[]];
505
+ /** Raw handle or prepared curator data shared by every offered currency. */
506
+ payee: CashPayeeInput;
507
+ currency?: never;
486
508
  }
487
509
  interface CashoutInput {
488
510
  /**
@@ -498,8 +520,8 @@ interface CashoutInput {
498
520
  /** Relay amount mode. Omit for the recommended exact source-input flow. */
499
521
  tradeType?: 'EXACT_INPUT' | 'EXACT_OUTPUT' | 'EXPECTED_OUTPUT';
500
522
  };
501
- /** Where the fiat should arrive. Multi-payout is a deliberate v1 cut. */
502
- receive: CashLeg;
523
+ /** Where the fiat should arrive. One method may offer multiple currencies. */
524
+ receive: CashLeg | CashMultiCurrencyLeg;
503
525
  /** Per-order min/max override (USDC base units). */
504
526
  intentAmountRange?: {
505
527
  min: bigint;
@@ -572,6 +594,12 @@ interface PrepareResult {
572
594
  hashedOnchainIds: string[];
573
595
  };
574
596
  }
597
+ /** Confirmed createDeposit receipt from an externally executed prepare() plan. */
598
+ interface PreparedCashoutReceipt {
599
+ transactionHash: Hash;
600
+ status: 'success' | 'reverted';
601
+ logs: readonly Log[];
602
+ }
575
603
  interface WithdrawResult {
576
604
  depositId: string;
577
605
  /** Present when expired intents had to be pruned before withdrawal. */
@@ -597,10 +625,11 @@ interface CashClient {
597
625
  includeRelaySources: true;
598
626
  }): Promise<CashCapabilities>;
599
627
  /**
600
- * 0c - Raw 30-day demand and first-fill speed evidence keyed by
601
- * `platform:currency`. A recommended consumer gate is `fills >= 10 &&
602
- * medianFillSeconds <= 48h`; fail open to the full capability catalog when
603
- * stats are unavailable or the gate would remove every pair.
628
+ * 0c - Raw 30-day demand and first-fill speed evidence keyed by an exact
629
+ * `platform:currency` pair or sorted multi-currency set. A recommended
630
+ * consumer gate is `fills >= 10 && medianFillSeconds <= 48h`; fail open to
631
+ * the full capability catalog when stats are unavailable or the gate would
632
+ * remove every pair.
604
633
  */
605
634
  fillStats(): Promise<CashFillStats>;
606
635
  /** Relay-only source discovery helper. */
@@ -624,6 +653,8 @@ interface CashClient {
624
653
  cashout(input: CashoutInput, opts: CashoutOptions): Promise<CashoutResult>;
625
654
  /** 2b - Unsigned path: `txs[]` for agent wallets, AA, server keys, policy layers. */
626
655
  prepare(input: CashoutInput): Promise<PrepareResult>;
656
+ /** Resolve an externally executed createDeposit receipt into resumable cash-out state. */
657
+ finalizePreparedCashout(receipt: PreparedCashoutReceipt): CashoutResult;
627
658
  /** 3 - Observe: resumable from `depositId` alone; no session state anywhere. */
628
659
  order(depositId: string): Promise<CashOrder>;
629
660
  /**
@@ -662,4 +693,4 @@ interface CashClient {
662
693
  }
663
694
  declare function createCashClient(options: CashClientOptions): CashClient;
664
695
 
665
- export { type CashPlatformCapability as A, type CashPreparedStepKind as B, type CashPayoutInfo as C, type CashoutInput as D, type CashoutOptions as E, type CuratorPayeeDataInput as F, type EstimateInput as G, type EstimateOptions as H, type IntentStatus as I, RECOMMENDED_MIN_CASHOUT_AMOUNT as J, type RelayOptions as K, type RelayQuoteInput as L, MIN_CASHOUT_AMOUNT as M, type RelaySourceInput as N, type OrdersOptions as O, type PrepareResult as P, type RelayTransaction as Q, type RelayExecutionResult as R, type SignerOptions as S, type TopUpResult as T, type WatchOptions as U, type WithdrawOptions as V, type WithdrawResult as W, buildCapabilities as X, createCashClient as Y, type IntentEntity as a, type CashBuyerProfile as b, type CashDepositInput as c, type CreateDepositParamsArg as d, type CashOrder as e, type CashFill as f, type CashCapabilities as g, type CashoutResult as h, type CashEstimate as i, type CashFillStats as j, type CashPreparedStep as k, type RelayQuote as l, type RelayStatus as m, type CashSourceCapabilities as n, CASH_ATTRIBUTION_CODE as o, type CashAsset as p, type CashChain as q, type CashClient as r, type CashClientOptions as s, type CashFillEta as t, type CashLeg as u, type CashNextAction as v, type CashOrderState as w, type CashPairFillStats as x, type CashPayout as y, type CashPayoutPricing as z };
696
+ export { createCashClient as $, type CashPayout as A, type CashPayoutPricing as B, type CashPayoutInfo as C, type CashPlatformCapability as D, type CashPreparedStepKind as E, type CashoutInput as F, type CashoutOptions as G, type CuratorPayeeDataInput as H, type IntentStatus as I, type EstimateInput as J, type EstimateOptions as K, type PreparedCashoutReceipt as L, MIN_CASHOUT_AMOUNT as M, RECOMMENDED_MIN_CASHOUT_AMOUNT as N, type OrdersOptions as O, type PrepareResult as P, type RelayOptions as Q, type RelayExecutionResult as R, type RelayQuoteInput as S, type TopUpResult as T, type RelaySourceInput as U, type RelayTransaction as V, type WithdrawResult as W, type SignerOptions as X, type WatchOptions as Y, type WithdrawOptions as Z, buildCapabilities as _, type IntentEntity as a, normalizeCashPayee as a0, type CashBuyerProfile as b, type CashDepositInput as c, type CreateDepositParamsArg as d, type CashOrder as e, type CashFill as f, type CashCapabilities as g, type CashoutResult as h, type CashEstimate as i, type CashFillStats as j, type CashPreparedStep as k, type RelayQuote as l, type RelayStatus as m, type CashSourceCapabilities as n, CASH_ATTRIBUTION_CODE as o, type CashAsset as p, type CashChain as q, type CashClient as r, type CashClientOptions as s, type CashFillEta as t, type CashLeg as u, type CashMultiCurrencyLeg as v, type CashNextAction as w, type CashOrderState as x, type CashPairFillStats as y, type CashPayeeInput as z };