@zkp2p/cash 0.1.6 → 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
@@ -107,6 +107,13 @@ The destination is always canonical Base USDC
107
107
  discovered and quoted by `@relayprotocol/relay-sdk`, not a static token
108
108
  allowlist.
109
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
+
110
117
  `capabilities()` tells you which platforms need a verified identity for a new
111
118
  payee registration (`requiresIdentityAttestation` - Wise and PayPal today).
112
119
  An already-registered Wise or PayPal handle can be reused with bare payee data.
@@ -120,6 +127,10 @@ they are available. A source-routed result includes both a flat
120
127
  `source.txHashes` list and chain-aware `source.transactions.origin` /
121
128
  `.destination` entries.
122
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.
123
134
  - `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED`: Relay completed, but the Base
124
135
  cashout was not created. Do not route again. Retry a Base-USDC-only
125
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)) {
@@ -2559,6 +2574,7 @@ var CASH_ERROR_CODES = defineCashErrorCodes([
2559
2574
  "SOURCE_RECIPIENT_MISMATCH",
2560
2575
  "SOURCE_CAPABILITIES_FAILED",
2561
2576
  "SOURCE_QUOTE_FAILED",
2577
+ "SOURCE_NONCE_MANAGER_REQUIRED",
2562
2578
  "SOURCE_EXECUTION_FAILED",
2563
2579
  "SOURCE_STATUS_FAILED",
2564
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)) {
@@ -2186,6 +2195,7 @@ var CASH_ERROR_CODES = defineCashErrorCodes([
2186
2195
  "SOURCE_RECIPIENT_MISMATCH",
2187
2196
  "SOURCE_CAPABILITIES_FAILED",
2188
2197
  "SOURCE_QUOTE_FAILED",
2198
+ "SOURCE_NONCE_MANAGER_REQUIRED",
2189
2199
  "SOURCE_EXECUTION_FAILED",
2190
2200
  "SOURCE_STATUS_FAILED",
2191
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.6"};
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.6"};
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkp2p/cash",
3
- "version": "0.1.6",
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)",