@zkp2p/cash 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -54,6 +54,9 @@ const relayCaps = await cash.capabilities({ includeRelaySources: true });
54
54
  // 2. Estimate - idempotent, cacheable, no side effects. Includes rolling ETA.
55
55
  const est = await cash.estimate({ amount: usdc(500), currency: 'EUR' });
56
56
 
57
+ // Progressive UI: do not let indexer-backed history hold up the oracle rate.
58
+ const rateOnly = await cash.estimate({ amount: usdc(500), currency: 'EUR' }, { includeEta: false });
59
+
57
60
  // Optional: raw demand + speed evidence per offered platform:currency pair.
58
61
  const stats = await cash.fillStats();
59
62
 
@@ -106,7 +109,9 @@ console.log(routed.source?.transactions?.origin, routed.source?.transactions?.de
106
109
  - **Do not invent an ETA.** Use `estimate().eta`: `{ seconds, label }` backed
107
110
  by the same rolling 30-day, intent-attributed pair sample as `fillStats()`,
108
111
  measured from deposit creation to first fill. Use `order.explain()` for live
109
- order state.
112
+ order state. For progressive UIs, call `estimate(..., { includeEta: false })`
113
+ and load `fillStats()["platform:CURRENCY"]` separately. The SDK caches the
114
+ raw snapshot for 15 minutes, but never substitutes another pair's data.
110
115
  - **Do not hardcode Relay source assets.** Use Relay SDK-backed EVM
111
116
  `capabilities({ includeRelaySources: true })` and `cashout({ source, ... })`.
112
117
  Destination is always Base USDC. Non-Base source chains require
package/README.md CHANGED
@@ -34,6 +34,13 @@ const est = await cash.estimate({ amount: usdc(1000), currency: 'USD' });
34
34
  // { rate: 1, receiveAmount: 1000, kind: 'oracle-estimate', eta: { seconds, label } }
35
35
  // "≈", never a locked quote. Base USDC remains the default source.
36
36
 
37
+ // Progressive UI: render rate/receive first, then resolve the exact pair ETA.
38
+ const rateOnly = await cash.estimate(
39
+ { amount: usdc(1000), currency: 'USD' },
40
+ { includeEta: false },
41
+ );
42
+ const pairStats = (await cash.fillStats())['venmo:USD'];
43
+
37
44
  const { depositId } = await cash.cashout(
38
45
  {
39
46
  amount: usdc(1000),
@@ -76,10 +83,10 @@ console.log(source?.transactions?.origin, source?.transactions?.destination);
76
83
  | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
77
84
  | `capabilities()` | Sync discovery: Base USDC destination/default source, platforms × currencies × payee hints × amount bounds |
78
85
  | `capabilities({ includeRelaySources: true })` | Async discovery: adds live Relay SDK EVM source chains/tokens |
79
- | `fillStats()` | Raw 30-day fill counts and median first-fill time per `platform:currency` pair |
86
+ | `fillStats()` | Cached 30-day fill counts and median first-fill time per exact `platform:currency` pair |
80
87
  | `quoteSource(input)` / `executeSourceQuote(quote, { signer })` | Relay SDK EVM source routing into Base USDC before cashout |
81
88
  | `relayStatus(requestId)` | Relay request status from the Relay SDK request path |
82
- | `estimate({ amount, currency })` | Base USDC oracle estimate plus simple recent-fill ETA |
89
+ | `estimate({ amount, currency }, { includeEta? })` | Base USDC oracle estimate; optionally skip the historical ETA for progressive rendering |
83
90
  | `cashout(input, { signer })` | Registers your payee, creates the protocol-held order, returns the `depositId` |
84
91
  | `order(depositId)` / `orders(owner)` | Resume any order from its id alone; list all orders for a wallet |
85
92
  | `watch(depositId)` | Async iterator: yields on every state change until terminal, abort, or timeout |
@@ -99,9 +106,14 @@ cashout. Every Peer Cash transaction, including approves, carries ERC-8021
99
106
  attribution: `peer-cash` first, your own `referrer` code(s) after it.
100
107
 
101
108
  `capabilities()` presents Zelle as one platform. A cashout with
102
- `receive.platform: 'zelle'` automatically attaches the generic Zelle method
103
- and its Chase, Bank of America, and Citi buyer routes to the deposit; the payee
104
- handle and public API remain bank-agnostic.
109
+ `receive.platform: 'zelle'` attaches only the generic Zelle payment method to
110
+ the deposit. Bank-specific capture routing is outside this maker-side SDK and
111
+ never changes the on-chain payment method.
112
+
113
+ Order reads fail closed against the same active catalog. If any method on an
114
+ indexed deposit is unsupported, `orders()` excludes the whole deposit and
115
+ `order()` returns `ORDER_NOT_FOUND`; Peer Cash never partially reclassifies a
116
+ mixed historical deposit.
105
117
 
106
118
  The default/minimal flow is unchanged: pass Base USDC base units to
107
119
  `estimate()` and `cashout()`. For any other source asset, pass `source` to
@@ -180,6 +192,9 @@ awaiting-buyer ──────────► matched ───────
180
192
  - **ETA is historical.** `estimate().eta` is just `{ seconds, label }`, backed
181
193
  by the same rolling 30-day, intent-attributed pair sampler as `fillStats()`,
182
194
  measured from deposit creation to the first fulfilled fill through the pair.
195
+ The raw snapshot is cached for 15 minutes per client and each ETA is still
196
+ resolved from its exact normalized `platform:currency` key. Use
197
+ `{ includeEta: false }` when rate and receive amount should render first.
183
198
  - **Availability thresholds belong to the consumer.** `fillStats()` returns raw
184
199
  evidence. A recommended gate is `fills >= 10 && medianFillSeconds <= 48h`.
185
200
  Fail open to the full `capabilities()` catalog when stats are unavailable or
@@ -3,7 +3,7 @@ import { IndexerIntentStatus, Zkp2pClient, IndexerIntent, CurrencyType, Prepared
3
3
  import { Execute, RelayClient, RelayChain, ProgressData } from '@relayprotocol/relay-sdk';
4
4
 
5
5
  /**
6
- * Name-mapping shim over the published `@zkp2p/sdk` (^0.8).
6
+ * Name-mapping shim over the published `@zkp2p/sdk` (^0.9).
7
7
  *
8
8
  * The reference implementation imported these from internal SDK paths; the
9
9
  * published package exports them under indexer-prefixed names, and one type
@@ -97,8 +97,8 @@ interface CashPayoutPricing {
97
97
  }
98
98
  /** One payout leg reconstructed from the chain - platform, currency, payee hash, pricing. */
99
99
  interface CashPayoutInfo {
100
- /** Decoded platform id, e.g. `'venmo'` (undefined if the catalog doesn't know the hash). */
101
- platform?: string;
100
+ /** Decoded active platform id, e.g. `'venmo'`. */
101
+ platform: string;
102
102
  /** Raw payment method hash (bytes32). */
103
103
  platformHash: string;
104
104
  /** Decoded fiat currency code, e.g. `'USD'`. */
@@ -401,6 +401,13 @@ interface EstimateInput {
401
401
  tradeType?: 'EXACT_INPUT' | 'EXACT_OUTPUT' | 'EXPECTED_OUTPUT';
402
402
  };
403
403
  }
404
+ interface EstimateOptions {
405
+ /**
406
+ * Include the historical indexer-backed ETA. Disable for progressive UIs
407
+ * that render the oracle rate first and load pair fill stats separately.
408
+ */
409
+ includeEta?: boolean;
410
+ }
404
411
  interface CashEstimate {
405
412
  /** Always `'oracle-estimate'` - there is no committed quote in Peer Cash. */
406
413
  kind: 'oracle-estimate';
@@ -612,7 +619,7 @@ interface CashClient {
612
619
  /** Track Relay execution status by quote/request id. */
613
620
  relayStatus(requestId: string): Promise<RelayStatus>;
614
621
  /** 1 - Estimate: currency + amount only. No payee, no side effects, no expiry. */
615
- estimate(input: EstimateInput): Promise<CashEstimate>;
622
+ estimate(input: EstimateInput, options?: EstimateOptions): Promise<CashEstimate>;
616
623
  /** 2 - Cash out: payee registration + deposit params + submission happen here. */
617
624
  cashout(input: CashoutInput, opts: CashoutOptions): Promise<CashoutResult>;
618
625
  /** 2b - Unsigned path: `txs[]` for agent wallets, AA, server keys, policy layers. */
@@ -655,4 +662,4 @@ interface CashClient {
655
662
  }
656
663
  declare function createCashClient(options: CashClientOptions): CashClient;
657
664
 
658
- export { type CashPlatformCapability as A, type CashPreparedStepKind as B, type CashPayoutInfo as C, type CashoutInput as D, type CashoutOptions as E, type CuratorPayeeDataInput as F, type EstimateInput as G, RECOMMENDED_MIN_CASHOUT_AMOUNT as H, type IntentStatus as I, type RelayOptions as J, type RelayQuoteInput as K, type RelaySourceInput as L, MIN_CASHOUT_AMOUNT as M, type RelayTransaction as N, type OrdersOptions as O, type PrepareResult as P, type WatchOptions as Q, type RelayExecutionResult as R, type SignerOptions as S, type TopUpResult as T, type WithdrawOptions as U, buildCapabilities as V, type WithdrawResult as W, createCashClient as X, type IntentEntity as a, type CashBuyerProfile as b, type CashDepositInput as c, type CreateDepositParamsArg as d, type CashOrder as e, type CashFill as f, type CashCapabilities as g, type CashoutResult as h, type CashEstimate as i, type CashFillStats as j, type CashPreparedStep as k, type RelayQuote as l, type RelayStatus as m, type CashSourceCapabilities as n, CASH_ATTRIBUTION_CODE as o, type CashAsset as p, type CashChain as q, type CashClient as r, type CashClientOptions as s, type CashFillEta as t, type CashLeg as u, type CashNextAction as v, type CashOrderState as w, type CashPairFillStats as x, type CashPayout as y, type CashPayoutPricing as z };
665
+ export { type CashPlatformCapability as A, type CashPreparedStepKind as B, type CashPayoutInfo as C, type CashoutInput as D, type CashoutOptions as E, type CuratorPayeeDataInput as F, type EstimateInput as G, type EstimateOptions as H, type IntentStatus as I, RECOMMENDED_MIN_CASHOUT_AMOUNT as J, type RelayOptions as K, type RelayQuoteInput as L, MIN_CASHOUT_AMOUNT as M, type RelaySourceInput as N, type OrdersOptions as O, type PrepareResult as P, type RelayTransaction as Q, type RelayExecutionResult as R, type SignerOptions as S, type TopUpResult as T, type WatchOptions as U, type WithdrawOptions as V, type WithdrawResult as W, buildCapabilities as X, createCashClient as Y, type IntentEntity as a, type CashBuyerProfile as b, type CashDepositInput as c, type CreateDepositParamsArg as d, type CashOrder as e, type CashFill as f, type CashCapabilities as g, type CashoutResult as h, type CashEstimate as i, type CashFillStats as j, type CashPreparedStep as k, type RelayQuote as l, type RelayStatus as m, type CashSourceCapabilities as n, CASH_ATTRIBUTION_CODE as o, type CashAsset as p, type CashChain as q, type CashClient as r, type CashClientOptions as s, type CashFillEta as t, type CashLeg as u, type CashNextAction as v, type CashOrderState as w, type CashPairFillStats as x, type CashPayout as y, type CashPayoutPricing as z };
@@ -3,7 +3,7 @@ import { IndexerIntentStatus, Zkp2pClient, IndexerIntent, CurrencyType, Prepared
3
3
  import { Execute, RelayClient, RelayChain, ProgressData } from '@relayprotocol/relay-sdk';
4
4
 
5
5
  /**
6
- * Name-mapping shim over the published `@zkp2p/sdk` (^0.8).
6
+ * Name-mapping shim over the published `@zkp2p/sdk` (^0.9).
7
7
  *
8
8
  * The reference implementation imported these from internal SDK paths; the
9
9
  * published package exports them under indexer-prefixed names, and one type
@@ -97,8 +97,8 @@ interface CashPayoutPricing {
97
97
  }
98
98
  /** One payout leg reconstructed from the chain - platform, currency, payee hash, pricing. */
99
99
  interface CashPayoutInfo {
100
- /** Decoded platform id, e.g. `'venmo'` (undefined if the catalog doesn't know the hash). */
101
- platform?: string;
100
+ /** Decoded active platform id, e.g. `'venmo'`. */
101
+ platform: string;
102
102
  /** Raw payment method hash (bytes32). */
103
103
  platformHash: string;
104
104
  /** Decoded fiat currency code, e.g. `'USD'`. */
@@ -401,6 +401,13 @@ interface EstimateInput {
401
401
  tradeType?: 'EXACT_INPUT' | 'EXACT_OUTPUT' | 'EXPECTED_OUTPUT';
402
402
  };
403
403
  }
404
+ interface EstimateOptions {
405
+ /**
406
+ * Include the historical indexer-backed ETA. Disable for progressive UIs
407
+ * that render the oracle rate first and load pair fill stats separately.
408
+ */
409
+ includeEta?: boolean;
410
+ }
404
411
  interface CashEstimate {
405
412
  /** Always `'oracle-estimate'` - there is no committed quote in Peer Cash. */
406
413
  kind: 'oracle-estimate';
@@ -612,7 +619,7 @@ interface CashClient {
612
619
  /** Track Relay execution status by quote/request id. */
613
620
  relayStatus(requestId: string): Promise<RelayStatus>;
614
621
  /** 1 - Estimate: currency + amount only. No payee, no side effects, no expiry. */
615
- estimate(input: EstimateInput): Promise<CashEstimate>;
622
+ estimate(input: EstimateInput, options?: EstimateOptions): Promise<CashEstimate>;
616
623
  /** 2 - Cash out: payee registration + deposit params + submission happen here. */
617
624
  cashout(input: CashoutInput, opts: CashoutOptions): Promise<CashoutResult>;
618
625
  /** 2b - Unsigned path: `txs[]` for agent wallets, AA, server keys, policy layers. */
@@ -655,4 +662,4 @@ interface CashClient {
655
662
  }
656
663
  declare function createCashClient(options: CashClientOptions): CashClient;
657
664
 
658
- export { type CashPlatformCapability as A, type CashPreparedStepKind as B, type CashPayoutInfo as C, type CashoutInput as D, type CashoutOptions as E, type CuratorPayeeDataInput as F, type EstimateInput as G, RECOMMENDED_MIN_CASHOUT_AMOUNT as H, type IntentStatus as I, type RelayOptions as J, type RelayQuoteInput as K, type RelaySourceInput as L, MIN_CASHOUT_AMOUNT as M, type RelayTransaction as N, type OrdersOptions as O, type PrepareResult as P, type WatchOptions as Q, type RelayExecutionResult as R, type SignerOptions as S, type TopUpResult as T, type WithdrawOptions as U, buildCapabilities as V, type WithdrawResult as W, createCashClient as X, type IntentEntity as a, type CashBuyerProfile as b, type CashDepositInput as c, type CreateDepositParamsArg as d, type CashOrder as e, type CashFill as f, type CashCapabilities as g, type CashoutResult as h, type CashEstimate as i, type CashFillStats as j, type CashPreparedStep as k, type RelayQuote as l, type RelayStatus as m, type CashSourceCapabilities as n, CASH_ATTRIBUTION_CODE as o, type CashAsset as p, type CashChain as q, type CashClient as r, type CashClientOptions as s, type CashFillEta as t, type CashLeg as u, type CashNextAction as v, type CashOrderState as w, type CashPairFillStats as x, type CashPayout as y, type CashPayoutPricing as z };
665
+ export { type CashPlatformCapability as A, type CashPreparedStepKind as B, type CashPayoutInfo as C, type CashoutInput as D, type CashoutOptions as E, type CuratorPayeeDataInput as F, type EstimateInput as G, type EstimateOptions as H, type IntentStatus as I, RECOMMENDED_MIN_CASHOUT_AMOUNT as J, type RelayOptions as K, type RelayQuoteInput as L, MIN_CASHOUT_AMOUNT as M, type RelaySourceInput as N, type OrdersOptions as O, type PrepareResult as P, type RelayTransaction as Q, type RelayExecutionResult as R, type SignerOptions as S, type TopUpResult as T, type WatchOptions as U, type WithdrawOptions as V, type WithdrawResult as W, buildCapabilities as X, createCashClient as Y, type IntentEntity as a, type CashBuyerProfile as b, type CashDepositInput as c, type CreateDepositParamsArg as d, type CashOrder as e, type CashFill as f, type CashCapabilities as g, type CashoutResult as h, type CashEstimate as i, type CashFillStats as j, type CashPreparedStep as k, type RelayQuote as l, type RelayStatus as m, type CashSourceCapabilities as n, CASH_ATTRIBUTION_CODE as o, type CashAsset as p, type CashChain as q, type CashClient as r, type CashClientOptions as s, type CashFillEta as t, type CashLeg as u, type CashNextAction as v, type CashOrderState as w, type CashPairFillStats as x, type CashPayout as y, type CashPayoutPricing as z };
package/dist/index.cjs CHANGED
@@ -303,35 +303,41 @@ function toPricing(tuple) {
303
303
  };
304
304
  }
305
305
  function derivePayouts(paymentMethods, currencies, catalog) {
306
- return paymentMethods.flatMap((method) => {
306
+ const payouts = [];
307
+ for (const method of paymentMethods) {
307
308
  const platformHash = method.paymentMethodHash ?? "";
308
309
  if (!platformHash) return [];
309
310
  let platform;
310
311
  try {
311
312
  platform = sdk.resolvePaymentMethodNameFromHash(platformHash, catalog);
312
313
  } catch {
313
- platform = void 0;
314
+ return [];
314
315
  }
316
+ if (!platform) return [];
315
317
  const tuples = currencies.filter(
316
318
  (c) => (c.paymentMethodHash ?? "").toLowerCase() === platformHash.toLowerCase()
317
319
  );
318
320
  const base2 = {
319
- ...platform !== void 0 ? { platform } : {},
321
+ platform,
320
322
  platformHash,
321
323
  payeeHash: method.payeeDetailsHash ?? "",
322
324
  active: method.active ?? true
323
325
  };
324
- if (tuples.length === 0) return [{ ...base2, pricing: toPricing(void 0) }];
325
- return tuples.map((tuple) => {
326
+ if (tuples.length === 0) {
327
+ payouts.push({ ...base2, pricing: toPricing(void 0) });
328
+ continue;
329
+ }
330
+ for (const tuple of tuples) {
326
331
  const currency = tuple.currencyCode != null ? sdk.getCurrencyCodeFromHash(tuple.currencyCode) : void 0;
327
- return {
332
+ payouts.push({
328
333
  ...base2,
329
334
  ...currency !== void 0 ? { currency } : {},
330
335
  ...tuple.currencyCode != null ? { currencyHash: tuple.currencyCode } : {},
331
336
  pricing: toPricing(tuple)
332
- };
333
- });
334
- });
337
+ });
338
+ }
339
+ }
340
+ return payouts;
335
341
  }
336
342
 
337
343
  // src/engine/buyerProfile.ts
@@ -403,26 +409,6 @@ function parseCompositeDepositId(compositeId) {
403
409
  const onchainDepositId = BigInt(rawDepositId);
404
410
  return { escrowAddress: canonicalEscrowAddress, onchainDepositId };
405
411
  }
406
-
407
- // src/client/platformGroups.ts
408
- var PLATFORM_METHOD_GROUPS = {
409
- zelle: ["zelle", "zelle-chase", "zelle-bofa", "zelle-citi"]
410
- };
411
- var METHOD_TO_BASE_PLATFORM = new Map(
412
- Object.entries(PLATFORM_METHOD_GROUPS).flatMap(
413
- ([platform, methods]) => methods.map((method) => [method, platform])
414
- )
415
- );
416
- function basePlatformForMethod(method) {
417
- return METHOD_TO_BASE_PLATFORM.get(method) ?? method;
418
- }
419
- function paymentMethodsForPlatform(platform, catalog) {
420
- const configured = PLATFORM_METHOD_GROUPS[platform];
421
- const methods = configured ?? [platform];
422
- return methods.filter((method) => catalog[method] !== void 0);
423
- }
424
-
425
- // src/client/capabilities.ts
426
412
  var MIN_CASHOUT_AMOUNT = 10000n;
427
413
  var RECOMMENDED_MIN_CASHOUT_AMOUNT = 1000000n;
428
414
  var PAYEE_HINTS = {
@@ -441,20 +427,13 @@ var PAYEE_HINTS = {
441
427
  var IDENTITY_ATTESTATION_PLATFORMS = /* @__PURE__ */ new Set(["wise", "paypal"]);
442
428
  function buildCapabilities(environment) {
443
429
  const catalog = sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
444
- const currenciesByPlatform = /* @__PURE__ */ new Map();
445
- for (const [method, entry] of Object.entries(catalog)) {
446
- const platform = basePlatformForMethod(method);
430
+ const platforms = Object.entries(catalog).map(([platform, entry]) => {
447
431
  const currencies2 = (entry.currencies ?? []).map((hash) => sdk.getCurrencyCodeFromHash(hash)).filter(
448
432
  (code) => code != null && isMarketRateSupported(code)
449
433
  );
450
- const aggregate = currenciesByPlatform.get(platform) ?? /* @__PURE__ */ new Set();
451
- for (const currency of currencies2) aggregate.add(currency);
452
- currenciesByPlatform.set(platform, aggregate);
453
- }
454
- const platforms = [...currenciesByPlatform.entries()].map(([platform, currencies2]) => {
455
434
  return {
456
435
  platform,
457
- currencies: [...currencies2].sort(),
436
+ currencies: [...new Set(currencies2)].sort(),
458
437
  payeeHint: PAYEE_HINTS[platform] ?? "Your payment handle for this platform",
459
438
  requiresIdentityAttestation: IDENTITY_ATTESTATION_PLATFORMS.has(platform)
460
439
  };
@@ -896,7 +875,7 @@ function computeFillStatsSample(deposits, nowSeconds, environment) {
896
875
  }
897
876
  const currency = normalizeCurrencyCode(intent.fiatCurrency);
898
877
  if (!method || !currency) continue;
899
- const pair = `${basePlatformForMethod(method)}:${currency}`;
878
+ const pair = `${method}:${currency}`;
900
879
  fillCounts.set(pair, (fillCounts.get(pair) ?? 0) + 1);
901
880
  if (createdAt === void 0 || createdAt < windowStart || fulfilledAt < createdAt) continue;
902
881
  const previousPairFill = firstFillByPair.get(pair);
@@ -959,18 +938,18 @@ async function readFillStatsSample(client, environment) {
959
938
  }
960
939
  return computeFillStatsSample(deposits, now, environment);
961
940
  }
962
- async function readFillStats(client, environment) {
963
- return (await readFillStatsSample(client, environment)).stats;
964
- }
965
- async function readFillEta(client, input) {
966
- const sample = await readFillStatsSample(client, input.environment);
941
+ function fillEtaFromSample(sample, input) {
967
942
  const currency = input.currency.toUpperCase();
968
- const seconds = input.platform ? sample.stats[`${basePlatformForMethod(input.platform)}:${currency}`]?.medianFillSeconds : sample.medianFillSecondsByCurrency.get(currency);
943
+ const seconds = input.platform ? sample.stats[`${input.platform}:${currency}`]?.medianFillSeconds : sample.medianFillSecondsByCurrency.get(currency);
969
944
  return {
970
945
  ...seconds !== void 0 ? { seconds } : {},
971
946
  label: etaLabel(seconds)
972
947
  };
973
948
  }
949
+ async function readFillEta(client, input) {
950
+ const sample = await readFillStatsSample(client, input.environment);
951
+ return fillEtaFromSample(sample, input);
952
+ }
974
953
  var RELAY_API_URL = relaySdk.MAINNET_RELAY_API;
975
954
  var NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
976
955
  var BASE_USDC_ASSET = {
@@ -1506,13 +1485,18 @@ async function readEstimate(publicClient, input, context = {}) {
1506
1485
  }
1507
1486
  } : {}
1508
1487
  };
1509
- if (context.indexerClient && context.environment) {
1488
+ if (context.includeEta !== false && context.environment) {
1510
1489
  try {
1511
- estimate.eta = await readFillEta(context.indexerClient, {
1490
+ const etaInput = {
1512
1491
  environment: context.environment,
1513
1492
  currency,
1514
1493
  ...input.platform ? { platform: input.platform } : {}
1515
- });
1494
+ };
1495
+ if (context.etaReader) {
1496
+ estimate.eta = await context.etaReader(etaInput);
1497
+ } else if (context.indexerClient) {
1498
+ estimate.eta = await readFillEta(context.indexerClient, etaInput);
1499
+ }
1516
1500
  } catch {
1517
1501
  }
1518
1502
  }
@@ -1521,6 +1505,7 @@ async function readEstimate(publicClient, input, context = {}) {
1521
1505
 
1522
1506
  // src/client/createCashClient.ts
1523
1507
  var DEFAULT_RPC_URL = "https://mainnet.base.org";
1508
+ var FILL_STATS_CACHE_MS = 15 * 60 * 1e3;
1524
1509
  var CASH_ATTRIBUTION_CODE = "peer-cash";
1525
1510
  var DEFAULT_CURATOR_URLS = {
1526
1511
  preproduction: "https://api-preprod.zkp2p.xyz",
@@ -1619,6 +1604,25 @@ function createCashClient(options) {
1619
1604
  });
1620
1605
  }
1621
1606
  const readClient = buildSdkClient(viem.createWalletClient({ chain: chains.base, transport }));
1607
+ let fillStatsCache = null;
1608
+ let fillStatsRequest = null;
1609
+ async function getFillStatsSample() {
1610
+ if (fillStatsCache && fillStatsCache.expiresAt > Date.now()) {
1611
+ return fillStatsCache.sample;
1612
+ }
1613
+ if (fillStatsRequest) return fillStatsRequest;
1614
+ fillStatsRequest = readFillStatsSample(readClient, environment);
1615
+ try {
1616
+ const sample = await fillStatsRequest;
1617
+ fillStatsCache = {
1618
+ sample,
1619
+ expiresAt: Date.now() + FILL_STATS_CACHE_MS
1620
+ };
1621
+ return sample;
1622
+ } finally {
1623
+ fillStatsRequest = null;
1624
+ }
1625
+ }
1622
1626
  const signingClients = /* @__PURE__ */ new WeakMap();
1623
1627
  async function signingClient(verb, opts) {
1624
1628
  const signer = opts?.signer;
@@ -1633,7 +1637,6 @@ function createCashClient(options) {
1633
1637
  }
1634
1638
  function validatePayout(input) {
1635
1639
  const { receive } = input;
1636
- const catalog = sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
1637
1640
  const platform = buildCapabilities(environment).platforms.find(
1638
1641
  (capability) => capability.platform === receive.platform
1639
1642
  );
@@ -1644,13 +1647,14 @@ function createCashClient(options) {
1644
1647
  if (!platform.currencies.includes(receive.currency)) {
1645
1648
  throw errors.unsupportedPlatformCurrency(receive.platform, receive.currency);
1646
1649
  }
1647
- const paymentMethods = paymentMethodsForPlatform(receive.platform, catalog);
1648
1650
  return {
1649
- payouts: paymentMethods.map((processorName) => ({
1650
- processorName,
1651
- currency: receive.currency,
1652
- payeeData: receive.payee
1653
- }))
1651
+ payouts: [
1652
+ {
1653
+ processorName: receive.platform,
1654
+ currency: receive.currency,
1655
+ payeeData: receive.payee
1656
+ }
1657
+ ]
1654
1658
  };
1655
1659
  }
1656
1660
  function validateDepositInput(amount, input, payoutInput = validatePayout(input)) {
@@ -1667,6 +1671,9 @@ function createCashClient(options) {
1667
1671
  ...range ? { intentAmountRange: range } : {}
1668
1672
  };
1669
1673
  }
1674
+ function isCashPayoutSet(payouts) {
1675
+ return payouts.length === 1 && payouts.every((payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0);
1676
+ }
1670
1677
  async function buildDepositParams(client, depositInput) {
1671
1678
  try {
1672
1679
  return await prepareCashDepositParams(client, depositInput);
@@ -1721,9 +1728,10 @@ function createCashClient(options) {
1721
1728
  deposit.currencies ?? [],
1722
1729
  sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment)
1723
1730
  );
1731
+ if (!isCashPayoutSet(payouts)) throw errors.orderNotFound(compositeId);
1724
1732
  return deriveCashOrder(compositeId, deposit.intents ?? [], {
1725
1733
  ...depositOrderOptions(deposit),
1726
- ...payouts.length > 0 ? { payouts } : {}
1734
+ payouts
1727
1735
  });
1728
1736
  }
1729
1737
  function escrowContext(depositId) {
@@ -1921,16 +1929,17 @@ function createCashClient(options) {
1921
1929
  async relayStatus(requestId) {
1922
1930
  return readRelayStatus(requestId, options.relay);
1923
1931
  },
1924
- async estimate(input) {
1932
+ async estimate(input, estimateOptions) {
1925
1933
  return readEstimate(readClient.publicClient, input, {
1926
- indexerClient: readClient,
1927
1934
  environment,
1935
+ ...estimateOptions?.includeEta !== void 0 ? { includeEta: estimateOptions.includeEta } : {},
1936
+ etaReader: async (etaInput) => fillEtaFromSample(await getFillStatsSample(), etaInput),
1928
1937
  ...options.relay ? { relay: options.relay } : {}
1929
1938
  });
1930
1939
  },
1931
1940
  async fillStats() {
1932
1941
  try {
1933
- return await readFillStats(readClient, environment);
1942
+ return (await getFillStatsSample()).stats;
1934
1943
  } catch (err) {
1935
1944
  throw errors.indexerUnavailable("fill stats", err);
1936
1945
  }
@@ -2169,7 +2178,7 @@ function createCashClient(options) {
2169
2178
  deposit.currencies ?? [],
2170
2179
  catalog
2171
2180
  );
2172
- if (payouts.length !== 1 || !payouts.every((payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0)) {
2181
+ if (!isCashPayoutSet(payouts)) {
2173
2182
  return [];
2174
2183
  }
2175
2184
  return [
@@ -2438,7 +2447,7 @@ var cashPayoutPricingJsonSchema = zod.z.object({
2438
2447
  marketRate: zod.z.boolean()
2439
2448
  });
2440
2449
  var cashPayoutInfoJsonSchema = zod.z.object({
2441
- platform: zod.z.string().optional(),
2450
+ platform: zod.z.string(),
2442
2451
  platformHash: zod.z.string(),
2443
2452
  currency: zod.z.string().optional(),
2444
2453
  currencyHash: zod.z.string().optional(),