@zkp2p/cash 0.1.5 → 0.1.7

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
@@ -110,10 +110,19 @@ console.log(routed.source?.transactions?.origin, routed.source?.transactions?.de
110
110
  `sourceSigner`. Use `EXACT_INPUT` in high-level cash-out flows so `amount`
111
111
  remains source-token base units. `source.amount` is Relay's guaranteed
112
112
  minimum output and the exact Base USDC deposit amount.
113
+ - **Use a nonce-managed source signer for routed cashouts.** Relay routes
114
+ with more than one source-chain transaction (approve, then route) are
115
+ refused preflight with `SOURCE_NONCE_MANAGER_REQUIRED` on plain local
116
+ accounts - create the source signer with
117
+ `privateKeyToAccount(pk, { nonceManager })`. Browser (`json-rpc`) wallets
118
+ are unaffected.
113
119
  - **Persist source evidence.** A routed result carries `requestId`, flat
114
120
  `txHashes`, and chain-aware `transactions.origin` / `.destination` arrays.
115
121
  - **Never repeat a completed or uncertain route.** On `SOURCE_EXECUTION_FAILED`,
116
- inspect its Relay request and transaction recovery evidence. On
122
+ inspect its Relay request and transaction recovery evidence. A failed
123
+ approval-only route can sit in `relayStatus` `waiting` indefinitely - decide
124
+ from the recovery payload and origin transactions, never by waiting for a
125
+ terminal Relay status. On
117
126
  `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED`, retry without `source` using
118
127
  `BigInt(err.recovery.amount)`. On `SOURCE_CASHOUT_SUBMISSION_UNKNOWN`, inspect
119
128
  Base wallet activity and `orders(err.recovery.depositor)`. On
@@ -128,7 +137,8 @@ console.log(routed.source?.transactions?.origin, routed.source?.transactions?.de
128
137
  deposit. The tx receipt you hold is the truth. Retry; `watch()` absorbs
129
138
  this automatically.
130
139
  - **Unwind with `withdraw()` only.** It is state-aware (prunes expired
131
- intents first). Do not call escrow functions directly.
140
+ intents first). Do not call escrow functions directly. Partial withdrawals
141
+ increment `returnedAmount`; `totalAmount` is historical and never shrinks.
132
142
  - **Read fills as receipts.** `fiatOwed` is the buyer's obligation at the
133
143
  locked rate; after the proof, `fiatPaid`/`paymentId`/`releasedAmount` are
134
144
  the verified outcome. Reconcile against those, not your own math.
@@ -161,6 +171,7 @@ Every `CashError` carries `code`, `retryable`, `remediation`. Behavior:
161
171
  | `SOURCE_RECIPIENT_MISMATCH` | no | Route Base USDC to the cashout depositor |
162
172
  | `SOURCE_CAPABILITIES_FAILED` | yes | Retry discovery or fall back to Base USDC |
163
173
  | `SOURCE_QUOTE_FAILED` | yes | Refresh capabilities and request a new canonical Base-USDC quote |
174
+ | `SOURCE_NONCE_MANAGER_REQUIRED` | no | Preflight; recreate the source signer with viem's `nonceManager`, then quote again |
164
175
  | `SOURCE_EXECUTION_FAILED` | no | Inspect source transactions and Relay status before any retry |
165
176
  | `SOURCE_STATUS_FAILED` | yes | Retry only the status read |
166
177
  | `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED` | no | Do not route again; retry Base-only with `recovery.amount` |
package/README.md CHANGED
@@ -5,12 +5,25 @@ on Venmo, Revolut, Wise, Zelle, and more at the live Chainlink market rate,
5
5
  with zero spread and no centralized off-ramp provider.
6
6
 
7
7
  Peer Cash is an **offramp-only** SDK for the [ZKP2P](https://peer.xyz)
8
- protocol. The cashing-out user is the maker: their USDC becomes a
9
- protocol-held deposit, Peer handles the buyer side, and the SDK gives the
10
- integrator a small set of typed verbs plus readable order state. No hosted
11
- widget, no provider custody, no quote engine to maintain.
8
+ protocol. The cashing-out user is the maker: their USDC becomes a deposit in
9
+ the protocol contracts, a buyer pays them fiat and proves the payment, and the
10
+ SDK gives the integrator a small set of typed verbs plus readable order state.
11
+ No hosted widget, no provider custody, no quote engine to maintain.
12
12
 
13
- **[Live demo](https://react-cashout-demo.vercel.app)** · **[Product page](https://peer.xyz/cash)**
13
+ **[npm](https://www.npmjs.com/package/@zkp2p/cash)** · **[Lifecycle and recovery](docs/lifecycle-and-recovery.md)** · **[Agent integration manual](AGENTS.md)**
14
+
15
+ ## Pick the right SDK
16
+
17
+ Peer Cash and the general ZKP2P SDK serve different integration depths:
18
+
19
+ | Package | Use it when | Boundary |
20
+ | ------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | `@zkp2p/cash` | Cash-out is the product | Offramp only. The user is always the maker, the destination is Base USDC, pricing is the live Chainlink rate at fill with zero spread, and the SDK owns the resumable order lifecycle. |
22
+ | `@zkp2p/sdk` | You are composing directly with the Peer protocol | General maker and taker operations, deposits, intents, proofs, quotes, vaults, rate managers, referrals, hooks, and API helpers. Your application owns the workflow and protocol choices. |
23
+
24
+ Peer Cash is a narrow facade over `@zkp2p/sdk`, not a replacement for it. It
25
+ cannot express custom spreads, buyer-side proof flows, vaults, disputes, or
26
+ arbitrary protocol operations.
14
27
 
15
28
  ```ts
16
29
  import { createCashClient, usdc } from '@zkp2p/cash';
@@ -94,6 +107,13 @@ The destination is always canonical Base USDC
94
107
  discovered and quoted by `@relayprotocol/relay-sdk`, not a static token
95
108
  allowlist.
96
109
 
110
+ Routes that submit more than one source-chain transaction (approve, then
111
+ route) require a nonce-managed source signer -
112
+ `privateKeyToAccount(pk, { nonceManager })` from viem. Without one the SDK
113
+ refuses the route preflight with `SOURCE_NONCE_MANAGER_REQUIRED` instead of
114
+ letting the route transaction reuse the approval's nonce and revert
115
+ mid-route. Browser wallets are unaffected.
116
+
97
117
  `capabilities()` tells you which platforms need a verified identity for a new
98
118
  payee registration (`requiresIdentityAttestation` - Wise and PayPal today).
99
119
  An already-registered Wise or PayPal handle can be reused with bare payee data.
@@ -107,6 +127,10 @@ they are available. A source-routed result includes both a flat
107
127
  `source.txHashes` list and chain-aware `source.transactions.origin` /
108
128
  `.destination` entries.
109
129
 
130
+ - `SOURCE_EXECUTION_FAILED` where only the approval landed: the Relay request
131
+ can stay in `relayStatus` `waiting` indefinitely. Decide from the error's
132
+ recovery payload and origin transactions, never by waiting for a terminal
133
+ Relay status.
110
134
  - `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED`: Relay completed, but the Base
111
135
  cashout was not created. Do not route again. Retry a Base-USDC-only
112
136
  `cashout()` with `BigInt(error.recovery.amount)`.
@@ -189,6 +189,12 @@ var errors = {
189
189
  },
190
190
  { cause }
191
191
  ),
192
+ sourceNonceManagerRequired: (transactionCount) => new CashError({
193
+ code: "SOURCE_NONCE_MANAGER_REQUIRED",
194
+ message: `This Relay route submits ${transactionCount} source-chain transactions, but the local signer has no nonce manager; the route transaction would reuse the approval nonce and revert.`,
195
+ retryable: false,
196
+ remediation: `Create the source signer with viem's nonce manager - privateKeyToAccount(pk, { nonceManager }) - then request a fresh quote and retry. No transaction was submitted.`
197
+ }),
192
198
  sourceExecutionFailed: (cause, evidence) => new CashError(
193
199
  {
194
200
  code: "SOURCE_EXECUTION_FAILED",
package/dist/index.cjs CHANGED
@@ -623,6 +623,12 @@ var errors = {
623
623
  },
624
624
  { cause }
625
625
  ),
626
+ sourceNonceManagerRequired: (transactionCount) => new CashError({
627
+ code: "SOURCE_NONCE_MANAGER_REQUIRED",
628
+ message: `This Relay route submits ${transactionCount} source-chain transactions, but the local signer has no nonce manager; the route transaction would reuse the approval nonce and revert.`,
629
+ retryable: false,
630
+ remediation: `Create the source signer with viem's nonce manager - privateKeyToAccount(pk, { nonceManager }) - then request a fresh quote and retry. No transaction was submitted.`
631
+ }),
626
632
  sourceExecutionFailed: (cause, evidence) => new CashError(
627
633
  {
628
634
  code: "SOURCE_EXECUTION_FAILED",
@@ -1048,6 +1054,14 @@ async function assertRelayExecutionIdentity(quote, wallet, expectedRecipient) {
1048
1054
  throw new Error("Relay quote recipient does not match the expected Base recipient");
1049
1055
  }
1050
1056
  }
1057
+ function assertRelayNonceManagement(quote, wallet) {
1058
+ const account = wallet.account;
1059
+ if (!account || account.type !== "local" || account.nonceManager !== void 0) return;
1060
+ const transactionCount = quote.steps.flatMap(
1061
+ (step) => step.items.filter((item) => asString(asRecord(item.data).to) !== void 0)
1062
+ ).length;
1063
+ if (transactionCount > 1) throw errors.sourceNonceManagerRequired(transactionCount);
1064
+ }
1051
1065
  function isRelaySecretKey(key) {
1052
1066
  const normalized = key.toLowerCase();
1053
1067
  return normalized === "headers" || normalized === "apikey";
@@ -1249,6 +1263,7 @@ async function executeRelayQuote(quote, wallet, options = {}) {
1249
1263
  observedRequestId = quoteRequestId(rawQuote);
1250
1264
  assertCanonicalRelayDestination(rawQuote);
1251
1265
  await assertRelayExecutionIdentity(rawQuote, wallet, options.recipient);
1266
+ assertRelayNonceManagement(rawQuote, wallet);
1252
1267
  const client = relayClient(options.relay);
1253
1268
  const sourceChainId = quoteSourceChainId(rawQuote);
1254
1269
  if (sourceChainId !== void 0 && !(client.chains ?? []).some((chain) => chain.id === sourceChainId)) {
@@ -1717,32 +1732,51 @@ function createCashClient(options) {
1717
1732
  (transaction) => transaction.chainId === BASE_CHAIN_ID
1718
1733
  );
1719
1734
  const batchIds = baseTransactions.filter((transaction) => transaction.isBatchTx === true).map((transaction) => transaction.hash);
1735
+ const batchExecutionFailed = (cause) => errors.sourceExecutionFailed(cause, {
1736
+ ...executed.requestId ? { requestId: executed.requestId } : {},
1737
+ txHashes: executed.txHashes,
1738
+ ...executed.transactions ? { transactions: executed.transactions } : {}
1739
+ });
1720
1740
  let batchTransactionHashes = [];
1721
1741
  if (batchIds.length > 0) {
1722
1742
  let batchError;
1723
1743
  let batchesComplete = false;
1744
+ let batchesSucceededWithoutReceipts = false;
1724
1745
  for (let attempt = 0; attempt < 20; attempt++) {
1725
1746
  try {
1726
1747
  const statuses = await Promise.all(
1727
1748
  batchIds.map((id) => sourceSigner.getCallsStatus({ id }))
1728
1749
  );
1729
1750
  if (statuses.some((status) => status.status === "failure")) {
1730
- throw new Error("Relay wallet call bundle failed");
1751
+ throw batchExecutionFailed(new Error("Relay wallet call bundle failed"));
1731
1752
  }
1732
1753
  if (statuses.every((status) => status.status === "success")) {
1733
- batchTransactionHashes = statuses.flatMap(
1734
- (status) => (status.receipts ?? []).map((receipt) => receipt.transactionHash)
1735
- );
1736
- batchesComplete = true;
1737
- break;
1754
+ const receiptGroups = statuses.map((status) => status.receipts ?? []);
1755
+ if (receiptGroups.some((receipts) => receipts.length === 0)) {
1756
+ batchError = new Error(
1757
+ "Relay wallet call bundle did not include transaction receipts"
1758
+ );
1759
+ batchesSucceededWithoutReceipts = true;
1760
+ break;
1761
+ } else {
1762
+ batchTransactionHashes = receiptGroups.flatMap(
1763
+ (receipts) => receipts.map((receipt) => receipt.transactionHash)
1764
+ );
1765
+ batchesComplete = true;
1766
+ break;
1767
+ }
1738
1768
  }
1739
1769
  } catch (err) {
1770
+ if (isCashError(err)) throw err;
1740
1771
  batchError = err;
1741
1772
  }
1742
1773
  await sleep(250);
1743
1774
  }
1744
1775
  if (!batchesComplete) {
1745
- throw batchError ?? new Error("Relay wallet call bundle did not complete");
1776
+ if (batchesSucceededWithoutReceipts) throw batchError;
1777
+ throw batchExecutionFailed(
1778
+ batchError ?? new Error("Relay wallet call bundle did not complete")
1779
+ );
1746
1780
  }
1747
1781
  }
1748
1782
  const hashes = [
@@ -1864,6 +1898,7 @@ function createCashClient(options) {
1864
1898
  executed
1865
1899
  );
1866
1900
  } catch (err) {
1901
+ if (isCashError(err)) throw err;
1867
1902
  throw errors.sourceRouteCompletedCashoutFailed(
1868
1903
  routedSource,
1869
1904
  mapChainError("resolve same-chain Relay nonce", err)
@@ -2539,6 +2574,7 @@ var CASH_ERROR_CODES = defineCashErrorCodes([
2539
2574
  "SOURCE_RECIPIENT_MISMATCH",
2540
2575
  "SOURCE_CAPABILITIES_FAILED",
2541
2576
  "SOURCE_QUOTE_FAILED",
2577
+ "SOURCE_NONCE_MANAGER_REQUIRED",
2542
2578
  "SOURCE_EXECUTION_FAILED",
2543
2579
  "SOURCE_STATUS_FAILED",
2544
2580
  "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED",
package/dist/index.d.cts CHANGED
@@ -10,7 +10,7 @@ import '@relayprotocol/relay-sdk';
10
10
  * Typed errors - every failure carries a `code`, whether it is `retryable`,
11
11
  * and a `remediation` sentence so agents can self-drive recovery.
12
12
  */
13
- type CashErrorCode = 'ORACLE_UNSUPPORTED_CURRENCY' | 'ORACLE_READ_FAILED' | 'UNSUPPORTED_PLATFORM' | 'UNSUPPORTED_PLATFORM_CURRENCY' | 'AMOUNT_BELOW_MINIMUM' | 'INVALID_INTENT_AMOUNT_RANGE' | 'ACTIVE_INTENT_BLOCKS_WITHDRAWAL' | 'NOTHING_TO_WITHDRAW' | 'INSUFFICIENT_AVAILABLE_FUNDS' | 'INSUFFICIENT_TOKEN_BALANCE' | 'ORDER_NOT_ACTIVE' | 'INVALID_DEPOSIT_ID' | 'ESCROW_PAUSED' | 'INDEXER_LAG' | 'INDEXER_UNAVAILABLE' | 'ORDER_NOT_FOUND' | 'PAYEE_REGISTRATION_FAILED' | 'PAYEE_VERIFICATION_REQUIRED' | 'SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE' | 'SOURCE_RECIPIENT_MISMATCH' | 'SOURCE_CAPABILITIES_FAILED' | 'SOURCE_QUOTE_FAILED' | 'SOURCE_EXECUTION_FAILED' | 'SOURCE_STATUS_FAILED' | 'SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED' | 'SOURCE_CASHOUT_SUBMISSION_UNKNOWN' | 'SOURCE_CASHOUT_STATUS_UNKNOWN' | 'DEPOSIT_RESOLUTION_FAILED' | 'ALLOWANCE_NOT_VISIBLE' | 'SIGNER_REQUIRED' | 'SIGNER_CHAIN_MISMATCH' | 'SIGNER_CHAIN_UNAVAILABLE' | 'WATCH_TIMEOUT' | 'TRANSACTION_FAILED' | 'TRANSACTION_SUBMISSION_UNKNOWN' | 'TRANSACTION_STATUS_UNKNOWN';
13
+ type CashErrorCode = 'ORACLE_UNSUPPORTED_CURRENCY' | 'ORACLE_READ_FAILED' | 'UNSUPPORTED_PLATFORM' | 'UNSUPPORTED_PLATFORM_CURRENCY' | 'AMOUNT_BELOW_MINIMUM' | 'INVALID_INTENT_AMOUNT_RANGE' | 'ACTIVE_INTENT_BLOCKS_WITHDRAWAL' | 'NOTHING_TO_WITHDRAW' | 'INSUFFICIENT_AVAILABLE_FUNDS' | 'INSUFFICIENT_TOKEN_BALANCE' | 'ORDER_NOT_ACTIVE' | 'INVALID_DEPOSIT_ID' | 'ESCROW_PAUSED' | 'INDEXER_LAG' | 'INDEXER_UNAVAILABLE' | 'ORDER_NOT_FOUND' | 'PAYEE_REGISTRATION_FAILED' | 'PAYEE_VERIFICATION_REQUIRED' | 'SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE' | 'SOURCE_RECIPIENT_MISMATCH' | 'SOURCE_CAPABILITIES_FAILED' | 'SOURCE_QUOTE_FAILED' | 'SOURCE_NONCE_MANAGER_REQUIRED' | 'SOURCE_EXECUTION_FAILED' | 'SOURCE_STATUS_FAILED' | 'SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED' | 'SOURCE_CASHOUT_SUBMISSION_UNKNOWN' | 'SOURCE_CASHOUT_STATUS_UNKNOWN' | 'DEPOSIT_RESOLUTION_FAILED' | 'ALLOWANCE_NOT_VISIBLE' | 'SIGNER_REQUIRED' | 'SIGNER_CHAIN_MISMATCH' | 'SIGNER_CHAIN_UNAVAILABLE' | 'WATCH_TIMEOUT' | 'TRANSACTION_FAILED' | 'TRANSACTION_SUBMISSION_UNKNOWN' | 'TRANSACTION_STATUS_UNKNOWN';
14
14
  interface CashErrorShape {
15
15
  code: CashErrorCode;
16
16
  message: string;
@@ -92,6 +92,7 @@ declare const errors: {
92
92
  sourceRecipientMismatch: (recipient: string, owner: string) => CashError;
93
93
  sourceCapabilitiesFailed: (cause?: unknown) => CashError;
94
94
  sourceQuoteFailed: (cause?: unknown) => CashError;
95
+ sourceNonceManagerRequired: (transactionCount: number) => CashError;
95
96
  sourceExecutionFailed: (cause?: unknown, evidence?: {
96
97
  requestId?: string;
97
98
  txHashes: string[];
@@ -3153,7 +3154,7 @@ declare const cashErrorRecoveryJsonSchema: z.ZodDiscriminatedUnion<"kind", [z.Zo
3153
3154
  operation: string;
3154
3155
  }>]>;
3155
3156
  declare const cashErrorJsonSchema: z.ZodObject<{
3156
- code: z.ZodEnum<["ORACLE_UNSUPPORTED_CURRENCY", "ORACLE_READ_FAILED", "UNSUPPORTED_PLATFORM", "UNSUPPORTED_PLATFORM_CURRENCY", "AMOUNT_BELOW_MINIMUM", "INVALID_INTENT_AMOUNT_RANGE", "ACTIVE_INTENT_BLOCKS_WITHDRAWAL", "NOTHING_TO_WITHDRAW", "INSUFFICIENT_AVAILABLE_FUNDS", "INSUFFICIENT_TOKEN_BALANCE", "ORDER_NOT_ACTIVE", "INVALID_DEPOSIT_ID", "ESCROW_PAUSED", "INDEXER_LAG", "INDEXER_UNAVAILABLE", "ORDER_NOT_FOUND", "PAYEE_REGISTRATION_FAILED", "PAYEE_VERIFICATION_REQUIRED", "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE", "SOURCE_RECIPIENT_MISMATCH", "SOURCE_CAPABILITIES_FAILED", "SOURCE_QUOTE_FAILED", "SOURCE_EXECUTION_FAILED", "SOURCE_STATUS_FAILED", "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED", "SOURCE_CASHOUT_SUBMISSION_UNKNOWN", "SOURCE_CASHOUT_STATUS_UNKNOWN", "DEPOSIT_RESOLUTION_FAILED", "ALLOWANCE_NOT_VISIBLE", "SIGNER_REQUIRED", "SIGNER_CHAIN_MISMATCH", "SIGNER_CHAIN_UNAVAILABLE", "WATCH_TIMEOUT", "TRANSACTION_FAILED", "TRANSACTION_SUBMISSION_UNKNOWN", "TRANSACTION_STATUS_UNKNOWN"]>;
3157
+ code: z.ZodEnum<["ORACLE_UNSUPPORTED_CURRENCY", "ORACLE_READ_FAILED", "UNSUPPORTED_PLATFORM", "UNSUPPORTED_PLATFORM_CURRENCY", "AMOUNT_BELOW_MINIMUM", "INVALID_INTENT_AMOUNT_RANGE", "ACTIVE_INTENT_BLOCKS_WITHDRAWAL", "NOTHING_TO_WITHDRAW", "INSUFFICIENT_AVAILABLE_FUNDS", "INSUFFICIENT_TOKEN_BALANCE", "ORDER_NOT_ACTIVE", "INVALID_DEPOSIT_ID", "ESCROW_PAUSED", "INDEXER_LAG", "INDEXER_UNAVAILABLE", "ORDER_NOT_FOUND", "PAYEE_REGISTRATION_FAILED", "PAYEE_VERIFICATION_REQUIRED", "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE", "SOURCE_RECIPIENT_MISMATCH", "SOURCE_CAPABILITIES_FAILED", "SOURCE_QUOTE_FAILED", "SOURCE_NONCE_MANAGER_REQUIRED", "SOURCE_EXECUTION_FAILED", "SOURCE_STATUS_FAILED", "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED", "SOURCE_CASHOUT_SUBMISSION_UNKNOWN", "SOURCE_CASHOUT_STATUS_UNKNOWN", "DEPOSIT_RESOLUTION_FAILED", "ALLOWANCE_NOT_VISIBLE", "SIGNER_REQUIRED", "SIGNER_CHAIN_MISMATCH", "SIGNER_CHAIN_UNAVAILABLE", "WATCH_TIMEOUT", "TRANSACTION_FAILED", "TRANSACTION_SUBMISSION_UNKNOWN", "TRANSACTION_STATUS_UNKNOWN"]>;
3157
3158
  message: z.ZodString;
3158
3159
  retryable: z.ZodBoolean;
3159
3160
  remediation: z.ZodString;
@@ -3540,7 +3541,7 @@ declare const cashErrorJsonSchema: z.ZodObject<{
3540
3541
  }>]>>;
3541
3542
  }, "strict", z.ZodTypeAny, {
3542
3543
  message: string;
3543
- code: "ORACLE_UNSUPPORTED_CURRENCY" | "ORACLE_READ_FAILED" | "UNSUPPORTED_PLATFORM" | "UNSUPPORTED_PLATFORM_CURRENCY" | "AMOUNT_BELOW_MINIMUM" | "INVALID_INTENT_AMOUNT_RANGE" | "ACTIVE_INTENT_BLOCKS_WITHDRAWAL" | "NOTHING_TO_WITHDRAW" | "INSUFFICIENT_AVAILABLE_FUNDS" | "INSUFFICIENT_TOKEN_BALANCE" | "ORDER_NOT_ACTIVE" | "INVALID_DEPOSIT_ID" | "ESCROW_PAUSED" | "INDEXER_LAG" | "INDEXER_UNAVAILABLE" | "ORDER_NOT_FOUND" | "PAYEE_REGISTRATION_FAILED" | "PAYEE_VERIFICATION_REQUIRED" | "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE" | "SOURCE_RECIPIENT_MISMATCH" | "SOURCE_CAPABILITIES_FAILED" | "SOURCE_QUOTE_FAILED" | "SOURCE_EXECUTION_FAILED" | "SOURCE_STATUS_FAILED" | "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED" | "SOURCE_CASHOUT_SUBMISSION_UNKNOWN" | "SOURCE_CASHOUT_STATUS_UNKNOWN" | "DEPOSIT_RESOLUTION_FAILED" | "ALLOWANCE_NOT_VISIBLE" | "SIGNER_REQUIRED" | "SIGNER_CHAIN_MISMATCH" | "SIGNER_CHAIN_UNAVAILABLE" | "WATCH_TIMEOUT" | "TRANSACTION_FAILED" | "TRANSACTION_SUBMISSION_UNKNOWN" | "TRANSACTION_STATUS_UNKNOWN";
3544
+ code: "ORACLE_UNSUPPORTED_CURRENCY" | "ORACLE_READ_FAILED" | "UNSUPPORTED_PLATFORM" | "UNSUPPORTED_PLATFORM_CURRENCY" | "AMOUNT_BELOW_MINIMUM" | "INVALID_INTENT_AMOUNT_RANGE" | "ACTIVE_INTENT_BLOCKS_WITHDRAWAL" | "NOTHING_TO_WITHDRAW" | "INSUFFICIENT_AVAILABLE_FUNDS" | "INSUFFICIENT_TOKEN_BALANCE" | "ORDER_NOT_ACTIVE" | "INVALID_DEPOSIT_ID" | "ESCROW_PAUSED" | "INDEXER_LAG" | "INDEXER_UNAVAILABLE" | "ORDER_NOT_FOUND" | "PAYEE_REGISTRATION_FAILED" | "PAYEE_VERIFICATION_REQUIRED" | "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE" | "SOURCE_RECIPIENT_MISMATCH" | "SOURCE_CAPABILITIES_FAILED" | "SOURCE_QUOTE_FAILED" | "SOURCE_NONCE_MANAGER_REQUIRED" | "SOURCE_EXECUTION_FAILED" | "SOURCE_STATUS_FAILED" | "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED" | "SOURCE_CASHOUT_SUBMISSION_UNKNOWN" | "SOURCE_CASHOUT_STATUS_UNKNOWN" | "DEPOSIT_RESOLUTION_FAILED" | "ALLOWANCE_NOT_VISIBLE" | "SIGNER_REQUIRED" | "SIGNER_CHAIN_MISMATCH" | "SIGNER_CHAIN_UNAVAILABLE" | "WATCH_TIMEOUT" | "TRANSACTION_FAILED" | "TRANSACTION_SUBMISSION_UNKNOWN" | "TRANSACTION_STATUS_UNKNOWN";
3544
3545
  retryable: boolean;
3545
3546
  remediation: string;
3546
3547
  recovery?: {
@@ -3622,7 +3623,7 @@ declare const cashErrorJsonSchema: z.ZodObject<{
3622
3623
  } | undefined;
3623
3624
  }, {
3624
3625
  message: string;
3625
- code: "ORACLE_UNSUPPORTED_CURRENCY" | "ORACLE_READ_FAILED" | "UNSUPPORTED_PLATFORM" | "UNSUPPORTED_PLATFORM_CURRENCY" | "AMOUNT_BELOW_MINIMUM" | "INVALID_INTENT_AMOUNT_RANGE" | "ACTIVE_INTENT_BLOCKS_WITHDRAWAL" | "NOTHING_TO_WITHDRAW" | "INSUFFICIENT_AVAILABLE_FUNDS" | "INSUFFICIENT_TOKEN_BALANCE" | "ORDER_NOT_ACTIVE" | "INVALID_DEPOSIT_ID" | "ESCROW_PAUSED" | "INDEXER_LAG" | "INDEXER_UNAVAILABLE" | "ORDER_NOT_FOUND" | "PAYEE_REGISTRATION_FAILED" | "PAYEE_VERIFICATION_REQUIRED" | "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE" | "SOURCE_RECIPIENT_MISMATCH" | "SOURCE_CAPABILITIES_FAILED" | "SOURCE_QUOTE_FAILED" | "SOURCE_EXECUTION_FAILED" | "SOURCE_STATUS_FAILED" | "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED" | "SOURCE_CASHOUT_SUBMISSION_UNKNOWN" | "SOURCE_CASHOUT_STATUS_UNKNOWN" | "DEPOSIT_RESOLUTION_FAILED" | "ALLOWANCE_NOT_VISIBLE" | "SIGNER_REQUIRED" | "SIGNER_CHAIN_MISMATCH" | "SIGNER_CHAIN_UNAVAILABLE" | "WATCH_TIMEOUT" | "TRANSACTION_FAILED" | "TRANSACTION_SUBMISSION_UNKNOWN" | "TRANSACTION_STATUS_UNKNOWN";
3626
+ code: "ORACLE_UNSUPPORTED_CURRENCY" | "ORACLE_READ_FAILED" | "UNSUPPORTED_PLATFORM" | "UNSUPPORTED_PLATFORM_CURRENCY" | "AMOUNT_BELOW_MINIMUM" | "INVALID_INTENT_AMOUNT_RANGE" | "ACTIVE_INTENT_BLOCKS_WITHDRAWAL" | "NOTHING_TO_WITHDRAW" | "INSUFFICIENT_AVAILABLE_FUNDS" | "INSUFFICIENT_TOKEN_BALANCE" | "ORDER_NOT_ACTIVE" | "INVALID_DEPOSIT_ID" | "ESCROW_PAUSED" | "INDEXER_LAG" | "INDEXER_UNAVAILABLE" | "ORDER_NOT_FOUND" | "PAYEE_REGISTRATION_FAILED" | "PAYEE_VERIFICATION_REQUIRED" | "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE" | "SOURCE_RECIPIENT_MISMATCH" | "SOURCE_CAPABILITIES_FAILED" | "SOURCE_QUOTE_FAILED" | "SOURCE_NONCE_MANAGER_REQUIRED" | "SOURCE_EXECUTION_FAILED" | "SOURCE_STATUS_FAILED" | "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED" | "SOURCE_CASHOUT_SUBMISSION_UNKNOWN" | "SOURCE_CASHOUT_STATUS_UNKNOWN" | "DEPOSIT_RESOLUTION_FAILED" | "ALLOWANCE_NOT_VISIBLE" | "SIGNER_REQUIRED" | "SIGNER_CHAIN_MISMATCH" | "SIGNER_CHAIN_UNAVAILABLE" | "WATCH_TIMEOUT" | "TRANSACTION_FAILED" | "TRANSACTION_SUBMISSION_UNKNOWN" | "TRANSACTION_STATUS_UNKNOWN";
3626
3627
  retryable: boolean;
3627
3628
  remediation: string;
3628
3629
  recovery?: {
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ import '@relayprotocol/relay-sdk';
10
10
  * Typed errors - every failure carries a `code`, whether it is `retryable`,
11
11
  * and a `remediation` sentence so agents can self-drive recovery.
12
12
  */
13
- type CashErrorCode = 'ORACLE_UNSUPPORTED_CURRENCY' | 'ORACLE_READ_FAILED' | 'UNSUPPORTED_PLATFORM' | 'UNSUPPORTED_PLATFORM_CURRENCY' | 'AMOUNT_BELOW_MINIMUM' | 'INVALID_INTENT_AMOUNT_RANGE' | 'ACTIVE_INTENT_BLOCKS_WITHDRAWAL' | 'NOTHING_TO_WITHDRAW' | 'INSUFFICIENT_AVAILABLE_FUNDS' | 'INSUFFICIENT_TOKEN_BALANCE' | 'ORDER_NOT_ACTIVE' | 'INVALID_DEPOSIT_ID' | 'ESCROW_PAUSED' | 'INDEXER_LAG' | 'INDEXER_UNAVAILABLE' | 'ORDER_NOT_FOUND' | 'PAYEE_REGISTRATION_FAILED' | 'PAYEE_VERIFICATION_REQUIRED' | 'SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE' | 'SOURCE_RECIPIENT_MISMATCH' | 'SOURCE_CAPABILITIES_FAILED' | 'SOURCE_QUOTE_FAILED' | 'SOURCE_EXECUTION_FAILED' | 'SOURCE_STATUS_FAILED' | 'SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED' | 'SOURCE_CASHOUT_SUBMISSION_UNKNOWN' | 'SOURCE_CASHOUT_STATUS_UNKNOWN' | 'DEPOSIT_RESOLUTION_FAILED' | 'ALLOWANCE_NOT_VISIBLE' | 'SIGNER_REQUIRED' | 'SIGNER_CHAIN_MISMATCH' | 'SIGNER_CHAIN_UNAVAILABLE' | 'WATCH_TIMEOUT' | 'TRANSACTION_FAILED' | 'TRANSACTION_SUBMISSION_UNKNOWN' | 'TRANSACTION_STATUS_UNKNOWN';
13
+ type CashErrorCode = 'ORACLE_UNSUPPORTED_CURRENCY' | 'ORACLE_READ_FAILED' | 'UNSUPPORTED_PLATFORM' | 'UNSUPPORTED_PLATFORM_CURRENCY' | 'AMOUNT_BELOW_MINIMUM' | 'INVALID_INTENT_AMOUNT_RANGE' | 'ACTIVE_INTENT_BLOCKS_WITHDRAWAL' | 'NOTHING_TO_WITHDRAW' | 'INSUFFICIENT_AVAILABLE_FUNDS' | 'INSUFFICIENT_TOKEN_BALANCE' | 'ORDER_NOT_ACTIVE' | 'INVALID_DEPOSIT_ID' | 'ESCROW_PAUSED' | 'INDEXER_LAG' | 'INDEXER_UNAVAILABLE' | 'ORDER_NOT_FOUND' | 'PAYEE_REGISTRATION_FAILED' | 'PAYEE_VERIFICATION_REQUIRED' | 'SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE' | 'SOURCE_RECIPIENT_MISMATCH' | 'SOURCE_CAPABILITIES_FAILED' | 'SOURCE_QUOTE_FAILED' | 'SOURCE_NONCE_MANAGER_REQUIRED' | 'SOURCE_EXECUTION_FAILED' | 'SOURCE_STATUS_FAILED' | 'SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED' | 'SOURCE_CASHOUT_SUBMISSION_UNKNOWN' | 'SOURCE_CASHOUT_STATUS_UNKNOWN' | 'DEPOSIT_RESOLUTION_FAILED' | 'ALLOWANCE_NOT_VISIBLE' | 'SIGNER_REQUIRED' | 'SIGNER_CHAIN_MISMATCH' | 'SIGNER_CHAIN_UNAVAILABLE' | 'WATCH_TIMEOUT' | 'TRANSACTION_FAILED' | 'TRANSACTION_SUBMISSION_UNKNOWN' | 'TRANSACTION_STATUS_UNKNOWN';
14
14
  interface CashErrorShape {
15
15
  code: CashErrorCode;
16
16
  message: string;
@@ -92,6 +92,7 @@ declare const errors: {
92
92
  sourceRecipientMismatch: (recipient: string, owner: string) => CashError;
93
93
  sourceCapabilitiesFailed: (cause?: unknown) => CashError;
94
94
  sourceQuoteFailed: (cause?: unknown) => CashError;
95
+ sourceNonceManagerRequired: (transactionCount: number) => CashError;
95
96
  sourceExecutionFailed: (cause?: unknown, evidence?: {
96
97
  requestId?: string;
97
98
  txHashes: string[];
@@ -3153,7 +3154,7 @@ declare const cashErrorRecoveryJsonSchema: z.ZodDiscriminatedUnion<"kind", [z.Zo
3153
3154
  operation: string;
3154
3155
  }>]>;
3155
3156
  declare const cashErrorJsonSchema: z.ZodObject<{
3156
- code: z.ZodEnum<["ORACLE_UNSUPPORTED_CURRENCY", "ORACLE_READ_FAILED", "UNSUPPORTED_PLATFORM", "UNSUPPORTED_PLATFORM_CURRENCY", "AMOUNT_BELOW_MINIMUM", "INVALID_INTENT_AMOUNT_RANGE", "ACTIVE_INTENT_BLOCKS_WITHDRAWAL", "NOTHING_TO_WITHDRAW", "INSUFFICIENT_AVAILABLE_FUNDS", "INSUFFICIENT_TOKEN_BALANCE", "ORDER_NOT_ACTIVE", "INVALID_DEPOSIT_ID", "ESCROW_PAUSED", "INDEXER_LAG", "INDEXER_UNAVAILABLE", "ORDER_NOT_FOUND", "PAYEE_REGISTRATION_FAILED", "PAYEE_VERIFICATION_REQUIRED", "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE", "SOURCE_RECIPIENT_MISMATCH", "SOURCE_CAPABILITIES_FAILED", "SOURCE_QUOTE_FAILED", "SOURCE_EXECUTION_FAILED", "SOURCE_STATUS_FAILED", "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED", "SOURCE_CASHOUT_SUBMISSION_UNKNOWN", "SOURCE_CASHOUT_STATUS_UNKNOWN", "DEPOSIT_RESOLUTION_FAILED", "ALLOWANCE_NOT_VISIBLE", "SIGNER_REQUIRED", "SIGNER_CHAIN_MISMATCH", "SIGNER_CHAIN_UNAVAILABLE", "WATCH_TIMEOUT", "TRANSACTION_FAILED", "TRANSACTION_SUBMISSION_UNKNOWN", "TRANSACTION_STATUS_UNKNOWN"]>;
3157
+ code: z.ZodEnum<["ORACLE_UNSUPPORTED_CURRENCY", "ORACLE_READ_FAILED", "UNSUPPORTED_PLATFORM", "UNSUPPORTED_PLATFORM_CURRENCY", "AMOUNT_BELOW_MINIMUM", "INVALID_INTENT_AMOUNT_RANGE", "ACTIVE_INTENT_BLOCKS_WITHDRAWAL", "NOTHING_TO_WITHDRAW", "INSUFFICIENT_AVAILABLE_FUNDS", "INSUFFICIENT_TOKEN_BALANCE", "ORDER_NOT_ACTIVE", "INVALID_DEPOSIT_ID", "ESCROW_PAUSED", "INDEXER_LAG", "INDEXER_UNAVAILABLE", "ORDER_NOT_FOUND", "PAYEE_REGISTRATION_FAILED", "PAYEE_VERIFICATION_REQUIRED", "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE", "SOURCE_RECIPIENT_MISMATCH", "SOURCE_CAPABILITIES_FAILED", "SOURCE_QUOTE_FAILED", "SOURCE_NONCE_MANAGER_REQUIRED", "SOURCE_EXECUTION_FAILED", "SOURCE_STATUS_FAILED", "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED", "SOURCE_CASHOUT_SUBMISSION_UNKNOWN", "SOURCE_CASHOUT_STATUS_UNKNOWN", "DEPOSIT_RESOLUTION_FAILED", "ALLOWANCE_NOT_VISIBLE", "SIGNER_REQUIRED", "SIGNER_CHAIN_MISMATCH", "SIGNER_CHAIN_UNAVAILABLE", "WATCH_TIMEOUT", "TRANSACTION_FAILED", "TRANSACTION_SUBMISSION_UNKNOWN", "TRANSACTION_STATUS_UNKNOWN"]>;
3157
3158
  message: z.ZodString;
3158
3159
  retryable: z.ZodBoolean;
3159
3160
  remediation: z.ZodString;
@@ -3540,7 +3541,7 @@ declare const cashErrorJsonSchema: z.ZodObject<{
3540
3541
  }>]>>;
3541
3542
  }, "strict", z.ZodTypeAny, {
3542
3543
  message: string;
3543
- code: "ORACLE_UNSUPPORTED_CURRENCY" | "ORACLE_READ_FAILED" | "UNSUPPORTED_PLATFORM" | "UNSUPPORTED_PLATFORM_CURRENCY" | "AMOUNT_BELOW_MINIMUM" | "INVALID_INTENT_AMOUNT_RANGE" | "ACTIVE_INTENT_BLOCKS_WITHDRAWAL" | "NOTHING_TO_WITHDRAW" | "INSUFFICIENT_AVAILABLE_FUNDS" | "INSUFFICIENT_TOKEN_BALANCE" | "ORDER_NOT_ACTIVE" | "INVALID_DEPOSIT_ID" | "ESCROW_PAUSED" | "INDEXER_LAG" | "INDEXER_UNAVAILABLE" | "ORDER_NOT_FOUND" | "PAYEE_REGISTRATION_FAILED" | "PAYEE_VERIFICATION_REQUIRED" | "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE" | "SOURCE_RECIPIENT_MISMATCH" | "SOURCE_CAPABILITIES_FAILED" | "SOURCE_QUOTE_FAILED" | "SOURCE_EXECUTION_FAILED" | "SOURCE_STATUS_FAILED" | "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED" | "SOURCE_CASHOUT_SUBMISSION_UNKNOWN" | "SOURCE_CASHOUT_STATUS_UNKNOWN" | "DEPOSIT_RESOLUTION_FAILED" | "ALLOWANCE_NOT_VISIBLE" | "SIGNER_REQUIRED" | "SIGNER_CHAIN_MISMATCH" | "SIGNER_CHAIN_UNAVAILABLE" | "WATCH_TIMEOUT" | "TRANSACTION_FAILED" | "TRANSACTION_SUBMISSION_UNKNOWN" | "TRANSACTION_STATUS_UNKNOWN";
3544
+ code: "ORACLE_UNSUPPORTED_CURRENCY" | "ORACLE_READ_FAILED" | "UNSUPPORTED_PLATFORM" | "UNSUPPORTED_PLATFORM_CURRENCY" | "AMOUNT_BELOW_MINIMUM" | "INVALID_INTENT_AMOUNT_RANGE" | "ACTIVE_INTENT_BLOCKS_WITHDRAWAL" | "NOTHING_TO_WITHDRAW" | "INSUFFICIENT_AVAILABLE_FUNDS" | "INSUFFICIENT_TOKEN_BALANCE" | "ORDER_NOT_ACTIVE" | "INVALID_DEPOSIT_ID" | "ESCROW_PAUSED" | "INDEXER_LAG" | "INDEXER_UNAVAILABLE" | "ORDER_NOT_FOUND" | "PAYEE_REGISTRATION_FAILED" | "PAYEE_VERIFICATION_REQUIRED" | "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE" | "SOURCE_RECIPIENT_MISMATCH" | "SOURCE_CAPABILITIES_FAILED" | "SOURCE_QUOTE_FAILED" | "SOURCE_NONCE_MANAGER_REQUIRED" | "SOURCE_EXECUTION_FAILED" | "SOURCE_STATUS_FAILED" | "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED" | "SOURCE_CASHOUT_SUBMISSION_UNKNOWN" | "SOURCE_CASHOUT_STATUS_UNKNOWN" | "DEPOSIT_RESOLUTION_FAILED" | "ALLOWANCE_NOT_VISIBLE" | "SIGNER_REQUIRED" | "SIGNER_CHAIN_MISMATCH" | "SIGNER_CHAIN_UNAVAILABLE" | "WATCH_TIMEOUT" | "TRANSACTION_FAILED" | "TRANSACTION_SUBMISSION_UNKNOWN" | "TRANSACTION_STATUS_UNKNOWN";
3544
3545
  retryable: boolean;
3545
3546
  remediation: string;
3546
3547
  recovery?: {
@@ -3622,7 +3623,7 @@ declare const cashErrorJsonSchema: z.ZodObject<{
3622
3623
  } | undefined;
3623
3624
  }, {
3624
3625
  message: string;
3625
- code: "ORACLE_UNSUPPORTED_CURRENCY" | "ORACLE_READ_FAILED" | "UNSUPPORTED_PLATFORM" | "UNSUPPORTED_PLATFORM_CURRENCY" | "AMOUNT_BELOW_MINIMUM" | "INVALID_INTENT_AMOUNT_RANGE" | "ACTIVE_INTENT_BLOCKS_WITHDRAWAL" | "NOTHING_TO_WITHDRAW" | "INSUFFICIENT_AVAILABLE_FUNDS" | "INSUFFICIENT_TOKEN_BALANCE" | "ORDER_NOT_ACTIVE" | "INVALID_DEPOSIT_ID" | "ESCROW_PAUSED" | "INDEXER_LAG" | "INDEXER_UNAVAILABLE" | "ORDER_NOT_FOUND" | "PAYEE_REGISTRATION_FAILED" | "PAYEE_VERIFICATION_REQUIRED" | "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE" | "SOURCE_RECIPIENT_MISMATCH" | "SOURCE_CAPABILITIES_FAILED" | "SOURCE_QUOTE_FAILED" | "SOURCE_EXECUTION_FAILED" | "SOURCE_STATUS_FAILED" | "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED" | "SOURCE_CASHOUT_SUBMISSION_UNKNOWN" | "SOURCE_CASHOUT_STATUS_UNKNOWN" | "DEPOSIT_RESOLUTION_FAILED" | "ALLOWANCE_NOT_VISIBLE" | "SIGNER_REQUIRED" | "SIGNER_CHAIN_MISMATCH" | "SIGNER_CHAIN_UNAVAILABLE" | "WATCH_TIMEOUT" | "TRANSACTION_FAILED" | "TRANSACTION_SUBMISSION_UNKNOWN" | "TRANSACTION_STATUS_UNKNOWN";
3626
+ code: "ORACLE_UNSUPPORTED_CURRENCY" | "ORACLE_READ_FAILED" | "UNSUPPORTED_PLATFORM" | "UNSUPPORTED_PLATFORM_CURRENCY" | "AMOUNT_BELOW_MINIMUM" | "INVALID_INTENT_AMOUNT_RANGE" | "ACTIVE_INTENT_BLOCKS_WITHDRAWAL" | "NOTHING_TO_WITHDRAW" | "INSUFFICIENT_AVAILABLE_FUNDS" | "INSUFFICIENT_TOKEN_BALANCE" | "ORDER_NOT_ACTIVE" | "INVALID_DEPOSIT_ID" | "ESCROW_PAUSED" | "INDEXER_LAG" | "INDEXER_UNAVAILABLE" | "ORDER_NOT_FOUND" | "PAYEE_REGISTRATION_FAILED" | "PAYEE_VERIFICATION_REQUIRED" | "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE" | "SOURCE_RECIPIENT_MISMATCH" | "SOURCE_CAPABILITIES_FAILED" | "SOURCE_QUOTE_FAILED" | "SOURCE_NONCE_MANAGER_REQUIRED" | "SOURCE_EXECUTION_FAILED" | "SOURCE_STATUS_FAILED" | "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED" | "SOURCE_CASHOUT_SUBMISSION_UNKNOWN" | "SOURCE_CASHOUT_STATUS_UNKNOWN" | "DEPOSIT_RESOLUTION_FAILED" | "ALLOWANCE_NOT_VISIBLE" | "SIGNER_REQUIRED" | "SIGNER_CHAIN_MISMATCH" | "SIGNER_CHAIN_UNAVAILABLE" | "WATCH_TIMEOUT" | "TRANSACTION_FAILED" | "TRANSACTION_SUBMISSION_UNKNOWN" | "TRANSACTION_STATUS_UNKNOWN";
3626
3627
  retryable: boolean;
3627
3628
  remediation: string;
3628
3629
  recovery?: {
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, errors, isCashError, CASH_ORDER_STATUSES, mapChainError, CashError } from './chunk-P3KYZ2FX.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 } from './chunk-P3KYZ2FX.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 } from './chunk-TR6JVYYF.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 } from './chunk-TR6JVYYF.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';
@@ -675,6 +675,14 @@ async function assertRelayExecutionIdentity(quote, wallet, expectedRecipient) {
675
675
  throw new Error("Relay quote recipient does not match the expected Base recipient");
676
676
  }
677
677
  }
678
+ function assertRelayNonceManagement(quote, wallet) {
679
+ const account = wallet.account;
680
+ if (!account || account.type !== "local" || account.nonceManager !== void 0) return;
681
+ const transactionCount = quote.steps.flatMap(
682
+ (step) => step.items.filter((item) => asString(asRecord(item.data).to) !== void 0)
683
+ ).length;
684
+ if (transactionCount > 1) throw errors.sourceNonceManagerRequired(transactionCount);
685
+ }
678
686
  function isRelaySecretKey(key) {
679
687
  const normalized = key.toLowerCase();
680
688
  return normalized === "headers" || normalized === "apikey";
@@ -876,6 +884,7 @@ async function executeRelayQuote(quote, wallet, options = {}) {
876
884
  observedRequestId = quoteRequestId(rawQuote);
877
885
  assertCanonicalRelayDestination(rawQuote);
878
886
  await assertRelayExecutionIdentity(rawQuote, wallet, options.recipient);
887
+ assertRelayNonceManagement(rawQuote, wallet);
879
888
  const client = relayClient(options.relay);
880
889
  const sourceChainId = quoteSourceChainId(rawQuote);
881
890
  if (sourceChainId !== void 0 && !(client.chains ?? []).some((chain) => chain.id === sourceChainId)) {
@@ -1344,32 +1353,51 @@ function createCashClient(options) {
1344
1353
  (transaction) => transaction.chainId === BASE_CHAIN_ID
1345
1354
  );
1346
1355
  const batchIds = baseTransactions.filter((transaction) => transaction.isBatchTx === true).map((transaction) => transaction.hash);
1356
+ const batchExecutionFailed = (cause) => errors.sourceExecutionFailed(cause, {
1357
+ ...executed.requestId ? { requestId: executed.requestId } : {},
1358
+ txHashes: executed.txHashes,
1359
+ ...executed.transactions ? { transactions: executed.transactions } : {}
1360
+ });
1347
1361
  let batchTransactionHashes = [];
1348
1362
  if (batchIds.length > 0) {
1349
1363
  let batchError;
1350
1364
  let batchesComplete = false;
1365
+ let batchesSucceededWithoutReceipts = false;
1351
1366
  for (let attempt = 0; attempt < 20; attempt++) {
1352
1367
  try {
1353
1368
  const statuses = await Promise.all(
1354
1369
  batchIds.map((id) => sourceSigner.getCallsStatus({ id }))
1355
1370
  );
1356
1371
  if (statuses.some((status) => status.status === "failure")) {
1357
- throw new Error("Relay wallet call bundle failed");
1372
+ throw batchExecutionFailed(new Error("Relay wallet call bundle failed"));
1358
1373
  }
1359
1374
  if (statuses.every((status) => status.status === "success")) {
1360
- batchTransactionHashes = statuses.flatMap(
1361
- (status) => (status.receipts ?? []).map((receipt) => receipt.transactionHash)
1362
- );
1363
- batchesComplete = true;
1364
- break;
1375
+ const receiptGroups = statuses.map((status) => status.receipts ?? []);
1376
+ if (receiptGroups.some((receipts) => receipts.length === 0)) {
1377
+ batchError = new Error(
1378
+ "Relay wallet call bundle did not include transaction receipts"
1379
+ );
1380
+ batchesSucceededWithoutReceipts = true;
1381
+ break;
1382
+ } else {
1383
+ batchTransactionHashes = receiptGroups.flatMap(
1384
+ (receipts) => receipts.map((receipt) => receipt.transactionHash)
1385
+ );
1386
+ batchesComplete = true;
1387
+ break;
1388
+ }
1365
1389
  }
1366
1390
  } catch (err) {
1391
+ if (isCashError(err)) throw err;
1367
1392
  batchError = err;
1368
1393
  }
1369
1394
  await sleep(250);
1370
1395
  }
1371
1396
  if (!batchesComplete) {
1372
- throw batchError ?? new Error("Relay wallet call bundle did not complete");
1397
+ if (batchesSucceededWithoutReceipts) throw batchError;
1398
+ throw batchExecutionFailed(
1399
+ batchError ?? new Error("Relay wallet call bundle did not complete")
1400
+ );
1373
1401
  }
1374
1402
  }
1375
1403
  const hashes = [
@@ -1491,6 +1519,7 @@ function createCashClient(options) {
1491
1519
  executed
1492
1520
  );
1493
1521
  } catch (err) {
1522
+ if (isCashError(err)) throw err;
1494
1523
  throw errors.sourceRouteCompletedCashoutFailed(
1495
1524
  routedSource,
1496
1525
  mapChainError("resolve same-chain Relay nonce", err)
@@ -2166,6 +2195,7 @@ var CASH_ERROR_CODES = defineCashErrorCodes([
2166
2195
  "SOURCE_RECIPIENT_MISMATCH",
2167
2196
  "SOURCE_CAPABILITIES_FAILED",
2168
2197
  "SOURCE_QUOTE_FAILED",
2198
+ "SOURCE_NONCE_MANAGER_REQUIRED",
2169
2199
  "SOURCE_EXECUTION_FAILED",
2170
2200
  "SOURCE_STATUS_FAILED",
2171
2201
  "SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED",
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isCashError, CASH_ORDER_POLL_INTERVAL_MS } from './chunk-P3KYZ2FX.js';
1
+ import { isCashError, CASH_ORDER_POLL_INTERVAL_MS } from './chunk-TR6JVYYF.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.1.5"};
5
+ version: "0.1.7"};
6
6
 
7
7
  // src/tools/index.ts
8
8
  var bigintString = {
package/dist/tools.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // package.json
2
2
  var package_default = {
3
- version: "0.1.5"};
3
+ version: "0.1.7"};
4
4
 
5
5
  // src/tools/index.ts
6
6
  var bigintString = {
@@ -36,6 +36,16 @@ For any other EVM source asset, Peer Cash uses `@relayprotocol/relay-sdk`:
36
36
  `executeSourceQuote()` remains available for apps that want a separate
37
37
  bridge step.
38
38
 
39
+ Routes that need more than one source-chain transaction (an ERC-20 `approve`,
40
+ then the route transaction) are submitted back-to-back by the Relay SDK. On a
41
+ plain local account the route transaction reuses the approval's nonce and
42
+ reverts mid-route, so `executeSourceQuote()` and `cashout({ source })` refuse
43
+ multi-transaction routes up front with `SOURCE_NONCE_MANAGER_REQUIRED` unless
44
+ the source signer carries a viem nonce manager:
45
+ `privateKeyToAccount(pk, { nonceManager })`. The check fires before anything
46
+ is submitted. Browser (`json-rpc`) wallets are unaffected - the node
47
+ allocates their nonces.
48
+
39
49
  Cash-out interfaces should use `tradeType: 'EXACT_INPUT'` (also the default),
40
50
  so `amount` always means source-token base units. A `RelayQuote.outputAmount`
41
51
  is the guaranteed minimum Base USDC output. The same value becomes
@@ -62,10 +72,16 @@ destination constant.
62
72
 
63
73
  Do not retry a source route merely because the Base cashout did not finish:
64
74
 
75
+ - `SOURCE_NONCE_MANAGER_REQUIRED` is preflight: nothing was submitted.
76
+ Recreate the source signer with viem's nonce manager and execute a fresh
77
+ quote.
65
78
  - `SOURCE_EXECUTION_FAILED` means Relay execution did not report success. Check
66
79
  its `inspect-relay-route` recovery evidence, submitted wallet transactions,
67
80
  and `relayStatus(requestId)` before taking another action; a blind retry can
68
- route twice.
81
+ route twice. A failed route whose only landed transaction is the approval
82
+ can sit in `relayStatus` `waiting` indefinitely - it never becomes terminal,
83
+ so decide from the persisted recovery payload and the origin transactions,
84
+ not from Relay reaching a final status.
69
85
  - `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED` means Relay completed, but the Base
70
86
  cashout was not created. Its recovery payload has
71
87
  `kind: 'retry-base-usdc-cashout'`, the guaranteed Base USDC `amount`, Relay
@@ -132,7 +148,9 @@ UI built on this SDK is a bug in that UI.
132
148
  - **Partial withdrawal** - `withdraw(depositId, { amount })` pulls part of
133
149
  the _unlocked_ balance back out. A live buyer intent does not block it
134
150
  (their locked portion is untouched); asking for more than the unlocked
135
- balance fails with `INSUFFICIENT_AVAILABLE_FUNDS`.
151
+ balance fails with `INSUFFICIENT_AVAILABLE_FUNDS`. Accounting note: a
152
+ partial withdrawal increments `returnedAmount`; `totalAmount` records
153
+ everything the order has ever held and does not shrink.
136
154
  - There is no retain-on-empty or rate knob to manage - a cash order cleans
137
155
  itself up when fully filled, and the market rate is not configurable.
138
156
 
@@ -252,6 +270,7 @@ explicit override.
252
270
  | `SOURCE_RECIPIENT_MISMATCH` | no | Relay output recipient differs from the cashout depositor. Use the depositor address. |
253
271
  | `SOURCE_CAPABILITIES_FAILED` | yes | Relay source discovery failed. Retry or use Base USDC. |
254
272
  | `SOURCE_QUOTE_FAILED` | yes | Relay returned no valid canonical Base-USDC route. Refresh capabilities and quote again. |
273
+ | `SOURCE_NONCE_MANAGER_REQUIRED` | no | Preflight; nothing was submitted. Recreate the source signer with viem's `nonceManager` and execute a fresh quote. |
255
274
  | `SOURCE_EXECUTION_FAILED` | no | Route execution did not report success. Inspect source transactions and Relay status before retrying. |
256
275
  | `SOURCE_STATUS_FAILED` | yes | Relay status is temporarily unavailable. Retry the status read without resubmitting. |
257
276
  | `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED` | no | Relay completed but no Base cashout was created. Use the recovery amount for a Base-USDC-only retry; never repeat Relay. |
package/llms.txt CHANGED
@@ -3,8 +3,8 @@
3
3
  > Offramp-only SDK for the ZKP2P protocol: route any Relay-supported EVM source
4
4
  > asset to Base USDC, then cash out to fiat (Venmo, Revolut, Wise, Zelle, ...)
5
5
  > at the live Chainlink oracle market rate with zero spread and no centralized
6
- > off-ramp provider. The user is the maker, Peer handles the buyer side, and the
7
- > SDK exposes readable order state.
6
+ > off-ramp provider. The user is the maker, a buyer pays them fiat and proves
7
+ > the payment, and the SDK exposes readable order state.
8
8
  > Base-USDC flows are serializable through prepare paths; source-routed cashout
9
9
  > uses Relay execution first. React apps, Node services, and agent hosts use the
10
10
  > same typed surface.
@@ -59,9 +59,8 @@ Key facts:
59
59
 
60
60
  ## Links
61
61
 
62
- - Live demo: https://react-cashout-demo.vercel.app
63
- - Product page: https://peer.xyz/cash
64
62
  - npm: https://www.npmjs.com/package/@zkp2p/cash
63
+ - Source: https://github.com/zkp2p/peer-cash
65
64
 
66
65
  ## Docs
67
66
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkp2p/cash",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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)",
@@ -12,7 +12,14 @@
12
12
  "publishConfig": {
13
13
  "access": "public"
14
14
  },
15
- "homepage": "https://peer.xyz/cash",
15
+ "homepage": "https://github.com/zkp2p/peer-cash#readme",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/zkp2p/peer-cash.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/zkp2p/peer-cash/issues"
22
+ },
16
23
  "keywords": [
17
24
  "zkp2p",
18
25
  "peer",
@@ -98,7 +105,7 @@
98
105
  "dependencies": {
99
106
  "@relayprotocol/relay-sdk": "^6.1.3",
100
107
  "@zkp2p/sdk": "^0.8.1",
101
- "zod": "^3.24.1"
108
+ "zod": "^3.25.76"
102
109
  },
103
110
  "peerDependencies": {
104
111
  "react": ">=18",
@@ -110,19 +117,19 @@
110
117
  }
111
118
  },
112
119
  "devDependencies": {
113
- "@eslint/js": "^9.17.0",
114
- "@types/node": "^22.10.2",
115
- "@types/react": "^19.0.2",
120
+ "@eslint/js": "^9.39.5",
121
+ "@types/node": "^22.20.1",
122
+ "@types/react": "^19.2.17",
116
123
  "@types/react-test-renderer": "19.1.0",
117
- "eslint": "^9.17.0",
118
- "eslint-config-prettier": "^9.1.0",
119
- "prettier": "^3.4.2",
120
- "react": "^19.0.0",
124
+ "eslint": "^9.39.5",
125
+ "eslint-config-prettier": "^9.1.2",
126
+ "prettier": "^3.9.5",
127
+ "react": "^19.2.7",
121
128
  "react-test-renderer": "19.2.7",
122
- "tsup": "^8.3.5",
123
- "typescript": "^5.7.2",
124
- "typescript-eslint": "^8.18.1",
125
- "viem": "^2.55.0",
129
+ "tsup": "^8.5.1",
130
+ "typescript": "^5.9.3",
131
+ "typescript-eslint": "^8.63.0",
132
+ "viem": "^2.55.1",
126
133
  "vitest": "^4.1.10"
127
134
  }
128
135
  }