@zkp2p/cash 0.4.7 → 0.4.9

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, CASH_RETAIN_ON_EMPTY, BASE_USDC_ADDRESS, USDC_DECIMALS, BASE_CHAIN_ID, CASH_RESTRICTED_PLATFORMS, errors, isCashError, CASH_ORDER_STATUSES, mapChainError, CashError, CASH_ACCESS_GROUP_IDS } from './chunk-SYE25ICW.js';
2
- 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 } from './chunk-SYE25ICW.js';
1
+ import { MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, CASH_RETAIN_ON_EMPTY, BASE_USDC_ADDRESS, USDC_DECIMALS, BASE_CHAIN_ID, errors, isCashError, CASH_ORDER_STATUSES, mapChainError, CashError, CASH_ACCESS_GROUP_IDS, CASH_RESTRICTED_PLATFORMS } from './chunk-4LPWKZMW.js';
2
+ 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 } from './chunk-4LPWKZMW.js';
3
3
  import { parseAbi, parseEventLogs, isAddress, http, createWalletClient, encodeFunctionData } from 'viem';
4
4
  import { base } from 'viem/chains';
5
5
  import { getSpreadOracleConfig, currencyInfo, getPaymentMethodsCatalog, getGatingServiceAddress, resolvePaymentMethodHashFromCatalog, resolvePaymentMethodNameFromHash, getCurrencyCodeFromHash, createCompositeDepositId, appendAttributionToCalldata, Zkp2pClient, CHAINLINK_ORACLE_FEEDS } from '@zkp2p/sdk';
@@ -461,7 +461,7 @@ function buildCapabilities(environment) {
461
461
  currencies: [...new Set(currencies2)].sort(),
462
462
  payeeHint: PAYEE_HINTS[platform] ?? "Your payment handle for this platform",
463
463
  requiresIdentityAttestation: IDENTITY_ATTESTATION_PLATFORMS.has(platform),
464
- requiresAtomicAccessPolicy: CASH_RESTRICTED_PLATFORMS.has(platform)
464
+ requiresAtomicAccessPolicy: false
465
465
  };
466
466
  }).filter((p) => p.currencies.length > 0).sort((a, b) => a.platform.localeCompare(b.platform));
467
467
  const currencies = [...new Set(platforms.flatMap((p) => p.currencies))].sort();
