@zkp2p/cash 0.4.6 → 0.4.8

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
@@ -9,9 +9,9 @@ can withdraw an unmatched deposit.
9
9
 
10
10
  ## Decision tree: pick your entry point
11
11
 
12
- 1. **You control a signer in-process** (viem `WalletClient`, e.g. a local
13
- key or embedded wallet) → use `cashout()` / `topUp()` / `withdraw()`
14
- directly.
12
+ 1. **You control a signer in-process** (any viem `WalletClient`, including a
13
+ local or externally connected EOA) → use `cashout()` / `topUp()` /
14
+ `withdraw()` directly. No Privy wallet is required.
15
15
  2. **Signing happens elsewhere** (AA bundler, policy engine, custody service,
16
16
  human approval step) → use `prepare()` / `prepareTopUp()` /
17
17
  `prepareWithdraw()`. Each returns unsigned `txs[]`
@@ -34,11 +34,13 @@ deposit-level integration share instead of applying maker L1/L2.
34
34
 
35
35
  **Platform caveats, all surfaced in `capabilities()`:**
36
36
 
37
- - **Venmo, Cash App, and PayPal** carry
38
- `requiresAtomicAccessPolicy: true`. Generic `cashout()` and `prepare()` reject
39
- them with `ATOMIC_ACCESS_POLICY_REQUIRED` before any side effect. Use Peer
40
- web's Curator-backed atomic flow or a host with the same atomic guarantee;
41
- never create the deposit and attach the group policy sequentially.
37
+ - **Venmo, Cash App, and PayPal attach access groups by default.** Signed
38
+ `cashout()` confirms `createDeposit`, then submits and confirms the Plus, Pro,
39
+ Peer Makers, and Peer Pay policy using the same viem wallet. This is a
40
+ deliberate non-atomic follow-up with a brief unprotected interval. For
41
+ `prepare()`, check `accessPolicyRequired`; after `createDeposit` confirms,
42
+ finalize its receipt and submit `prepareAccessPolicy(depositId)` with the
43
+ depositor. Any viem EOA works; Privy is not required.
42
44
 
43
45
  - **Wise and PayPal** carry `requiresIdentityAttestation: true`. A new curator
44
46
  registration needs a signed maker identity attestation this SDK cannot mint
@@ -214,8 +216,8 @@ Every `CashError` carries `code`, `retryable`, `remediation`. Behavior:
214
216
  | `INVALID_PAYOUT_PLATFORMS` | no | Pass one leg or an array of legs, using each platform at most once |
215
217
  | `PAYEE_VERIFICATION_REQUIRED` | no | Register a new Wise/PayPal payee through Peer; an existing registered handle can be reused |
216
218
  | `PAYEE_REGISTRATION_FAILED` | yes | Validate against `payeeHint`, then retry |
217
- | `ATOMIC_ACCESS_POLICY_REQUIRED` | no | Use Peer web or an equivalent atomic host for Venmo, Cash App, or PayPal; nothing moved |
218
- | `ACCESS_POLICY_CONFIGURATION_FAILED` | no | Recover an existing `0.4.4` deposit from its policy recovery data; do not create it again |
219
+ | `ATOMIC_ACCESS_POLICY_REQUIRED` | no | Deprecated compatibility code; current SDK flows never emit it |
220
+ | `ACCESS_POLICY_CONFIGURATION_FAILED` | no | Deposit exists; retry its policy with `recovery.depositId`, never create another cash-out |
219
221
  | `SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE` | no | Execute Relay with a signer first, then prepare a Base-USDC cashout |
220
222
  | `SOURCE_RECIPIENT_MISMATCH` | no | Route Base USDC to the cashout depositor |
221
223
  | `SOURCE_CAPABILITIES_FAILED` | yes | Retry discovery or fall back to Base USDC |
package/README.md CHANGED
@@ -46,9 +46,9 @@ const multiCurrencyStats = fillStats['revolut:EUR+GBP+USD'];
46
46
  const { depositId } = await cash.cashout(
47
47
  {
48
48
  amount: usdc(1000),
49
- receive: { platform: 'chime', currency: 'USD', payee: '$you' },
49
+ receive: { platform: 'venmo', currency: 'USD', payee: '@you' },
50
50
  },
51
- { signer }, // any viem WalletClient on Base
51
+ { signer }, // any viem WalletClient on Base, including an EOA
52
52
  );
