@zkp2p/cash 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 |
@@ -180,6 +187,9 @@ awaiting-buyer ──────────► matched ───────
180
187
  - **ETA is historical.** `estimate().eta` is just `{ seconds, label }`, backed
181
188
  by the same rolling 30-day, intent-attributed pair sampler as `fillStats()`,
182
189
  measured from deposit creation to the first fulfilled fill through the pair.
190
+ The raw snapshot is cached for 15 minutes per client and each ETA is still
191
+ resolved from its exact normalized `platform:currency` key. Use
192
+ `{ includeEta: false }` when rate and receive amount should render first.
183
193
  - **Availability thresholds belong to the consumer.** `fillStats()` returns raw
184
194
  evidence. A recommended gate is `fills >= 10 && medianFillSeconds <= 48h`.
185
195
  Fail open to the full `capabilities()` catalog when stats are unavailable or
@@ -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 };
@@ -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
@@ -959,11 +959,7 @@ async function readFillStatsSample(client, environment) {
959
959
  }
960
960
  return computeFillStatsSample(deposits, now, environment);
961
961
  }
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);
962
+ function fillEtaFromSample(sample, input) {
967
963
  const currency = input.currency.toUpperCase();
968
964
  const seconds = input.platform ? sample.stats[`${basePlatformForMethod(input.platform)}:${currency}`]?.medianFillSeconds : sample.medianFillSecondsByCurrency.get(currency);
969
965
  return {
@@ -971,6 +967,10 @@ async function readFillEta(client, input) {
971
967
  label: etaLabel(seconds)
972
968
  };
973
969
  }
970
+ async function readFillEta(client, input) {
971
+ const sample = await readFillStatsSample(client, input.environment);
972
+ return fillEtaFromSample(sample, input);
973
+ }
974
974
  var RELAY_API_URL = relaySdk.MAINNET_RELAY_API;
975
975
  var NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
976
976
  var BASE_USDC_ASSET = {
@@ -1506,13 +1506,18 @@ async function readEstimate(publicClient, input, context = {}) {
1506
1506
  }
1507
1507
  } : {}
1508
1508
  };