@@ -1296,6 +1296,11 @@ async function submitAndConfirm(client, verb, send) {
1296
1296
  function isKnownPreBroadcastFailure(mapped) {
1297
1297
  return mapped.code === "TRANSACTION_REJECTED" || mapped.code === "INSUFFICIENT_TOKEN_BALANCE" || mapped.code === "ALLOWANCE_NOT_VISIBLE" || mapped.code === "ESCROW_PAUSED";
1298
1298
  }
1299
+ function requiresCashoutAccessPolicy(depositInput) {
1300
+ return depositInput.payouts.some(
1301
+ (payout) => CASH_RESTRICTED_PLATFORMS.has(payout.processorName.toLowerCase())
1302
+ );
1303
+ }
1299
1304
  function depositOrderOptions(deposit) {
1300
1305
  const remaining = toBigIntOrUndefined(deposit.remainingDeposits);
1301
1306
  const outstanding = toBigIntOrUndefined(deposit.outstandingIntentAmount);
@@ -1427,12 +1432,6 @@ function createCashClient(options) {
1427
1432
  ...range ? { intentAmountRange: range } : {}
1428
1433
  };
1429
1434
  }
1430
- function assertGenericCashoutSupported(payoutInput) {
1431
- const restrictedPlatforms = payoutInput.payouts.map((payout) => payout.processorName.toLowerCase()).filter((platform) => CASH_RESTRICTED_PLATFORMS.has(platform));
1432
- if (restrictedPlatforms.length > 0) {
1433
- throw errors.atomicAccessPolicyRequired(restrictedPlatforms);
1434
- }
1435
- }
1436
1435
  function isCashPayoutSet(payouts) {
1437
1436
  return payouts.length > 0 && payouts.every((payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0);
1438
1437
  }
@@ -1672,7 +1671,7 @@ function createCashClient(options) {
1672
1671
  if (lastNonceError) throw lastNonceError;
1673
1672
  throw new Error(`Signer provider did not observe Relay nonce ${afterRelay - 1}`);
1674
1673
  }
1675
- function prepareCashoutAccess(depositId, client = readClient) {
1674
+ function prepareCashoutAccess(depositId, client = readClient, source) {
1676
1675
  const { compositeId, escrowAddress, onchainDepositId } = parseDepositId(depositId);
1677
1676
  const groupIds = CASH_ACCESS_GROUP_IDS[environment];
1678
1677
  try {
@@ -1691,9 +1690,50 @@ function createCashClient(options) {
1691
1690
  chainId: prepared.chainId
1692
1691
  };
1693
1692
  } catch (err) {
1694
- throw errors.accessPolicyConfigurationFailed(compositeId, groupIds, err);
1693
+ throw errors.accessPolicyConfigurationFailed(compositeId, groupIds, {
1694
+ cause: err,
1695
+ ...source ? { source } : {}
1696
+ });
1695
1697
  }
1696
1698
  }
1699
+ async function configureCashoutAccess(client, signer, depositInput, depositId, source) {
1700
+ if (!requiresCashoutAccessPolicy(depositInput)) return void 0;
1701
+ const groupIds = CASH_ACCESS_GROUP_IDS[environment];
1702
+ const prepared = prepareCashoutAccess(depositId, client, source);
1703
+ let hash;
1704
+ try {
1705
+ hash = await signer.sendTransaction({
1706
+ account: signer.account,
1707
+ chain: signer.chain,
1708
+ to: prepared.to,
1709
+ data: prepared.data,
1710
+ value: prepared.value
1711
+ });
1712
+ } catch (err) {
1713
+ throw errors.accessPolicyConfigurationFailed(depositId, groupIds, {
1714
+ cause: err,
1715
+ ...source ? { source } : {}
1716
+ });
1717
+ }
1718
+ let receipt;
1719
+ try {
1720
+ receipt = await client.publicClient.waitForTransactionReceipt({ hash });
1721
+ } catch (err) {
1722
+ throw errors.accessPolicyConfigurationFailed(depositId, groupIds, {
1723
+ cause: err,
1724
+ transactionHash: hash,
1725
+ ...source ? { source } : {}
1726
+ });
1727
+ }
1728
+ if (receipt.status === "reverted") {
1729
+ throw errors.accessPolicyConfigurationFailed(depositId, groupIds, {
1730
+ cause: errors.transactionFailed(hash),
1731
+ transactionHash: hash,
1732
+ ...source ? { source } : {}
1733
+ });
1734
+ }
1735
+ return hash;
1736
+ }
1697
1737
  return {
1698
1738
  capabilities,
1699
1739
  async sourceCapabilities() {
@@ -1731,11 +1771,8 @@ function createCashClient(options) {
1731
1771
  },
1732
1772
  async cashout(input, opts) {
1733
1773
  const payoutInput = validatePayout(input);
1734
- assertGenericCashoutSupported(payoutInput);
1735
1774
  const client = await signingClient("cashout", opts);
1736
1775
  const owner = opts.signer.account.address;
1737
- let sourceResult;
1738
- let cashoutAmount = input.amount;
1739
1776
  if (input.source) {
1740
1777
  const sourceSigner = opts.sourceSigner ?? (input.source.chainId === BASE_CHAIN_ID ? opts.signer : void 0);
1741
1778
  if (!sourceSigner?.account) throw errors.signerRequired("source cashout");
@@ -1756,7 +1793,7 @@ function createCashClient(options) {
1756
1793
  if (relayQuote.outputAmount < MIN_CASHOUT_AMOUNT) {
1757
1794
  throw errors.amountBelowMinimum(relayQuote.outputAmount, MIN_CASHOUT_AMOUNT);
1758
1795
  }
1759
- cashoutAmount = relayQuote.outputAmount;
1796
+ const cashoutAmount = relayQuote.outputAmount;
1760
1797
  const depositInput2 = validateDepositInput(cashoutAmount, input, payoutInput);
1761
1798
  const params2 = await buildDepositParams(client, depositInput2);
1762
1799
  const escrow2 = client.escrowV2Address ?? client.escrowAddress;
@@ -1773,7 +1810,6 @@ function createCashClient(options) {
1773
1810
  txHashes: executed.txHashes,
1774
1811
  ...executed.transactions ? { transactions: executed.transactions } : {}
1775
1812
  };
1776
- sourceResult = routedSource;
1777
1813
  try {
1778
1814
  await waitForBaseSignerAfterRelay(
1779
1815
  client,
@@ -1829,6 +1865,13 @@ function createCashClient(options) {
1829
1865
  const abi2 = client.escrowV2Abi ?? client.escrowAbi;
1830
1866
  const resolved2 = resolveCashDepositId({ logs: receipt2.logs, abi: abi2 });
1831
1867
  if (!resolved2) throw errors.depositResolutionFailed(hash2);
1868
+ const accessPolicyTxHash2 = await configureCashoutAccess(
1869
+ client,
1870
+ opts.signer,
1871
+ depositInput2,
1872
+ resolved2.compositeId,
1873
+ routedSource
1874
+ );
1832
1875
  const order2 = deriveCashOrder(resolved2.compositeId, [], {
1833
1876
  remainingAmount: depositInput2.amount,
1834
1877
  status: "ACTIVE"
@@ -1839,6 +1882,7 @@ function createCashClient(options) {
1839
1882
  escrowAddress: resolved2.escrowAddress,
1840
1883
  onchainDepositId: resolved2.onchainDepositId,
1841
1884
  order: order2,
1885
+ ...accessPolicyTxHash2 ? { accessPolicyTxHash: accessPolicyTxHash2 } : {},
1842
1886
  source: routedSource
1843
1887
  };
1844
1888
  }
@@ -1883,6 +1927,12 @@ function createCashClient(options) {
1883
1927
  const abi = client.escrowV2Abi ?? client.escrowAbi;
1884
1928
  const resolved = resolveCashDepositId({ logs: receipt.logs, abi });
1885
1929
  if (!resolved) throw errors.depositResolutionFailed(hash);
1930
+ const accessPolicyTxHash = await configureCashoutAccess(
1931
+ client,
1932
+ opts.signer,
1933
+ depositInput,
1934
+ resolved.compositeId
1935
+ );
1886
1936
  const order = deriveCashOrder(resolved.compositeId, [], {
1887
1937
  remainingAmount: depositInput.amount,
1888
1938
  status: "ACTIVE"
@@ -1893,13 +1943,12 @@ function createCashClient(options) {
1893
1943
  escrowAddress: resolved.escrowAddress,
1894
1944
  onchainDepositId: resolved.onchainDepositId,
1895
1945
  order,
1896
- ...sourceResult ? { source: sourceResult } : {}
1946
+ ...accessPolicyTxHash ? { accessPolicyTxHash } : {}
1897
1947
  };
1898
1948
  },
1899
1949
  async prepare(input) {
1900
1950
  if (input.source) throw errors.sourceRouteUnsupportedInPrepare();
1901
1951
  const depositInput = validateDepositInput(input.amount, input);
1902
- assertGenericCashoutSupported(depositInput);
1903
1952
  const params = await buildDepositParams(readClient, depositInput);
1904
1953
  const { prepared } = await readClient.prepareCreateDeposit({
1905
1954
  ...params,
@@ -1932,7 +1981,7 @@ function createCashClient(options) {
1932
1981
  }
1933
1982
  ],
1934
1983
  register: { hashedOnchainIds },
1935
- accessPolicyRequired: false
1984
+ accessPolicyRequired: requiresCashoutAccessPolicy(depositInput)
1936
1985
  };
1937
1986
  },
1938
1987
  finalizePreparedCashout(receipt) {
@@ -2559,7 +2608,8 @@ var cashErrorRecoveryJsonSchema = z.discriminatedUnion("kind", [
2559
2608
  kind: z.literal("configure-cashout-access-policy"),
2560
2609
  depositId: z.string(),
2561
2610
  groupIds: z.array(z.string()),
2562
- transactionHash: z.string().optional()
2611
+ transactionHash: z.string().optional(),
2612
+ source: z.object(cashSourceRecoveryJsonShape).strict().optional()
2563
2613
  }).strict()
2564
2614
  ]);
2565
2615
  var cashErrorJsonSchema = z.object({
@@ -2887,6 +2937,10 @@ function topUpResultFromJson(json) {
2887
2937
  function capabilitiesToJson(caps) {
2888
2938
  return {
2889
2939
  ...caps,
2940
+ platforms: caps.platforms.map((platform) => ({
2941
+ ...platform,
2942
+ requiresAtomicAccessPolicy: false
2943
+ })),
2890
2944
  amount: {
2891
2945
  min: caps.amount.min.toString(),
2892
2946
  recommendedMin: caps.amount.recommendedMin.toString(),
@@ -2905,7 +2959,7 @@ function capabilitiesFromJson(json) {
2905
2959
  platforms: parsed.platforms.map((p) => ({
2906
2960
  ...p,
2907
2961
  currencies: p.currencies,
2908
- requiresAtomicAccessPolicy: p.requiresAtomicAccessPolicy ?? CASH_RESTRICTED_PLATFORMS.has(p.platform)
2962
+ requiresAtomicAccessPolicy: false
2909
2963
  })),
2910
2964
  currencies: parsed.currencies,
2911
2965
  amount: {
@@ -2939,7 +2993,15 @@ function cashErrorFromJson(json) {
2939
2993
  kind: parsed.recovery.kind,
2940
2994
  depositId: parsed.recovery.depositId,
2941
2995
  groupIds: parsed.recovery.groupIds,
2942
- ...parsed.recovery.transactionHash !== void 0 ? { transactionHash: parsed.recovery.transactionHash } : {}
2996
+ ...parsed.recovery.transactionHash !== void 0 ? { transactionHash: parsed.recovery.transactionHash } : {},
2997
+ ...parsed.recovery.source !== void 0 ? {
2998
+ source: {
2999
+ amount: parsed.recovery.source.amount,
3000
+ txHashes: parsed.recovery.source.txHashes,
3001
+ ...parsed.recovery.source.requestId !== void 0 ? { requestId: parsed.recovery.source.requestId } : {},
3002
+ ...parsed.recovery.source.transactions !== void 0 ? { transactions: parsed.recovery.source.transactions } : {}
3003
+ }
3004
+ } : {}
2943
3005
  };
2944
3006
  } else if (parsed.recovery.kind === "inspect-base-operation-submission") {
2945
3007
  recovery = {
package/dist/react.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CurrencyType } from '@zkp2p/sdk';
2
- import { s as CashClient, L as EstimateInput, i as CashEstimate, J as CashoutOptions, h as CashoutResult, H as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-CL1SCbqF.cjs';
2
+ import { s as CashClient, L as EstimateInput, i as CashEstimate, J as CashoutOptions, h as CashoutResult, H as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-Br7uu4lQ.cjs';
3
3
  import { WalletClient } from 'viem';
4
4
  import '@relayprotocol/relay-sdk';
5
5
 
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CurrencyType } from '@zkp2p/sdk';
2
- import { s as CashClient, L as EstimateInput, i as CashEstimate, J as CashoutOptions, h as CashoutResult, H as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-CL1SCbqF.js';
2
+ import { s as CashClient, L as EstimateInput, i as CashEstimate, J as CashoutOptions, h as CashoutResult, H as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-Br7uu4lQ.js';
3
3
  import { WalletClient } from 'viem';
4
4
  import '@relayprotocol/relay-sdk';
5
5
 
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isCashError, CASH_ORDER_POLL_INTERVAL_MS } from './chunk-SYE25ICW.js';
1
+ import { isCashError, CASH_ORDER_POLL_INTERVAL_MS } from './chunk-4LPWKZMW.js';
2
2
  import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
3
3
 
4
4
  function useEstimate({
package/dist/tools.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // package.json
4
4
  var package_default = {
5
- version: "0.4.7"};
5
+ version: "0.4.9"};
6
6
 
7
7
  // src/tools/index.ts
8
8
  var bigintString = {
@@ -164,7 +164,7 @@ var builtInCashTools = [
164
164
  },
165
165
  {
166
166
  name: "cash_cashout",
167
- description: "Start an unrestricted-rail Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. Platforms marked requiresAtomicAccessPolicy fail closed because this tool cannot safely prepare them. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.",
167
+ description: "Start a Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. If any payout leg is Venmo, Cash App, or PayPal, accessPolicyRequired is true: after createDeposit confirms, the host adapter must call CashClient.finalizePreparedCashout(receipt), then prepare and confirm CashClient.prepareAccessPolicy(depositId) with the depositor. These receipt/signing methods are not separate built-in tools. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.",
168
168
  inputSchema: {
169
169
  type: "object",
170
170
  properties: {
package/dist/tools.d.cts CHANGED
@@ -145,7 +145,7 @@ declare const builtInCashTools: readonly [{
145
145
  };
146
146
  }, {
147
147
  readonly name: "cash_cashout";
148
- readonly description: "Start an unrestricted-rail Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. Platforms marked requiresAtomicAccessPolicy fail closed because this tool cannot safely prepare them. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.";
148
+ readonly description: "Start a Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. If any payout leg is Venmo, Cash App, or PayPal, accessPolicyRequired is true: after createDeposit confirms, the host adapter must call CashClient.finalizePreparedCashout(receipt), then prepare and confirm CashClient.prepareAccessPolicy(depositId) with the depositor. These receipt/signing methods are not separate built-in tools. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.";
149
149
  readonly inputSchema: {
150
150
  readonly type: "object";
151
151
  readonly properties: {
package/dist/tools.d.ts CHANGED
@@ -145,7 +145,7 @@ declare const builtInCashTools: readonly [{
145
145
  };
146
146
  }, {
147
147
  readonly name: "cash_cashout";
148
- readonly description: "Start an unrestricted-rail Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. Platforms marked requiresAtomicAccessPolicy fail closed because this tool cannot safely prepare them. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.";
148
+ readonly description: "Start a Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. If any payout leg is Venmo, Cash App, or PayPal, accessPolicyRequired is true: after createDeposit confirms, the host adapter must call CashClient.finalizePreparedCashout(receipt), then prepare and confirm CashClient.prepareAccessPolicy(depositId) with the depositor. These receipt/signing methods are not separate built-in tools. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.";
149
149
  readonly inputSchema: {
150
150
  readonly type: "object";
151
151
  readonly properties: {
package/dist/tools.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // package.json
2
2
  var package_default = {
3
- version: "0.4.7"};
3
+ version: "0.4.9"};
4
4
 
5
5
  // src/tools/index.ts
6
6
  var bigintString = {
@@ -162,7 +162,7 @@ var builtInCashTools = [
162
162
  },
163
163
  {
164
164
  name: "cash_cashout",
165
- description: "Start an unrestricted-rail Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. Platforms marked requiresAtomicAccessPolicy fail closed because this tool cannot safely prepare them. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.",
165
+ description: "Start a Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. If any payout leg is Venmo, Cash App, or PayPal, accessPolicyRequired is true: after createDeposit confirms, the host adapter must call CashClient.finalizePreparedCashout(receipt), then prepare and confirm CashClient.prepareAccessPolicy(depositId) with the depositor. These receipt/signing methods are not separate built-in tools. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.",
166
166
  inputSchema: {
167
167
  type: "object",
168
168
  properties: {
@@ -65,15 +65,19 @@ but the host or a signer-backed client must execute the route before
65
65
  transaction confirms, call `finalizePreparedCashout(receipt)` to decode it with
66
66
  the environment-correct escrow ABI and obtain the resumable `CashoutResult`.
67
67
 
68
- Venmo, Cash App, and PayPal require atomic deposit creation and access-policy
69
- configuration. The generic `cashout()` and `prepare()` paths reject these rails
70
- with `ATOMIC_ACCESS_POLICY_REQUIRED` before wallet access, Relay work, payee
71
- registration, allowance, or deposit submission. Peer web performs the required
72
- guard-before, create, guard-after, and policy configuration in one Curator-backed
73
- batch. A host must provide the same atomic guarantee to support these rails.
74
- Discovery marks them with `requiresAtomicAccessPolicy: true`.
75
- `prepareAccessPolicy(depositId)` is retained only to recover a restricted
76
- deposit already created by `0.4.4`; it is not a safe new-order sequence.
68
+ The deprecated `requiresAtomicAccessPolicy` capability is always `false`:
69
+ policy attachment is sequential, not atomic. If any payout leg uses Venmo,
70
+ Cash App, or PayPal, the cash-out nevertheless restricts intent signaling to
71
+ the Plus, Pro, Peer Makers, and Peer Pay groups by default. Signed `cashout()`
72
+ confirms the deposit before submitting and confirming the policy with the same
73
+ viem wallet, so a brief unprotected interval exists. For `prepare()`,
74
+ `accessPolicyRequired` marks whether the host must call
75
+ `prepareAccessPolicy(depositId)` after finalizing the confirmed deposit receipt.
76
+ Any viem `WalletClient`, including an EOA, can submit it; Privy is not required.
77
+ If the follow-up fails, the deposit already exists. Never repeat the cash-out.
78
+ When `ACCESS_POLICY_CONFIGURATION_FAILED.recovery.transactionHash` is present,
79
+ inspect it before preparing another policy; resubmit only when that transaction
80
+ is absent or confirmed reverted.
77
81
 
78
82
  There is no static chain/token allowlist in Peer Cash. Relay decides source
79
83
  support through its metadata and quote execution, filtered to the viem/EVM
@@ -277,8 +281,10 @@ whole life. Two things to know for long-lived orders:
277
281
  the payee is re-registered. Format-only platforms (Zelle, Chime, …) are
278
282
  never re-checked.
279
283
  - **Wise and PayPal** require a signed identity attestation for a new payee
280
- registration. A previously registered handle can be reused with bare payee
281
- data. If the handle is new and no attestation is supplied, the SDK surfaces
284
+ registration. The SDK accepts the structured attestation but does not mint
285
+ it; first-party Peer web obtains it through the Peer TEE browser extension.
286
+ A previously registered handle can be reused with bare payee data. If the
287
+ handle is new and no attestation is supplied, the SDK surfaces
282
288
  `PAYEE_VERIFICATION_REQUIRED`; `capabilities()` flags these platforms with
283
289
  `requiresIdentityAttestation: true`.
284
290
 
@@ -299,9 +305,9 @@ explicit override.
299
305
  | `INVALID_INTENT_AMOUNT_RANGE` | no | Min/max is non-positive, inverted, or exceeds the deposit. Correct the range. |
300
306
  | `INVALID_PAYOUT_CURRENCIES` | no | The currency set is empty or contains duplicates. Pass a non-empty unique set from `capabilities()`. |
301
307
  | `INVALID_PAYOUT_PLATFORMS` | no | The payout leg set is empty or repeats a platform. Pass one leg, or an array of legs using each platform at most once. |
302
- | `PAYEE_VERIFICATION_REQUIRED` | no | A new Wise/PayPal payee needs an attestation. Register it through Peer first; an existing registration can be reused. |
308
+ | `PAYEE_VERIFICATION_REQUIRED` | no | A new Wise/PayPal payee needs an attestation from Peer web and its TEE browser extension; an existing registration can be reused. |
303
309
  | `PAYEE_REGISTRATION_FAILED` | yes | Curator rejected the handle or was unavailable. Check `payeeHint` and retry. |
304
- | `ATOMIC_ACCESS_POLICY_REQUIRED` | no | Venmo, Cash App, or PayPal needs atomic creation and group policy setup. Use Peer web or an equivalent atomic host; nothing was submitted. |
310
+ | `ATOMIC_ACCESS_POLICY_REQUIRED` | no | Deprecated compatibility code. Current SDK flows never emit it. |
305
311
  | `SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE` | no | `prepare()` accepts Base USDC only. Use signed source execution, or complete Relay first and then prepare the Base cashout. |
306
312
  | `SOURCE_RECIPIENT_MISMATCH` | no | Relay output recipient differs from the cashout depositor. Use the depositor address. |
307
313
  | `SOURCE_CAPABILITIES_FAILED` | yes | Relay source discovery failed. Retry or use Base USDC. |
@@ -319,7 +325,7 @@ explicit override.
319
325
  | `TRANSACTION_SUBMISSION_UNKNOWN` | no | A Base mutation returned no hash but may have broadcast. Inspect wallet/protocol state and its recovery action before any retry. |
320
326
  | `TRANSACTION_STATUS_UNKNOWN` | no | A transaction was submitted but its receipt is unknown. Inspect `recovery.transactionHash` before resubmitting. |
321
327
  | `DEPOSIT_RESOLUTION_FAILED` | no | Base tx succeeded but no `DepositReceived` was decoded. Inspect its logs and recover the composite id. |
322
- | `ACCESS_POLICY_CONFIGURATION_FAILED` | no | A `0.4.4` cash-out exists without confirmed group policy. Configure that existing deposit from recovery data; never create it again. |
328
+ | `ACCESS_POLICY_CONFIGURATION_FAILED` | no | The deposit exists but its required policy was not confirmed. Inspect `transactionHash` if present; attach the policy if still needed. |
323
329
  | `INVALID_DEPOSIT_ID` | no | The id is not `escrowAddress_onchainId`. A bare number cannot cold-hydrate; use the value returned by `cashout()`. |
324
330
  | `ORDER_NOT_FOUND` | yes | Unknown id or immediate indexer lag. Verify the id and retry shortly after creation. |
325
331
  | `INDEXER_LAG` | yes | Indexer trails the chain. Retry the read shortly. |
@@ -54,7 +54,9 @@ async function executeTool(name: string, args: Record<string, unknown>): Promise
54
54
  case 'cash_cashout': {
55
55
  // Tool/prepare path: Base USDC only. cash_source_quote is read-only;
56
56
  // the host must execute and confirm Relay with its own signer/runtime,
57
- // then call this tool with the guaranteed Base USDC amount.
57
+ // then call this tool with the guaranteed Base USDC amount. Persist
58
+ // accessPolicyRequired: after createDeposit confirms, the host adapter
59
+ // calls finalizePreparedCashout(receipt), then prepareAccessPolicy().
58
60
  const input = {
59
61
  amount: BigInt(args.amount as string),
60
62
  receive: args.receive as never,
@@ -108,12 +110,16 @@ const caps = (await executeTool('cash_capabilities', {})) as {
108
110
  platform: string;
109
111
  currencies: string[];
110
112
  payeeHint: string;
113
+ requiresIdentityAttestation: boolean;
111
114
  requiresAtomicAccessPolicy: boolean;
112
115
  }[];
113
116
  };
114
117
  const venmo = caps.platforms.find((p) => p.platform === 'venmo');
115
118
  console.log(`agent sees ${caps.platforms.length} platforms; venmo capability:`);
116
- console.log(` hint="${venmo?.payeeHint}" atomic=${venmo?.requiresAtomicAccessPolicy}\n`);
119
+ console.log(
120
+ ` hint="${venmo?.payeeHint}" identityAttestation=${venmo?.requiresIdentityAttestation}`,
121
+ );
122
+ console.log(' sequential access policy is reported by cash_cashout.accessPolicyRequired\n');
117
123
 
118
124
  const est = await executeTool('cash_estimate', {
119
125
  amount: usdc(250).toString(),
@@ -10,10 +10,11 @@
10
10
  *
11
11
  * The curator validates supported handles against the live platform, so the
12
12
  * payee must be a real account. A new Wise/PayPal registration also needs the
13
- * identity attestation created by Peer; an existing registered handle can be
14
- * reused. Venmo, Cash App, and PayPal require Peer web or another host that
15
- * creates the deposit and access policy atomically, so this generic demo uses
16
- * Chime by default. Override the demo corridor with:
13
+ * identity attestation obtained by Peer web through the Peer TEE browser
14
+ * extension; an existing registered handle can be reused. This private-key EOA
15
+ * works directly with every supported platform;
16
+ * no Privy wallet is required. Venmo, Cash App, and PayPal attach their access
17
+ * policy in a confirmed follow-up transaction. Override the demo corridor with:
17
18
  * CASH_PLATFORM=revolut CASH_CURRENCY=EUR CASH_PAYEE=your-revtag
18
19
  */
19
20
  import { createWalletClient, http } from 'viem';
@@ -28,9 +29,9 @@ const signer = createWalletClient({ account, chain: base, transport: http() });
28
29
  // One leg here; `receive` also accepts an array of legs to offer several
29
30
  // platforms on one order (each platform at most once, all at the oracle rate).
30
31
  const receive = {
31
- platform: process.env.CASH_PLATFORM ?? 'chime',
32
+ platform: process.env.CASH_PLATFORM ?? 'venmo',
32
33
  currency: (process.env.CASH_CURRENCY ?? 'USD') as CurrencyType,
33
- payee: { offchainId: process.env.CASH_PAYEE ?? '$your-chime-sign' },
34
+ payee: { offchainId: process.env.CASH_PAYEE ?? '@your-venmo' },
34
35
  };
35
36
 
36
37
  const cash = createCashClient({ environment: 'staging' });
@@ -51,6 +52,7 @@ console.log(
51
52
  // 2 - Cash out.
52
53
  const result = await cash.cashout({ amount: usdc(1), receive }, { signer });
53
54
  console.log(`deposit created: ${result.depositId} (tx ${result.txHash})`);
55
+ if (result.accessPolicyTxHash) console.log(`access policy attached: ${result.accessPolicyTxHash}`);
54
56
  // Persist this in YOUR system: userId → result.depositId
55
57
 
56
58
  // 3/5 - Track it. A real service would watch until terminal; the demo bails
package/llms.txt CHANGED
@@ -40,7 +40,8 @@ Key facts:
40
40
  prepareWithdraw, prepareTopUp) for host-side signing; source-routed cashout
41
41
  needs signer-backed Relay execution first. prepare() and the cash_cashout tool
42
42
  are Base-USDC-only; cash_source_quote/status do not execute the route.
43
- @zkp2p/cash/tools ships a JSON-schema tool manifest.
43
+ @zkp2p/cash/tools ships a JSON-schema tool manifest. Receipt finalization
44
+ and policy preparation remain CashClient methods for the host adapter.
44
45
  - Errors are typed: { code, retryable, remediation, recovery? }. Completed
45
46
  Relay routes retain requestId, flat hashes, and origin/destination
46
47
  transactions. Retry Base-only after SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED;
@@ -67,8 +68,17 @@ Key facts:
67
68
  - Default path is same-chain Base USDC. Optional `source` on `cashout()` runs
68
69
  Relay source-to-Base-USDC first; non-Base source chains require
69
70
  `sourceSigner`.
70
- - Wise and PayPal require attestation for a new payee registration; a
71
- previously registered bare handle can be reused.
71
+ - Wise and PayPal require attestation for a new payee registration. The SDK
72
+ accepts but does not mint it; first-party Peer web obtains it through the
73
+ Peer TEE browser extension. A previously registered bare handle can be reused.
74
+ - Venmo, Cash App, and PayPal attach Plus, Pro, Peer Makers, and Peer Pay
75
+ groups after the deposit confirms, restricting which takers can signal an
76
+ intent. If any payout leg uses one of these platforms, the follow-up is
77
+ intentionally non-atomic; `cashout()` submits it with the same viem wallet,
78
+ while prepared hosts must act when `accessPolicyRequired` is true. Any EOA
79
+ works; Privy is not required. On ACCESS_POLICY_CONFIGURATION_FAILED, never
80
+ repeat the cashout; inspect recovery.transactionHash before resubmitting a
81
+ policy.
72
82
  - Preproduction uses https://api-preprod.zkp2p.xyz by default; staging uses
73
83
  https://api-staging.zkp2p.xyz. curatorUrl can override either.
74
84
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkp2p/cash",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Peer Cash - offramp-only SDK for routing crypto to Base USDC, then cashing out to fiat at the live oracle market rate.",
5
5
  "license": "MIT",
6
6
  "author": "Peer (https://peer.xyz)",
@@ -105,9 +105,9 @@
105
105
  "ci": "bun run typecheck && bun run lint && bun run format:check && bun run test && bun run audit && bun run build && bun run pack:check"
106
106
  },
107
107
  "dependencies": {
108
- "@relayprotocol/relay-sdk": "^6.1.3",
108
+ "@relayprotocol/relay-sdk": "^7.0.1",
109
109
  "@zkp2p/sdk": "0.12.0",
110
- "zod": "^3.25.76"
110
+ "zod": "^4.4.3"
111
111
  },
112
112
  "peerDependencies": {
113
113
  "react": ">=18",
@@ -119,19 +119,19 @@
119
119
  }
120
120
  },
121
121
  "devDependencies": {
122
- "@eslint/js": "^9.39.5",
122
+ "@eslint/js": "^10.0.1",
123
123
  "@types/node": "^22.20.1",
124
- "@types/react": "^19.2.17",
124
+ "@types/react": "^19.2.18",
125
125
  "@types/react-test-renderer": "19.1.0",
126
- "eslint": "^9.39.5",
127
- "eslint-config-prettier": "^9.1.2",
128
- "prettier": "^3.9.5",
129
- "react": "^19.2.7",
130
- "react-test-renderer": "19.2.7",
126
+ "eslint": "^10.8.1",
127
+ "eslint-config-prettier": "^10.1.8",
128
+ "prettier": "^3.9.6",
129
+ "react": "^19.2.8",
130
+ "react-test-renderer": "19.2.8",
131
131
  "tsup": "^8.5.1",
132
132
  "typescript": "^5.9.3",
133
- "typescript-eslint": "^8.63.0",
134
- "viem": "^2.55.1",
133
+ "typescript-eslint": "^8.67.0",
134
+ "viem": "^2.55.15",
135
135
  "vitest": "^4.1.10"
136
136
  }
137
137
  }
@@ -29,6 +29,10 @@ custodial off-ramp provider.
29
29
  - anything in your UI or agent output implying a locked rate is a bug.
30
30
  - **Custody story.** Funds are held by the protocol contract only. An unmatched
31
31
  deposit is withdrawable by the maker at any time. The SDK never holds keys.
32
+ - **Restricted intent signaling.** If any payout leg uses Venmo, Cash App, or
33
+ PayPal, the Plus, Pro, Peer Makers, and Peer Pay groups attach after the
34
+ deposit confirms. Signed `cashout()` handles the sequential follow-up with
35
+ the same viem wallet. Prepared hosts must finish it explicitly; any EOA works.
32
36
  - **Honest ETA.** Use `estimate().eta`: `{ seconds, label }` backed by rolling
33
37
  30-day indexer data from zero-spread (`spreadBps: 0`) market-rate deposits in
34
38
  the same payout corridor, measured from deposit creation to first fill. Do
@@ -67,7 +71,13 @@ const res = await cash.cashout(
67
71
  },
68
72
  { signer },
69
73
  );
70
- const { txs, steps } = await cash.prepare({/* same input */}); // 2b unsigned plan
74
+ const { txs, steps, accessPolicyRequired } = await cash.prepare({/* same input */}); // 2b unsigned plan
75
+ // Submit txs in order. After createDeposit confirms:
76
+ const prepared = cash.finalizePreparedCashout(createDepositReceipt);
77
+ if (accessPolicyRequired) {
78
+ const policyTx = cash.prepareAccessPolicy(prepared.depositId);
79
+ await hostSubmitAndConfirm(policyTx);
80
+ }
71
81
  const order = await cash.order(res.depositId); // 3 observe
72
82
  const mine = await cash.orders(ownerAddress, { inFlight: true }); // 4 list
73
83
  for await (const o of cash.watch(res.depositId)) {
@@ -107,7 +117,10 @@ counterparts. `prepare()` rejects `source`. Source-routed cashout runs Relay
107
117
  first; use signed `cashout({ source }, { signer, sourceSigner })`, or execute
108
118
  and confirm Relay in the host before preparing a Base-USDC cashout.
109
119
  `cash_source_quote` and `cash_source_status` are quote/read tools, not a
110
- host-side execution path.
120
+ host-side execution path. The built-in tool manifest also does not expose
121
+ receipt finalization or access-policy submission as separate tools; the host
122
+ adapter calls those `CashClient` methods after its signer confirms
123
+ `createDeposit`.
111
124
  Every protocol transaction carries ERC-8021 attribution. To receive the
112
125
  deposit-level integration share, copy the six-character code from your Peer
113
126
  mobile or web referral screen and configure it directly:
@@ -127,9 +140,11 @@ that deposit. Use one referral code per deposit. Renaming the displayed code
127
140
  later does not change the owner of an already-attributed open deposit.
128
141
 
129
142
  Wise and PayPal require an identity attestation for a new payee registration.
130
- Do not disable them outright: a previously registered handle can be reused
131
- with bare payee data. Handle `PAYEE_VERIFICATION_REQUIRED` when registration
132
- is still needed.
143
+ The SDK accepts the structured attestation but does not mint it; first-party
144
+ Peer web obtains it through the Peer TEE browser extension. Do not disable these
145
+ platforms outright: a previously registered handle can be reused with bare
146
+ payee data. Handle `PAYEE_VERIFICATION_REQUIRED` when registration is still
147
+ needed.
133
148
 
134
149
  ## 4. Order management - indexer-native
135
150
 
@@ -167,6 +182,10 @@ those, don't re-derive. The recovery boundaries that matter most in practice:
167
182
  - `TRANSACTION_SUBMISSION_UNKNOWN` = a Base mutation returned no hash but may
168
183
  have broadcast. Follow `error.recovery`, inspect Base wallet/protocol state,
169
184
  and do not retry until absence is proven.
185
+ - `ACCESS_POLICY_CONFIGURATION_FAILED` = the deposit exists but its required
186
+ policy was not confirmed. Never cash out again. Inspect
187
+ `error.recovery.transactionHash` when present; prepare another policy only
188
+ if that transaction is absent or confirmed reverted.
170
189
  - `INDEXER_UNAVAILABLE` / `ORACLE_READ_FAILED` = retry the read only. Do not
171
190
  repeat the transaction that produced the id or balance being inspected.
172
191
  - `SIGNER_CHAIN_MISMATCH` = switch to the required chain and obtain a fresh
@@ -183,7 +202,8 @@ Run against `environment: 'staging'` with a small funded wallet.
183
202
 
184
203
  Prove both routes without waiting for a buyer:
185
204
 
186
- 1. Create a real 1–2 USDC Base-USDC deposit; retain `depositId` and Base tx.
205
+ 1. Create a real 1–2 USDC Base-USDC deposit; retain `depositId`, the Base tx,
206
+ and `accessPolicyTxHash` when using Venmo, Cash App, or PayPal.
187
207
  2. Retry through indexer lag until `order(depositId)` is `awaiting-buyer`, and
188
208
  assert `orders(owner)` contains it.
189
209
  3. Withdraw it; assert `returned` and the Base USDC balance is restored minus