53
53
 
54
54
  // One method can offer several currencies. The buyer chooses the fill
@@ -71,7 +71,7 @@ const widestReach = await cash.cashout(
71
71
  {
72
72
  amount: usdc(1000),
73
73
  receive: [
74
- { platform: 'chime', currency: 'USD', payee: '$you' },
74
+ { platform: 'venmo', currency: 'USD', payee: '@you' },
75
75
  { platform: 'revolut', currencies: ['EUR', 'GBP'], payee: { offchainId: 'revtag' } },
76
76
  ],
77
77
  },
@@ -120,7 +120,7 @@ const { depositId, source } = await cash.cashout(
120
120
  currency: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
121
121
  tradeType: 'EXACT_INPUT',
122
122
  },
123
- receive: { platform: 'chime', currency: 'USD', payee: { offchainId: '$you' } },
123
+ receive: { platform: 'venmo', currency: 'USD', payee: { offchainId: '@you' } },
124
124
  },
125
125
  { signer, sourceSigner },
126
126
  );
@@ -141,9 +141,9 @@ console.log(source?.transactions?.origin, source?.transactions?.destination);
141
141
  | `quoteSource(input)` / `executeSourceQuote(quote, { signer })` | Relay SDK EVM source routing into Base USDC before cashout |
142
142
  | `relayStatus(requestId)` | Relay request status from the Relay SDK request path |
143
143
  | `estimate({ amount, currency }, { includeEta? })` | Base USDC oracle estimate; optionally skip the historical ETA for progressive rendering |
144
- | `cashout(input, { signer })` | Registers the payee and creates an unrestricted-rail order; protected rails fail before any side effect |
145
- | `prepare(input)` / `finalizePreparedCashout(receipt)` | Prepare unrestricted-rail external signing, then resolve the confirmed createDeposit receipt into resumable state |
146
- | `prepareAccessPolicy(depositId)` | Recover a restricted-rail deposit created by the unsafe `0.4.4` sequential flow; never use it to create a new order |
144
+ | `cashout(input, { signer })` | Creates the order with any viem wallet; Venmo, Cash App, and PayPal then attach the canonical access groups |
145
+ | `prepare(input)` / `finalizePreparedCashout(receipt)` | Prepare external signing, resolve the deposit, then check `accessPolicyRequired` for the follow-up |
146
+ | `prepareAccessPolicy(depositId)` | Prepare the post-deposit Plus, Pro, Peer Makers, and Peer Pay policy transaction |
147
147
  | `order(depositId)` / `orders(owner)` | Resume any order from its id alone; list all orders for a wallet |
148
148
  | `watch(depositId)` | Async iterator: yields on every state change until terminal, abort, or timeout |