1509
- if (context.indexerClient && context.environment) {
1509
+ if (context.includeEta !== false && context.environment) {
1510
1510
  try {
1511
- estimate.eta = await readFillEta(context.indexerClient, {
1511
+ const etaInput = {
1512
1512
  environment: context.environment,
1513
1513
  currency,
1514
1514
  ...input.platform ? { platform: input.platform } : {}
1515
- });
1515
+ };
1516
+ if (context.etaReader) {
1517
+ estimate.eta = await context.etaReader(etaInput);
1518
+ } else if (context.indexerClient) {
1519
+ estimate.eta = await readFillEta(context.indexerClient, etaInput);
1520
+ }
1516
1521
  } catch {
1517
1522
  }
1518
1523
  }
@@ -1521,6 +1526,7 @@ async function readEstimate(publicClient, input, context = {}) {
1521
1526
 
1522
1527
  // src/client/createCashClient.ts
1523
1528
  var DEFAULT_RPC_URL = "https://mainnet.base.org";
1529
+ var FILL_STATS_CACHE_MS = 15 * 60 * 1e3;
1524
1530
  var CASH_ATTRIBUTION_CODE = "peer-cash";
1525
1531
  var DEFAULT_CURATOR_URLS = {
1526
1532
  preproduction: "https://api-preprod.zkp2p.xyz",
@@ -1619,6 +1625,25 @@ function createCashClient(options) {
1619
1625
  });
1620
1626
  }
1621
1627
  const readClient = buildSdkClient(viem.createWalletClient({ chain: chains.base, transport }));
1628
+ let fillStatsCache = null;
1629
+ let fillStatsRequest = null;
1630
+ async function getFillStatsSample() {
1631
+ if (fillStatsCache && fillStatsCache.expiresAt > Date.now()) {
1632
+ return fillStatsCache.sample;
1633
+ }
1634
+ if (fillStatsRequest) return fillStatsRequest;
1635
+ fillStatsRequest = readFillStatsSample(readClient, environment);
1636
+ try {
1637
+ const sample = await fillStatsRequest;
1638
+ fillStatsCache = {
1639
+ sample,
1640
+ expiresAt: Date.now() + FILL_STATS_CACHE_MS
1641
+ };
1642
+ return sample;
1643
+ } finally {
1644
+ fillStatsRequest = null;
1645
+ }
1646
+ }
1622
1647
  const signingClients = /* @__PURE__ */ new WeakMap();
1623
1648
  async function signingClient(verb, opts) {
1624
1649
  const signer = opts?.signer;
@@ -1921,16 +1946,17 @@ function createCashClient(options) {
1921
1946
  async relayStatus(requestId) {
1922
1947
  return readRelayStatus(requestId, options.relay);
1923
1948
  },
1924
- async estimate(input) {
1949
+ async estimate(input, estimateOptions) {
1925
1950
  return readEstimate(readClient.publicClient, input, {
1926
- indexerClient: readClient,
1927
1951
  environment,
1952
+ ...estimateOptions?.includeEta !== void 0 ? { includeEta: estimateOptions.includeEta } : {},
1953
+ etaReader: async (etaInput) => fillEtaFromSample(await getFillStatsSample(), etaInput),
1928
1954
  ...options.relay ? { relay: options.relay } : {}
1929
1955
  });
1930
1956
  },
1931
1957
  async fillStats() {
1932
1958
  try {
1933
- return await readFillStats(readClient, environment);
1959
+ return (await getFillStatsSample()).stats;
1934
1960
  } catch (err) {
1935
1961
  throw errors.indexerUnavailable("fill stats", err);
1936
1962
  }
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as IntentStatus, C as CashPayoutInfo, a as IntentEntity, b as CashBuyerProfile, c as CashDepositInput, d as CreateDepositParamsArg, e as CashOrder, f as CashFill, g as CashCapabilities, h as CashoutResult, i as CashEstimate, j as CashFillStats, P as PrepareResult, k as CashPreparedStep, R as RelayExecutionResult, l as RelayQuote, m as RelayStatus, n as CashSourceCapabilities, T as TopUpResult, W as WithdrawResult } from './createCashClient-BIzOLHjF.cjs';
2
- export { o as CASH_ATTRIBUTION_CODE, p as CashAsset, q as CashChain, r as CashClient, s as CashClientOptions, t as CashFillEta, u as CashLeg, v as CashNextAction, w as CashOrderState, x as CashPairFillStats, y as CashPayout, z as CashPayoutPricing, A as CashPlatformCapability, B as CashPreparedStepKind, D as CashoutInput, E as CashoutOptions, F as CuratorPayeeDataInput, G as EstimateInput, M as MIN_CASHOUT_AMOUNT, O as OrdersOptions, H as RECOMMENDED_MIN_CASHOUT_AMOUNT, J as RelayOptions, K as RelayQuoteInput, L as RelaySourceInput, N as RelayTransaction, S as SignerOptions, Q as WatchOptions, U as WithdrawOptions, V as buildCapabilities, X as createCashClient } from './createCashClient-BIzOLHjF.cjs';
1
+ import { I as IntentStatus, C as CashPayoutInfo, a as IntentEntity, b as CashBuyerProfile, c as CashDepositInput, d as CreateDepositParamsArg, e as CashOrder, f as CashFill, g as CashCapabilities, h as CashoutResult, i as CashEstimate, j as CashFillStats, P as PrepareResult, k as CashPreparedStep, R as RelayExecutionResult, l as RelayQuote, m as RelayStatus, n as CashSourceCapabilities, T as TopUpResult, W as WithdrawResult } from './createCashClient-BhOytyHE.cjs';
2
+ export { o as CASH_ATTRIBUTION_CODE, p as CashAsset, q as CashChain, r as CashClient, s as CashClientOptions, t as CashFillEta, u as CashLeg, v as CashNextAction, w as CashOrderState, x as CashPairFillStats, y as CashPayout, z as CashPayoutPricing, A as CashPlatformCapability, B as CashPreparedStepKind, D as CashoutInput, E as CashoutOptions, F as CuratorPayeeDataInput, G as EstimateInput, H as EstimateOptions, M as MIN_CASHOUT_AMOUNT, O as OrdersOptions, J as RECOMMENDED_MIN_CASHOUT_AMOUNT, K as RelayOptions, L as RelayQuoteInput, N as RelaySourceInput, Q as RelayTransaction, S as SignerOptions, U as WatchOptions, V as WithdrawOptions, X as buildCapabilities, Y as createCashClient } from './createCashClient-BhOytyHE.cjs';
3
3
  import { PaymentMethodCatalog, CurrencyType, OracleAdapterOverrides, OnchainCurrency, Zkp2pClient, PreparedTransaction } from '@zkp2p/sdk';
4
4
  export { CurrencyType, PreparedTransaction, RuntimeEnv } from '@zkp2p/sdk';
5
5
  import { Log, Abi } from 'viem';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as IntentStatus, C as CashPayoutInfo, a as IntentEntity, b as CashBuyerProfile, c as CashDepositInput, d as CreateDepositParamsArg, e as CashOrder, f as CashFill, g as CashCapabilities, h as CashoutResult, i as CashEstimate, j as CashFillStats, P as PrepareResult, k as CashPreparedStep, R as RelayExecutionResult, l as RelayQuote, m as RelayStatus, n as CashSourceCapabilities, T as TopUpResult, W as WithdrawResult } from './createCashClient-BIzOLHjF.js';
2
- export { o as CASH_ATTRIBUTION_CODE, p as CashAsset, q as CashChain, r as CashClient, s as CashClientOptions, t as CashFillEta, u as CashLeg, v as CashNextAction, w as CashOrderState, x as CashPairFillStats, y as CashPayout, z as CashPayoutPricing, A as CashPlatformCapability, B as CashPreparedStepKind, D as CashoutInput, E as CashoutOptions, F as CuratorPayeeDataInput, G as EstimateInput, M as MIN_CASHOUT_AMOUNT, O as OrdersOptions, H as RECOMMENDED_MIN_CASHOUT_AMOUNT, J as RelayOptions, K as RelayQuoteInput, L as RelaySourceInput, N as RelayTransaction, S as SignerOptions, Q as WatchOptions, U as WithdrawOptions, V as buildCapabilities, X as createCashClient } from './createCashClient-BIzOLHjF.js';
1
+ import { I as IntentStatus, C as CashPayoutInfo, a as IntentEntity, b as CashBuyerProfile, c as CashDepositInput, d as CreateDepositParamsArg, e as CashOrder, f as CashFill, g as CashCapabilities, h as CashoutResult, i as CashEstimate, j as CashFillStats, P as PrepareResult, k as CashPreparedStep, R as RelayExecutionResult, l as RelayQuote, m as RelayStatus, n as CashSourceCapabilities, T as TopUpResult, W as WithdrawResult } from './createCashClient-BhOytyHE.js';
2
+ export { o as CASH_ATTRIBUTION_CODE, p as CashAsset, q as CashChain, r as CashClient, s as CashClientOptions, t as CashFillEta, u as CashLeg, v as CashNextAction, w as CashOrderState, x as CashPairFillStats, y as CashPayout, z as CashPayoutPricing, A as CashPlatformCapability, B as CashPreparedStepKind, D as CashoutInput, E as CashoutOptions, F as CuratorPayeeDataInput, G as EstimateInput, H as EstimateOptions, M as MIN_CASHOUT_AMOUNT, O as OrdersOptions, J as RECOMMENDED_MIN_CASHOUT_AMOUNT, K as RelayOptions, L as RelayQuoteInput, N as RelaySourceInput, Q as RelayTransaction, S as SignerOptions, U as WatchOptions, V as WithdrawOptions, X as buildCapabilities, Y as createCashClient } from './createCashClient-BhOytyHE.js';
3
3
  import { PaymentMethodCatalog, CurrencyType, OracleAdapterOverrides, OnchainCurrency, Zkp2pClient, PreparedTransaction } from '@zkp2p/sdk';
4
4
  export { CurrencyType, PreparedTransaction, RuntimeEnv } from '@zkp2p/sdk';
5
5
  import { Log, Abi } from 'viem';
package/dist/index.js CHANGED
@@ -580,11 +580,7 @@ async function readFillStatsSample(client, environment) {
580
580
  }
581
581
  return computeFillStatsSample(deposits, now, environment);
582
582
  }
583
- async function readFillStats(client, environment) {
584
- return (await readFillStatsSample(client, environment)).stats;
585
- }
586
- async function readFillEta(client, input) {
587
- const sample = await readFillStatsSample(client, input.environment);
583
+ function fillEtaFromSample(sample, input) {
588
584
  const currency = input.currency.toUpperCase();
589
585
  const seconds = input.platform ? sample.stats[`${basePlatformForMethod(input.platform)}:${currency}`]?.medianFillSeconds : sample.medianFillSecondsByCurrency.get(currency);
590
586
  return {
@@ -592,6 +588,10 @@ async function readFillEta(client, input) {
592
588
  label: etaLabel(seconds)
593
589
  };
594
590
  }
591
+ async function readFillEta(client, input) {
592
+ const sample = await readFillStatsSample(client, input.environment);
593
+ return fillEtaFromSample(sample, input);
594
+ }
595
595
  var RELAY_API_URL = MAINNET_RELAY_API;
596
596
  var NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
597
597
  var BASE_USDC_ASSET = {
@@ -1127,13 +1127,18 @@ async function readEstimate(publicClient, input, context = {}) {
1127
1127
  }
1128
1128
  } : {}
1129
1129
  };
1130
- if (context.indexerClient && context.environment) {
1130
+ if (context.includeEta !== false && context.environment) {
1131
1131
  try {
1132
- estimate.eta = await readFillEta(context.indexerClient, {
1132
+ const etaInput = {
1133
1133
  environment: context.environment,
1134
1134
  currency,
1135
1135
  ...input.platform ? { platform: input.platform } : {}
1136
- });
1136
+ };
1137
+ if (context.etaReader) {
1138
+ estimate.eta = await context.etaReader(etaInput);
1139
+ } else if (context.indexerClient) {
1140
+ estimate.eta = await readFillEta(context.indexerClient, etaInput);
1141
+ }
1137
1142
  } catch {
1138
1143
  }
1139
1144
  }
@@ -1142,6 +1147,7 @@ async function readEstimate(publicClient, input, context = {}) {
1142
1147
 
1143
1148
  // src/client/createCashClient.ts
1144
1149
  var DEFAULT_RPC_URL = "https://mainnet.base.org";
1150
+ var FILL_STATS_CACHE_MS = 15 * 60 * 1e3;
1145
1151
  var CASH_ATTRIBUTION_CODE = "peer-cash";
1146
1152
  var DEFAULT_CURATOR_URLS = {
1147
1153
  preproduction: "https://api-preprod.zkp2p.xyz",
@@ -1240,6 +1246,25 @@ function createCashClient(options) {
1240
1246
  });
1241
1247
  }
1242
1248
  const readClient = buildSdkClient(createWalletClient({ chain: base, transport }));
1249
+ let fillStatsCache = null;
1250
+ let fillStatsRequest = null;
1251
+ async function getFillStatsSample() {
1252
+ if (fillStatsCache && fillStatsCache.expiresAt > Date.now()) {
1253
+ return fillStatsCache.sample;
1254
+ }
1255
+ if (fillStatsRequest) return fillStatsRequest;
1256
+ fillStatsRequest = readFillStatsSample(readClient, environment);
1257
+ try {
1258
+ const sample = await fillStatsRequest;
1259
+ fillStatsCache = {
1260
+ sample,
1261
+ expiresAt: Date.now() + FILL_STATS_CACHE_MS
1262
+ };
1263
+ return sample;
1264
+ } finally {
1265
+ fillStatsRequest = null;
1266
+ }
1267
+ }
1243
1268
  const signingClients = /* @__PURE__ */ new WeakMap();
1244
1269
  async function signingClient(verb, opts) {
1245
1270
  const signer = opts?.signer;
@@ -1542,16 +1567,17 @@ function createCashClient(options) {
1542
1567
  async relayStatus(requestId) {
1543
1568
  return readRelayStatus(requestId, options.relay);
1544
1569
  },
1545
- async estimate(input) {
1570
+ async estimate(input, estimateOptions) {
1546
1571
  return readEstimate(readClient.publicClient, input, {
1547
- indexerClient: readClient,
1548
1572
  environment,
1573
+ ...estimateOptions?.includeEta !== void 0 ? { includeEta: estimateOptions.includeEta } : {},
1574
+ etaReader: async (etaInput) => fillEtaFromSample(await getFillStatsSample(), etaInput),
1549
1575
  ...options.relay ? { relay: options.relay } : {}
1550
1576
  });
1551
1577
  },
1552
1578
  async fillStats() {
1553
1579
  try {
1554
- return await readFillStats(readClient, environment);
1580
+ return (await getFillStatsSample()).stats;
1555
1581
  } catch (err) {
1556
1582
  throw errors.indexerUnavailable("fill stats", err);
1557
1583
  }
package/dist/react.cjs CHANGED
@@ -9,6 +9,7 @@ function useEstimate({
9
9
  currency,
10
10
  platform,
11
11
  source,
12
+ includeEta = true,
12
13
  refreshIntervalMs = 0
13
14
  }) {
14
15
  const [estimate, setEstimate] = react.useState(null);
@@ -34,7 +35,7 @@ function useEstimate({
34
35
  }
35
36
  return;
36
37
  }
37
- const identity = { client, amount, currency, platform, source };
38
+ const identity = { client, amount, currency, platform, source, includeEta };
38
39
  if (isCurrent()) {
39
40
  loadingIdentityRef.current = identity;
40
41
  errorIdentityRef.current = null;
@@ -42,12 +43,15 @@ function useEstimate({
42
43
  setError(null);
43
44
  }
44
45
  try {
45
- const result = await client.estimate({
46
- amount,
47
- currency,
48
- ...platform ? { platform } : {},
49
- ...source ? { source } : {}
50
- });
46
+ const result = await client.estimate(
47
+ {
48
+ amount,
49
+ currency,
50
+ ...platform ? { platform } : {},
51
+ ...source ? { source } : {}
52
+ },
53
+ { includeEta }
54
+ );
51
55
  if (isCurrent()) {
52
56
  estimateIdentityRef.current = identity;
53
57
  setEstimate(result);
@@ -63,7 +67,7 @@ function useEstimate({
63
67
  } finally {
64
68
  if (isCurrent()) setIsLoading(false);
65
69
  }
66
- }, [client, currency, amount, platform, source]);
70
+ }, [client, currency, amount, platform, source, includeEta]);
67
71
  react.useEffect(() => {
68
72
  latestRequestRef.current += 1;
69
73
  estimateIdentityRef.current = null;
@@ -72,7 +76,7 @@ function useEstimate({
72
76
  setEstimate(null);
73
77
  setIsLoading(false);
74
78
  setError(null);
75
- }, [client, amount, currency, platform, source]);
79
+ }, [client, amount, currency, platform, source, includeEta]);
76
80
  react.useEffect(() => {
77
81
  mountedRef.current = true;
78
82
  void refresh();
@@ -85,7 +89,7 @@ function useEstimate({
85
89
  if (timerRef.current) clearInterval(timerRef.current);
86
90
  };
87
91
  }, [refresh, refreshIntervalMs]);
88
- const matchesCurrentIdentity = (identity) => identity?.client === client && identity.amount === amount && identity.currency === currency && identity.platform === platform && identity.source === source;
92
+ const matchesCurrentIdentity = (identity) => identity?.client === client && identity.amount === amount && identity.currency === currency && identity.platform === platform && identity.source === source && identity.includeEta === includeEta;
89
93
  return {
90
94
  estimate: matchesCurrentIdentity(estimateIdentityRef.current) ? estimate : null,
91
95
  isLoading: matchesCurrentIdentity(loadingIdentityRef.current) ? isLoading : false,
package/dist/react.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CurrencyType } from '@zkp2p/sdk';
2
- import { r as CashClient, G as EstimateInput, i as CashEstimate, E as CashoutOptions, h as CashoutResult, D as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-BIzOLHjF.cjs';
2
+ import { r as CashClient, G as EstimateInput, i as CashEstimate, E as CashoutOptions, h as CashoutResult, D as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-BhOytyHE.cjs';
3
3
  import { WalletClient } from 'viem';
4
4
  import '@relayprotocol/relay-sdk';
5
5
 
@@ -12,6 +12,8 @@ interface UseEstimateOptions {
12
12
  platform?: string | null | undefined;
13
13
  /** Optional Relay source. Omit for the Base USDC default path. */
14
14
  source?: EstimateInput['source'] | null | undefined;
15
+ /** Disable to render the oracle rate before loading pair fill stats separately. */
16
+ includeEta?: boolean;
15
17
  /** Re-fetch interval (ms) so the displayed rate tracks the market. 0 = no auto-refresh. */
16
18
  refreshIntervalMs?: number;
17
19
  }
@@ -20,7 +22,7 @@ interface UseEstimateOptions {
20
22
  * estimate; the binding rate resolves at the Chainlink oracle when a buyer
21
23
  * fills - there is no committed quote to show.
22
24
  */
23
- declare function useEstimate({ client, amount, currency, platform, source, refreshIntervalMs, }: UseEstimateOptions): {
25
+ declare function useEstimate({ client, amount, currency, platform, source, includeEta, refreshIntervalMs, }: UseEstimateOptions): {
24
26
  estimate: CashEstimate | null;
25
27
  isLoading: boolean;
26
28
  error: Error | null;
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CurrencyType } from '@zkp2p/sdk';
2
- import { r as CashClient, G as EstimateInput, i as CashEstimate, E as CashoutOptions, h as CashoutResult, D as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-BIzOLHjF.js';
2
+ import { r as CashClient, G as EstimateInput, i as CashEstimate, E as CashoutOptions, h as CashoutResult, D as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-BhOytyHE.js';
3
3
  import { WalletClient } from 'viem';
4
4
  import '@relayprotocol/relay-sdk';
5
5
 
@@ -12,6 +12,8 @@ interface UseEstimateOptions {
12
12
  platform?: string | null | undefined;
13
13
  /** Optional Relay source. Omit for the Base USDC default path. */
14
14
  source?: EstimateInput['source'] | null | undefined;
15
+ /** Disable to render the oracle rate before loading pair fill stats separately. */
16
+ includeEta?: boolean;
15
17
  /** Re-fetch interval (ms) so the displayed rate tracks the market. 0 = no auto-refresh. */
16
18
  refreshIntervalMs?: number;
17
19
  }
@@ -20,7 +22,7 @@ interface UseEstimateOptions {
20
22
  * estimate; the binding rate resolves at the Chainlink oracle when a buyer
21
23
  * fills - there is no committed quote to show.
22
24
  */
23
- declare function useEstimate({ client, amount, currency, platform, source, refreshIntervalMs, }: UseEstimateOptions): {
25
+ declare function useEstimate({ client, amount, currency, platform, source, includeEta, refreshIntervalMs, }: UseEstimateOptions): {
24
26
  estimate: CashEstimate | null;
25
27
  isLoading: boolean;
26
28
  error: Error | null;
package/dist/react.js CHANGED
@@ -7,6 +7,7 @@ function useEstimate({
7
7
  currency,
8
8
  platform,
9
9
  source,
10
+ includeEta = true,
10
11
  refreshIntervalMs = 0
11
12
  }) {
12
13
  const [estimate, setEstimate] = useState(null);
@@ -32,7 +33,7 @@ function useEstimate({
32
33
  }
33
34
  return;
34
35
  }
35
- const identity = { client, amount, currency, platform, source };
36
+ const identity = { client, amount, currency, platform, source, includeEta };
36
37
  if (isCurrent()) {
37
38
  loadingIdentityRef.current = identity;
38
39
  errorIdentityRef.current = null;
@@ -40,12 +41,15 @@ function useEstimate({
40
41
  setError(null);
41
42
  }
42
43
  try {
43
- const result = await client.estimate({
44
- amount,
45
- currency,
46
- ...platform ? { platform } : {},
47
- ...source ? { source } : {}
48
- });
44
+ const result = await client.estimate(
45
+ {
46
+ amount,
47
+ currency,
48
+ ...platform ? { platform } : {},
49
+ ...source ? { source } : {}
50
+ },
51
+ { includeEta }
52
+ );
49
53
  if (isCurrent()) {
50
54
  estimateIdentityRef.current = identity;
51
55
  setEstimate(result);
@@ -61,7 +65,7 @@ function useEstimate({
61
65
  } finally {
62
66
  if (isCurrent()) setIsLoading(false);
63
67
  }
64
- }, [client, currency, amount, platform, source]);
68
+ }, [client, currency, amount, platform, source, includeEta]);
65
69
  useEffect(() => {
66
70
  latestRequestRef.current += 1;
67
71
  estimateIdentityRef.current = null;
@@ -70,7 +74,7 @@ function useEstimate({
70
74
  setEstimate(null);
71
75
  setIsLoading(false);
72
76
  setError(null);
73
- }, [client, amount, currency, platform, source]);
77
+ }, [client, amount, currency, platform, source, includeEta]);
74
78
  useEffect(() => {
75
79
  mountedRef.current = true;
76
80
  void refresh();
@@ -83,7 +87,7 @@ function useEstimate({
83
87
  if (timerRef.current) clearInterval(timerRef.current);
84
88
  };
85
89
  }, [refresh, refreshIntervalMs]);
86
- const matchesCurrentIdentity = (identity) => identity?.client === client && identity.amount === amount && identity.currency === currency && identity.platform === platform && identity.source === source;
90
+ const matchesCurrentIdentity = (identity) => identity?.client === client && identity.amount === amount && identity.currency === currency && identity.platform === platform && identity.source === source && identity.includeEta === includeEta;
87
91
  return {
88
92
  estimate: matchesCurrentIdentity(estimateIdentityRef.current) ? estimate : null,
89
93
  isLoading: matchesCurrentIdentity(loadingIdentityRef.current) ? isLoading : false,
package/dist/tools.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // package.json
4
4
  var package_default = {
5
- version: "0.1.8"};
5
+ version: "0.1.9"};
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.8"};
3
+ version: "0.1.9"};
4
4
 
5
5
  // src/tools/index.ts
6
6
  var bigintString = {
@@ -135,6 +135,11 @@ methods aggregate to `zelle:USD`. Consumers own thresholding; the recommended
135
135
  gate is `fills >= 10 && medianFillSeconds <= 48h`, with a fail-open fallback to
136
136
  the full capability catalog when the read fails or filtering would empty it.
137
137
  Medians are per-deposit first-fill latencies, never means or censored cohorts.
138
+ The client caches one raw environment snapshot for 15 minutes and de-duplicates
139
+ concurrent reads; ETA resolution still uses only the requested normalized
140
+ `platform:currency` key. A progressive UI can call
141
+ `estimate(input, { includeEta: false })` to render rate/receive immediately,
142
+ then read that pair from `fillStats()` without coupling the two loading states.
138
143
 
139
144
  - **Buyer arrival time is market-driven.** A deposit at market rate should
140
145
  fill fast, but the ETA is only a recent historical sample.
package/llms.txt CHANGED
@@ -24,6 +24,11 @@ Key facts:
24
24
  - fillStats() returns raw `{ fills, medianFillSeconds? }` evidence keyed by
25
25
  `platform:currency`. Recommended consumer gate: fills >= 10 and median <=
26
26
  48h; fail open to capabilities() if unavailable or filtering empties it.
27
+ Its raw environment snapshot is cached for 15 minutes, while lookups remain
28
+ exact to the normalized platform:currency pair.
29
+ - Progressive UIs can call estimate(input, { includeEta: false }) so the
30
+ oracle rate is not blocked by indexer history, then load the exact pair from
31
+ fillStats() separately.
27
32
  - capabilities() exposes one Zelle platform. A zelle cashout internally attaches
28
33
  the generic method plus Chase, Bank of America, and Citi buyer routes.
29
34
  - Resume any order from its depositId alone (composite escrow_onchainId).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkp2p/cash",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Peer Cash - offramp-only SDK for routing crypto to Base USDC, then cashing out to fiat at the live oracle market rate.",
5
5
  "license": "MIT",
6
6
  "author": "Peer (https://peer.xyz)",