149
149
  | `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) |
@@ -165,21 +165,22 @@ Peer Cash transaction, including approves, carries ERC-8021 attribution:
165
165
  `peer-cash` first, optional `peer-ref-XXXXXX` from `referralCode` next, and your
166
166
  analytics-only `referrer` code(s) after it.
167
167
 
168
- Venmo, Cash App, and PayPal must attach Plus, Pro, Peer Makers, and Peer Pay in
169
- the same atomic operation that creates the deposit. A create-then-configure
170
- sequence leaves a public interval in which an unapproved taker can signal an
171
- intent. The generic `cashout()` and `prepare()` paths therefore throw
172
- `ATOMIC_ACCESS_POLICY_REQUIRED` before wallet access, Relay quoting, payee
173
- registration, allowance, or deposit submission. Peer web supports these rails
174
- through its atomic Curator-backed flow. Other hosts must provide an equivalent
175
- atomic batch rather than composing the two public SDK calls sequentially.
176
- `capabilities()` marks these platforms with
177
- `requiresAtomicAccessPolicy: true`.
178
-
179
- `prepareAccessPolicy(depositId)` remains available only to recover a restricted
180
- deposit already created by `0.4.4`. Configure that existing deposit immediately;
181
- do not call `cashout()` again and do not use this recovery helper as a new-order
182
- flow.
168
+ No platform requires an atomic access-policy flow. `cashout()` and `prepare()`
169
+ work with any viem `WalletClient`, including a local or externally connected
170
+ EOA; no Privy wallet or signer API is required. The deprecated
171
+ `requiresAtomicAccessPolicy` capability remains for wire compatibility and is
172
+ always `false`.
173
+
174
+ Venmo, Cash App, and PayPal cash-outs attach Plus, Pro, Peer Makers, and Peer
175
+ Pay groups by default. Signed `cashout()` creates the deposit first, then uses
176
+ the same wallet to submit and confirm the policy transaction; this intentionally
177
+ leaves a brief non-atomic interval. Prepared integrations receive
178
+ `accessPolicyRequired: true` for those platforms and, after confirming
179
+ `createDeposit`, must call `finalizePreparedCashout(receipt)` followed by
180
+ `prepareAccessPolicy(depositId)`. Other platforms do not need the follow-up.
181
+ If policy attachment fails, `ACCESS_POLICY_CONFIGURATION_FAILED.recovery`
182
+ identifies the existing deposit and any submitted policy transaction. Do not
183
+ create another cash-out; resume the policy step with the same depositor wallet.
183
184
 
184
185
  `capabilities()` presents Zelle as one platform. A cashout with
185
186
  `receive.platform: 'zelle'` attaches only the generic Zelle payment method to
@@ -239,6 +240,9 @@ they are available. A source-routed result includes both a flat
239
240
  - `TRANSACTION_SUBMISSION_UNKNOWN`: a Base-only cashout or another mutation
240
241
  returned no hash. Treat it as potentially broadcast. Inspect recent Base
241
242
  wallet activity and the supplied recovery action before any retry.
243
+ - `ACCESS_POLICY_CONFIGURATION_FAILED`: the deposit exists, but its required
244
+ Venmo, Cash App, or PayPal policy was not confirmed. Do not cash out again;
245
+ retry `prepareAccessPolicy(error.recovery.depositId)` with the depositor.
242
246
 
243
247
  Wallet clients pinned to the wrong chain fail with `SIGNER_CHAIN_MISMATCH`
244
248
  before a quote or transaction is submitted. Chainless wallets are checked
@@ -198,11 +198,12 @@ var errors = {
198
198
  },
199
199
  { cause }
200
200
  ),
201
+ /** @deprecated Cash-outs no longer require an atomic access-policy flow. */
201
202
  atomicAccessPolicyRequired: (platforms) => new CashError({
202
203
  code: "ATOMIC_ACCESS_POLICY_REQUIRED",
203
- message: `${platforms.join(", ")} cash-outs require atomic deposit creation and access-policy configuration.`,
204
+ message: `Atomic access-policy enforcement for ${platforms.join(", ")} is deprecated.`,
204
205
  retryable: false,
205
- remediation: `Use Peer web or a host that atomically batches guard-before, createDeposit, guard-after, and configureDeposit. Nothing was submitted by this call.`
206
+ remediation: `Upgrade @zkp2p/cash; current cash-out flows do not require atomic access-policy configuration.`
206
207
  }),
207
208
  sourceRouteUnsupportedInPrepare: () => new CashError({
208
209
  code: "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE",
@@ -405,20 +406,28 @@ var errors = {
405
406
  },
406
407
  { cause }
407
408
  ),
408
- accessPolicyConfigurationFailed: (depositId, groupIds, cause, transactionHash) => new CashError(
409
+ accessPolicyConfigurationFailed: (depositId, groupIds, context = {}) => new CashError(
409
410
  {
410
411
  code: "ACCESS_POLICY_CONFIGURATION_FAILED",
411
- message: `Cash-out deposit ${depositId} was created, but its required access policy was not confirmed.`,
412
+ message: `Cash-out deposit ${depositId} was created, but its access policy could not be confirmed.`,
412
413
  retryable: false,
413
- remediation: `Do not call cashout() again. Inspect the existing deposit and any access-policy transaction, then configure that deposit with recovery.groupIds through @zkp2p/sdk accessPolicy.`,
414
+ remediation: `Do not create another cash-out. Submit prepareAccessPolicy(recovery.depositId) with the same depositor wallet, then confirm that transaction.`,
414
415
  recovery: {
415
416
  kind: "configure-cashout-access-policy",
416
417
  depositId,
417
418
  groupIds: [...groupIds],
418
- ...transactionHash ? { transactionHash } : {}
419
+ ...context.transactionHash ? { transactionHash: context.transactionHash } : {},
420
+ ...context.source ? {
421
+ source: {
422
+ amount: context.source.amount.toString(),
423
+ ...context.source.requestId ? { requestId: context.source.requestId } : {},
424
+ txHashes: context.source.txHashes,
425
+ ...context.source.transactions ? { transactions: context.source.transactions } : {}
426
+ }
427
+ } : {}
419
428
  }
420
429
  },
421
- { cause }
430
+ { cause: context.cause }
422
431
  ),
423
432
  escrowPaused: () => new CashError({
424
433
  code: "ESCROW_PAUSED",
@@ -311,10 +311,7 @@ interface CashPlatformCapability {
311
311
  * `PAYEE_VERIFICATION_REQUIRED`.
312
312
  */
313
313
  requiresIdentityAttestation: boolean;
314
- /**
315
- * When true, the platform needs deposit creation and access-policy setup in
316
- * one atomic host operation. Generic `cashout()` and `prepare()` fail closed.
317
- */
314
+ /** @deprecated Always false. Restricted-platform policies are attached sequentially. */
318
315
  requiresAtomicAccessPolicy: boolean;
319
316
  }
320
317
  interface CashCapabilities {
@@ -549,7 +546,7 @@ interface CashoutInput {
549
546
  };
550
547
  }
551
548
  interface SignerOptions {
552
- /** A viem WalletClient with an account, on Base. */
549
+ /** Any viem WalletClient with a Base account, including a local or external EOA. */
553
550
  signer: WalletClient;
554
551
  }
555
552
  interface CashoutOptions extends SignerOptions {
@@ -587,7 +584,7 @@ interface CashoutResult {
587
584
  onchainDepositId: bigint;
588
585
  /** Optimistic snapshot (`awaiting-buyer`); poll `order(depositId)` for live state. */
589
586
  order: CashOrder;
590
- /** @deprecated Only present in recovery data produced by the unsafe 0.4.4 sequential flow. */
587
+ /** Confirmed access-policy transaction for Venmo, Cash App, or PayPal cash-outs. */
591
588
  accessPolicyTxHash?: Hash;
592
589
  /** Present when `cashout()` first routed a source asset through Relay. */
593
590
  source?: {
@@ -615,7 +612,7 @@ interface PrepareResult {
615
612
  register: {
616
613
  hashedOnchainIds: string[];
617
614
  };
618
- /** @deprecated Always false for new plans; restricted rails require an atomic host flow. */
615
+ /** Whether the host must submit `prepareAccessPolicy(depositId)` after `createDeposit`. */
619
616
  accessPolicyRequired: boolean;
620
617
  }
621
618
  /** Confirmed createDeposit receipt from an externally executed prepare() plan. */
@@ -679,7 +676,7 @@ interface CashClient {
679
676
  prepare(input: CashoutInput): Promise<PrepareResult>;
680
677
  /** Resolve an externally executed createDeposit receipt into resumable cash-out state. */
681
678
  finalizePreparedCashout(receipt: PreparedCashoutReceipt): CashoutResult;
682
- /** Recover an existing 0.4.4 restricted-rail deposit by preparing its four-group policy. */
679
+ /** Prepare the required four-group follow-up for a restricted cash-out. */
683
680
  prepareAccessPolicy(depositId: string): PreparedTransaction;
684
681
  /** 3 - Observe: resumable from `depositId` alone; no session state anywhere. */
685
682
  order(depositId: string): Promise<CashOrder>;
@@ -311,10 +311,7 @@ interface CashPlatformCapability {
311
311
  * `PAYEE_VERIFICATION_REQUIRED`.
312
312
  */
313
313
  requiresIdentityAttestation: boolean;
314
- /**
315
- * When true, the platform needs deposit creation and access-policy setup in
316
- * one atomic host operation. Generic `cashout()` and `prepare()` fail closed.
317
- */
314
+ /** @deprecated Always false. Restricted-platform policies are attached sequentially. */
318
315
  requiresAtomicAccessPolicy: boolean;
319
316
  }
320
317
  interface CashCapabilities {
@@ -549,7 +546,7 @@ interface CashoutInput {
549
546
  };
550
547
  }
551
548
  interface SignerOptions {
552
- /** A viem WalletClient with an account, on Base. */
549
+ /** Any viem WalletClient with a Base account, including a local or external EOA. */
553
550
  signer: WalletClient;
554
551
  }
555
552
  interface CashoutOptions extends SignerOptions {
@@ -587,7 +584,7 @@ interface CashoutResult {
587
584
  onchainDepositId: bigint;
588
585
  /** Optimistic snapshot (`awaiting-buyer`); poll `order(depositId)` for live state. */
589
586
  order: CashOrder;
590
- /** @deprecated Only present in recovery data produced by the unsafe 0.4.4 sequential flow. */
587
+ /** Confirmed access-policy transaction for Venmo, Cash App, or PayPal cash-outs. */
591
588
  accessPolicyTxHash?: Hash;
592
589
  /** Present when `cashout()` first routed a source asset through Relay. */
593
590
  source?: {
@@ -615,7 +612,7 @@ interface PrepareResult {
615
612
  register: {
616
613
  hashedOnchainIds: string[];
617
614
  };
618
- /** @deprecated Always false for new plans; restricted rails require an atomic host flow. */
615
+ /** Whether the host must submit `prepareAccessPolicy(depositId)` after `createDeposit`. */
619
616
  accessPolicyRequired: boolean;
620
617
  }
621
618
  /** Confirmed createDeposit receipt from an externally executed prepare() plan. */
@@ -679,7 +676,7 @@ interface CashClient {
679
676
  prepare(input: CashoutInput): Promise<PrepareResult>;
680
677
  /** Resolve an externally executed createDeposit receipt into resumable cash-out state. */
681
678
  finalizePreparedCashout(receipt: PreparedCashoutReceipt): CashoutResult;
682
- /** Recover an existing 0.4.4 restricted-rail deposit by preparing its four-group policy. */
679
+ /** Prepare the required four-group follow-up for a restricted cash-out. */
683
680
  prepareAccessPolicy(depositId: string): PreparedTransaction;
684
681
  /** 3 - Observe: resumable from `depositId` alone; no session state anywhere. */
685
682
  order(depositId: string): Promise<CashOrder>;
package/dist/index.cjs CHANGED
@@ -498,7 +498,7 @@ function buildCapabilities(environment) {
498
498
  currencies: [...new Set(currencies2)].sort(),
499
499
  payeeHint: PAYEE_HINTS[platform] ?? "Your payment handle for this platform",
500
500
  requiresIdentityAttestation: IDENTITY_ATTESTATION_PLATFORMS.has(platform),
501
- requiresAtomicAccessPolicy: CASH_RESTRICTED_PLATFORMS.has(platform)
501
+ requiresAtomicAccessPolicy: false
502
502
  };
503
503
  }).filter((p) => p.currencies.length > 0).sort((a, b) => a.platform.localeCompare(b.platform));
504
504
  const currencies = [...new Set(platforms.flatMap((p) => p.currencies))].sort();
@@ -680,11 +680,12 @@ var errors = {
680
680
  },
681
681
  { cause }
682
682
  ),
683
+ /** @deprecated Cash-outs no longer require an atomic access-policy flow. */
683
684
  atomicAccessPolicyRequired: (platforms) => new CashError({
684
685
  code: "ATOMIC_ACCESS_POLICY_REQUIRED",
685
- message: `${platforms.join(", ")} cash-outs require atomic deposit creation and access-policy configuration.`,
686
+ message: `Atomic access-policy enforcement for ${platforms.join(", ")} is deprecated.`,
686
687
  retryable: false,
687
- remediation: `Use Peer web or a host that atomically batches guard-before, createDeposit, guard-after, and configureDeposit. Nothing was submitted by this call.`
688
+ remediation: `Upgrade @zkp2p/cash; current cash-out flows do not require atomic access-policy configuration.`
688
689
  }),
689
690
  sourceRouteUnsupportedInPrepare: () => new CashError({
690
691
  code: "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE",
@@ -887,20 +888,28 @@ var errors = {
887
888
  },
888
889
  { cause }
889
890
  ),
890
- accessPolicyConfigurationFailed: (depositId, groupIds, cause, transactionHash) => new CashError(
891
+ accessPolicyConfigurationFailed: (depositId, groupIds, context = {}) => new CashError(
891
892
  {
892
893
  code: "ACCESS_POLICY_CONFIGURATION_FAILED",
893
- message: `Cash-out deposit ${depositId} was created, but its required access policy was not confirmed.`,
894
+ message: `Cash-out deposit ${depositId} was created, but its access policy could not be confirmed.`,
894
895
  retryable: false,
895
- remediation: `Do not call cashout() again. Inspect the existing deposit and any access-policy transaction, then configure that deposit with recovery.groupIds through @zkp2p/sdk accessPolicy.`,
896
+ remediation: `Do not create another cash-out. Submit prepareAccessPolicy(recovery.depositId) with the same depositor wallet, then confirm that transaction.`,
896
897
  recovery: {
897
898
  kind: "configure-cashout-access-policy",
898
899
  depositId,
899
900
  groupIds: [...groupIds],
900
- ...transactionHash ? { transactionHash } : {}
901
+ ...context.transactionHash ? { transactionHash: context.transactionHash } : {},
902
+ ...context.source ? {
903
+ source: {
904
+ amount: context.source.amount.toString(),
905
+ ...context.source.requestId ? { requestId: context.source.requestId } : {},
906
+ txHashes: context.source.txHashes,
907
+ ...context.source.transactions ? { transactions: context.source.transactions } : {}
908
+ }
909
+ } : {}
901
910
  }
902
911
  },
903
- { cause }
912
+ { cause: context.cause }
904
913
  ),
905
914
  escrowPaused: () => new CashError({
906
915
  code: "ESCROW_PAUSED",
@@ -1786,6 +1795,11 @@ async function submitAndConfirm(client, verb, send) {
1786
1795
  function isKnownPreBroadcastFailure(mapped) {
1787
1796
  return mapped.code === "TRANSACTION_REJECTED" || mapped.code === "INSUFFICIENT_TOKEN_BALANCE" || mapped.code === "ALLOWANCE_NOT_VISIBLE" || mapped.code === "ESCROW_PAUSED";
1788
1797
  }
1798
+ function requiresCashoutAccessPolicy(depositInput) {
1799
+ return depositInput.payouts.some(
1800
+ (payout) => CASH_RESTRICTED_PLATFORMS.has(payout.processorName.toLowerCase())
1801
+ );
1802
+ }
1789
1803
  function depositOrderOptions(deposit) {
1790
1804
  const remaining = toBigIntOrUndefined(deposit.remainingDeposits);
1791
1805
  const outstanding = toBigIntOrUndefined(deposit.outstandingIntentAmount);
@@ -1917,12 +1931,6 @@ function createCashClient(options) {
1917
1931
  ...range ? { intentAmountRange: range } : {}
1918
1932
  };
1919
1933
  }
1920
- function assertGenericCashoutSupported(payoutInput) {
1921
- const restrictedPlatforms = payoutInput.payouts.map((payout) => payout.processorName.toLowerCase()).filter((platform) => CASH_RESTRICTED_PLATFORMS.has(platform));
1922
- if (restrictedPlatforms.length > 0) {
1923
- throw errors.atomicAccessPolicyRequired(restrictedPlatforms);
1924
- }
1925
- }
1926
1934
  function isCashPayoutSet(payouts) {
1927
1935
  return payouts.length > 0 && payouts.every((payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0);
1928
1936
  }
@@ -2162,7 +2170,7 @@ function createCashClient(options) {
2162
2170
  if (lastNonceError) throw lastNonceError;
2163
2171
  throw new Error(`Signer provider did not observe Relay nonce ${afterRelay - 1}`);
2164
2172
  }
2165
- function prepareCashoutAccess(depositId, client = readClient) {
2173
+ function prepareCashoutAccess(depositId, client = readClient, source) {
2166
2174
  const { compositeId, escrowAddress, onchainDepositId } = parseDepositId(depositId);
2167
2175
  const groupIds = CASH_ACCESS_GROUP_IDS[environment];
2168
2176
  try {
@@ -2181,9 +2189,50 @@ function createCashClient(options) {
2181
2189
  chainId: prepared.chainId
2182
2190
  };
2183
2191
  } catch (err) {
2184
- throw errors.accessPolicyConfigurationFailed(compositeId, groupIds, err);
2192
+ throw errors.accessPolicyConfigurationFailed(compositeId, groupIds, {
2193
+ cause: err,
2194
+ ...source ? { source } : {}
2195
+ });
2185
2196
  }
2186
2197
  }
2198
+ async function configureCashoutAccess(client, signer, depositInput, depositId, source) {
2199
+ if (!requiresCashoutAccessPolicy(depositInput)) return void 0;
2200
+ const groupIds = CASH_ACCESS_GROUP_IDS[environment];
2201
+ const prepared = prepareCashoutAccess(depositId, client, source);
2202
+ let hash;
2203
+ try {
2204
+ hash = await signer.sendTransaction({
2205
+ account: signer.account,
2206
+ chain: signer.chain,
2207
+ to: prepared.to,
2208
+ data: prepared.data,
2209
+ value: prepared.value
2210
+ });
2211
+ } catch (err) {
2212
+ throw errors.accessPolicyConfigurationFailed(depositId, groupIds, {
2213
+ cause: err,
2214
+ ...source ? { source } : {}
2215
+ });
2216
+ }
2217
+ let receipt;
2218
+ try {
2219
+ receipt = await client.publicClient.waitForTransactionReceipt({ hash });
2220
+ } catch (err) {
2221
+ throw errors.accessPolicyConfigurationFailed(depositId, groupIds, {
2222
+ cause: err,
2223
+ transactionHash: hash,
2224
+ ...source ? { source } : {}
2225
+ });
2226
+ }
2227
+ if (receipt.status === "reverted") {
2228
+ throw errors.accessPolicyConfigurationFailed(depositId, groupIds, {
2229
+ cause: errors.transactionFailed(hash),
2230
+ transactionHash: hash,
2231
+ ...source ? { source } : {}
2232
+ });
2233
+ }
2234
+ return hash;
2235
+ }
2187
2236
  return {
2188
2237
  capabilities,
2189
2238
  async sourceCapabilities() {
@@ -2221,7 +2270,6 @@ function createCashClient(options) {
2221
2270
  },
2222
2271
  async cashout(input, opts) {
2223
2272
  const payoutInput = validatePayout(input);
2224
- assertGenericCashoutSupported(payoutInput);
2225
2273
  const client = await signingClient("cashout", opts);
2226
2274
  const owner = opts.signer.account.address;
2227
2275
  let sourceResult;
@@ -2319,6 +2367,13 @@ function createCashClient(options) {
2319
2367
  const abi2 = client.escrowV2Abi ?? client.escrowAbi;
2320
2368
  const resolved2 = resolveCashDepositId({ logs: receipt2.logs, abi: abi2 });
2321
2369
  if (!resolved2) throw errors.depositResolutionFailed(hash2);
2370
+ const accessPolicyTxHash2 = await configureCashoutAccess(
2371
+ client,
2372
+ opts.signer,
2373
+ depositInput2,
2374
+ resolved2.compositeId,
2375
+ routedSource
2376
+ );
2322
2377
  const order2 = deriveCashOrder(resolved2.compositeId, [], {
2323
2378
  remainingAmount: depositInput2.amount,
2324
2379
  status: "ACTIVE"
@@ -2329,6 +2384,7 @@ function createCashClient(options) {
2329
2384
  escrowAddress: resolved2.escrowAddress,
2330
2385
  onchainDepositId: resolved2.onchainDepositId,
2331
2386
  order: order2,
2387
+ ...accessPolicyTxHash2 ? { accessPolicyTxHash: accessPolicyTxHash2 } : {},
2332
2388
  source: routedSource
2333
2389
  };
2334
2390
  }
@@ -2373,6 +2429,12 @@ function createCashClient(options) {
2373
2429
  const abi = client.escrowV2Abi ?? client.escrowAbi;
2374
2430
  const resolved = resolveCashDepositId({ logs: receipt.logs, abi });
2375
2431
  if (!resolved) throw errors.depositResolutionFailed(hash);
2432
+ const accessPolicyTxHash = await configureCashoutAccess(
2433
+ client,
2434
+ opts.signer,
2435
+ depositInput,
2436
+ resolved.compositeId
2437
+ );
2376
2438
  const order = deriveCashOrder(resolved.compositeId, [], {
2377
2439
  remainingAmount: depositInput.amount,
2378
2440
  status: "ACTIVE"
@@ -2383,13 +2445,13 @@ function createCashClient(options) {
2383
2445
  escrowAddress: resolved.escrowAddress,
2384
2446
  onchainDepositId: resolved.onchainDepositId,
2385
2447
  order,
2448
+ ...accessPolicyTxHash ? { accessPolicyTxHash } : {},
2386
2449
  ...sourceResult ? { source: sourceResult } : {}
2387
2450
  };
2388
2451
  },
2389
2452
  async prepare(input) {
2390
2453
  if (input.source) throw errors.sourceRouteUnsupportedInPrepare();
2391
2454
  const depositInput = validateDepositInput(input.amount, input);
2392
- assertGenericCashoutSupported(depositInput);
2393
2455
  const params = await buildDepositParams(readClient, depositInput);
2394
2456
  const { prepared } = await readClient.prepareCreateDeposit({
2395
2457
  ...params,
@@ -2422,7 +2484,7 @@ function createCashClient(options) {
2422
2484
  }
2423
2485
  ],
2424
2486
  register: { hashedOnchainIds },
2425
- accessPolicyRequired: false
2487
+ accessPolicyRequired: requiresCashoutAccessPolicy(depositInput)
2426
2488
  };
2427
2489
  },
2428
2490
  finalizePreparedCashout(receipt) {
@@ -3049,7 +3111,8 @@ var cashErrorRecoveryJsonSchema = zod.z.discriminatedUnion("kind", [
3049
3111
  kind: zod.z.literal("configure-cashout-access-policy"),
3050
3112
  depositId: zod.z.string(),
3051
3113
  groupIds: zod.z.array(zod.z.string()),
3052
- transactionHash: zod.z.string().optional()
3114
+ transactionHash: zod.z.string().optional(),
3115
+ source: zod.z.object(cashSourceRecoveryJsonShape).strict().optional()
3053
3116
  }).strict()
3054
3117
  ]);
3055
3118
  var cashErrorJsonSchema = zod.z.object({
@@ -3377,6 +3440,10 @@ function topUpResultFromJson(json) {
3377
3440
  function capabilitiesToJson(caps) {
3378
3441
  return {
3379
3442
  ...caps,
3443
+ platforms: caps.platforms.map((platform) => ({
3444
+ ...platform,
3445
+ requiresAtomicAccessPolicy: false
3446
+ })),
3380
3447
  amount: {
3381
3448
  min: caps.amount.min.toString(),
3382
3449
  recommendedMin: caps.amount.recommendedMin.toString(),
@@ -3395,7 +3462,7 @@ function capabilitiesFromJson(json) {
3395
3462
  platforms: parsed.platforms.map((p) => ({
3396
3463
  ...p,
3397
3464
  currencies: p.currencies,
3398
- requiresAtomicAccessPolicy: p.requiresAtomicAccessPolicy ?? CASH_RESTRICTED_PLATFORMS.has(p.platform)
3465
+ requiresAtomicAccessPolicy: false
3399
3466
  })),
3400
3467
  currencies: parsed.currencies,
3401
3468
  amount: {
@@ -3429,7 +3496,15 @@ function cashErrorFromJson(json) {
3429
3496
  kind: parsed.recovery.kind,
3430
3497
  depositId: parsed.recovery.depositId,
3431
3498
  groupIds: parsed.recovery.groupIds,
3432
- ...parsed.recovery.transactionHash !== void 0 ? { transactionHash: parsed.recovery.transactionHash } : {}
3499
+ ...parsed.recovery.transactionHash !== void 0 ? { transactionHash: parsed.recovery.transactionHash } : {},
3500
+ ...parsed.recovery.source !== void 0 ? {
3501
+ source: {
3502
+ amount: parsed.recovery.source.amount,
3503
+ txHashes: parsed.recovery.source.txHashes,
3504
+ ...parsed.recovery.source.requestId !== void 0 ? { requestId: parsed.recovery.source.requestId } : {},
3505
+ ...parsed.recovery.source.transactions !== void 0 ? { transactions: parsed.recovery.source.transactions } : {}
3506
+ }
3507
+ } : {}
3433
3508
  };
3434
3509
  } else if (parsed.recovery.kind === "inspect-base-operation-submission") {
3435
3510
  recovery = {