@zkp2p/cash 0.1.7 → 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 +11 -3
- package/README.md +23 -4
- package/dist/{createCashClient-jUA_GNdh.d.cts → createCashClient-BhOytyHE.d.cts} +24 -2
- package/dist/{createCashClient-jUA_GNdh.d.ts → createCashClient-BhOytyHE.d.ts} +24 -2
- package/dist/index.cjs +194 -53
- package/dist/index.d.cts +28 -5
- package/dist/index.d.ts +28 -5
- package/dist/index.js +191 -54
- package/dist/react.cjs +14 -10
- package/dist/react.d.cts +4 -2
- package/dist/react.d.ts +4 -2
- package/dist/react.js +14 -10
- package/dist/tools.cjs +10 -1
- package/dist/tools.d.cts +8 -0
- package/dist/tools.d.ts +8 -0
- package/dist/tools.js +10 -1
- package/docs/lifecycle-and-recovery.md +16 -4
- package/llms.txt +12 -3
- package/package.json +4 -2
package/AGENTS.md
CHANGED
|
@@ -54,6 +54,12 @@ 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
|
+
|
|
60
|
+
// Optional: raw demand + speed evidence per offered platform:currency pair.
|
|
61
|
+
const stats = await cash.fillStats();
|
|
62
|
+
|
|
57
63
|
// 3. Execute.
|
|
58
64
|
const { depositId } = await cash.cashout(
|
|
59
65
|
{
|
|
@@ -101,9 +107,11 @@ console.log(routed.source?.transactions?.origin, routed.source?.transactions?.de
|
|
|
101
107
|
binding rate resolves at the oracle when a buyer fills. Do not display or
|
|
102
108
|
log it as a locked price.
|
|
103
109
|
- **Do not invent an ETA.** Use `estimate().eta`: `{ seconds, label }` backed
|
|
104
|
-
by rolling 30-day
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
by the same rolling 30-day, intent-attributed pair sample as `fillStats()`,
|
|
111
|
+
measured from deposit creation to first fill. Use `order.explain()` for live
|
|
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.
|
|
107
115
|
- **Do not hardcode Relay source assets.** Use Relay SDK-backed EVM
|
|
108
116
|
`capabilities({ includeRelaySources: true })` and `cashout({ source, ... })`.
|
|
109
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,9 +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 |
|
|
86
|
+
| `fillStats()` | Cached 30-day fill counts and median first-fill time per exact `platform:currency` pair |
|
|
79
87
|
| `quoteSource(input)` / `executeSourceQuote(quote, { signer })` | Relay SDK EVM source routing into Base USDC before cashout |
|
|
80
88
|
| `relayStatus(requestId)` | Relay request status from the Relay SDK request path |
|
|
81
|
-
| `estimate({ amount, currency })`
|
|
89
|
+
| `estimate({ amount, currency }, { includeEta? })` | Base USDC oracle estimate; optionally skip the historical ETA for progressive rendering |
|
|
82
90
|
| `cashout(input, { signer })` | Registers your payee, creates the protocol-held order, returns the `depositId` |
|
|
83
91
|
| `order(depositId)` / `orders(owner)` | Resume any order from its id alone; list all orders for a wallet |
|
|
84
92
|
| `watch(depositId)` | Async iterator: yields on every state change until terminal, abort, or timeout |
|
|
@@ -97,6 +105,11 @@ must execute and confirm its Relay route before preparing the Base-USDC
|
|
|
97
105
|
cashout. Every Peer Cash transaction, including approves, carries ERC-8021
|
|
98
106
|
attribution: `peer-cash` first, your own `referrer` code(s) after it.
|
|
99
107
|
|
|
108
|
+
`capabilities()` presents Zelle as one platform. A cashout with
|
|
109
|
+
`receive.platform: 'zelle'` automatically attaches the generic Zelle method
|
|
110
|
+
and its Chase, Bank of America, and Citi buyer routes to the deposit; the payee
|
|
111
|
+
handle and public API remain bank-agnostic.
|
|
112
|
+
|
|
100
113
|
The default/minimal flow is unchanged: pass Base USDC base units to
|
|
101
114
|
`estimate()` and `cashout()`. For any other source asset, pass `source` to
|
|
102
115
|
`cashout()` with a source-chain signer. The SDK settles the Base allowance,
|
|
@@ -172,9 +185,15 @@ awaiting-buyer ──────────► matched ───────
|
|
|
172
185
|
fills. `estimate()` says "approximately"; nothing in this API pretends to
|
|
173
186
|
lock a price.
|
|
174
187
|
- **ETA is historical.** `estimate().eta` is just `{ seconds, label }`, backed
|
|
175
|
-
by rolling 30-day
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
by the same rolling 30-day, intent-attributed pair sampler as `fillStats()`,
|
|
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.
|
|
193
|
+
- **Availability thresholds belong to the consumer.** `fillStats()` returns raw
|
|
194
|
+
evidence. A recommended gate is `fills >= 10 && medianFillSeconds <= 48h`.
|
|
195
|
+
Fail open to the full `capabilities()` catalog when stats are unavailable or
|
|
196
|
+
the gate would empty the offered catalog.
|
|
178
197
|
- **Everything is resumable.** An order is reconstructed from the chain by
|
|
179
198
|
`depositId` alone. Close the tab, switch devices, crash the process - then
|
|
180
199
|
call `order(depositId)`.
|
|
@@ -357,6 +357,14 @@ interface CashCapabilities {
|
|
|
357
357
|
}
|
|
358
358
|
declare function buildCapabilities(environment: RuntimeEnv): CashCapabilities;
|
|
359
359
|
|
|
360
|
+
interface CashPairFillStats {
|
|
361
|
+
/** Fulfilled intents through this pair inside the rolling 30-day window. */
|
|
362
|
+
fills: number;
|
|
363
|
+
/** Median deposit-to-first-fill seconds, sampled once per deposit for this pair. */
|
|
364
|
+
medianFillSeconds?: number;
|
|
365
|
+
}
|
|
366
|
+
/** Raw demand and speed evidence keyed by `basePlatform:currencyCode`. */
|
|
367
|
+
type CashFillStats = Record<string, CashPairFillStats>;
|
|
360
368
|
interface CashFillEta {
|
|
361
369
|
/** Simple headline ETA from recent deposits. Undefined when no recent sample exists. */
|
|
362
370
|
seconds?: number;
|
|
@@ -393,6 +401,13 @@ interface EstimateInput {
|
|
|
393
401
|
tradeType?: 'EXACT_INPUT' | 'EXACT_OUTPUT' | 'EXPECTED_OUTPUT';
|
|
394
402
|
};
|
|
395
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
|
+
}
|
|
396
411
|
interface CashEstimate {
|
|
397
412
|
/** Always `'oracle-estimate'` - there is no committed quote in Peer Cash. */
|
|
398
413
|
kind: 'oracle-estimate';
|
|
@@ -581,6 +596,13 @@ interface CashClient {
|
|
|
581
596
|
capabilities(options: {
|
|
582
597
|
includeRelaySources: true;
|
|
583
598
|
}): Promise<CashCapabilities>;
|
|
599
|
+
/**
|
|
600
|
+
* 0c - Raw 30-day demand and first-fill speed evidence keyed by
|
|
601
|
+
* `platform:currency`. A recommended consumer gate is `fills >= 10 &&
|
|
602
|
+
* medianFillSeconds <= 48h`; fail open to the full capability catalog when
|
|
603
|
+
* stats are unavailable or the gate would remove every pair.
|
|
604
|
+
*/
|
|
605
|
+
fillStats(): Promise<CashFillStats>;
|
|
584
606
|
/** Relay-only source discovery helper. */
|
|
585
607
|
sourceCapabilities(): Promise<CashSourceCapabilities>;
|
|
586
608
|
/** Quote any Relay-supported EVM source asset into Base USDC. */
|
|
@@ -597,7 +619,7 @@ interface CashClient {
|
|
|
597
619
|
/** Track Relay execution status by quote/request id. */
|
|
598
620
|
relayStatus(requestId: string): Promise<RelayStatus>;
|
|
599
621
|
/** 1 - Estimate: currency + amount only. No payee, no side effects, no expiry. */
|
|
600
|
-
estimate(input: EstimateInput): Promise<CashEstimate>;
|
|
622
|
+
estimate(input: EstimateInput, options?: EstimateOptions): Promise<CashEstimate>;
|
|
601
623
|
/** 2 - Cash out: payee registration + deposit params + submission happen here. */
|
|
602
624
|
cashout(input: CashoutInput, opts: CashoutOptions): Promise<CashoutResult>;
|
|
603
625
|
/** 2b - Unsigned path: `txs[]` for agent wallets, AA, server keys, policy layers. */
|
|
@@ -640,4 +662,4 @@ interface CashClient {
|
|
|
640
662
|
}
|
|
641
663
|
declare function createCashClient(options: CashClientOptions): CashClient;
|
|
642
664
|
|
|
643
|
-
export { type
|
|
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 };
|
|
@@ -357,6 +357,14 @@ interface CashCapabilities {
|
|
|
357
357
|
}
|
|
358
358
|
declare function buildCapabilities(environment: RuntimeEnv): CashCapabilities;
|
|
359
359
|
|
|
360
|
+
interface CashPairFillStats {
|
|
361
|
+
/** Fulfilled intents through this pair inside the rolling 30-day window. */
|
|
362
|
+
fills: number;
|
|
363
|
+
/** Median deposit-to-first-fill seconds, sampled once per deposit for this pair. */
|
|
364
|
+
medianFillSeconds?: number;
|
|
365
|
+
}
|
|
366
|
+
/** Raw demand and speed evidence keyed by `basePlatform:currencyCode`. */
|
|
367
|
+
type CashFillStats = Record<string, CashPairFillStats>;
|
|
360
368
|
interface CashFillEta {
|
|
361
369
|
/** Simple headline ETA from recent deposits. Undefined when no recent sample exists. */
|
|
362
370
|
seconds?: number;
|
|
@@ -393,6 +401,13 @@ interface EstimateInput {
|
|
|
393
401
|
tradeType?: 'EXACT_INPUT' | 'EXACT_OUTPUT' | 'EXPECTED_OUTPUT';
|
|
394
402
|
};
|
|
395
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
|
+
}
|
|
396
411
|
interface CashEstimate {
|
|
397
412
|
/** Always `'oracle-estimate'` - there is no committed quote in Peer Cash. */
|
|
398
413
|
kind: 'oracle-estimate';
|
|
@@ -581,6 +596,13 @@ interface CashClient {
|
|
|
581
596
|
capabilities(options: {
|
|
582
597
|
includeRelaySources: true;
|
|
583
598
|
}): Promise<CashCapabilities>;
|
|
599
|
+
/**
|
|
600
|
+
* 0c - Raw 30-day demand and first-fill speed evidence keyed by
|
|
601
|
+
* `platform:currency`. A recommended consumer gate is `fills >= 10 &&
|
|
602
|
+
* medianFillSeconds <= 48h`; fail open to the full capability catalog when
|
|
603
|
+
* stats are unavailable or the gate would remove every pair.
|
|
604
|
+
*/
|
|
605
|
+
fillStats(): Promise<CashFillStats>;
|
|
584
606
|
/** Relay-only source discovery helper. */
|
|
585
607
|
sourceCapabilities(): Promise<CashSourceCapabilities>;
|
|
586
608
|
/** Quote any Relay-supported EVM source asset into Base USDC. */
|
|
@@ -597,7 +619,7 @@ interface CashClient {
|
|
|
597
619
|
/** Track Relay execution status by quote/request id. */
|
|
598
620
|
relayStatus(requestId: string): Promise<RelayStatus>;
|
|
599
621
|
/** 1 - Estimate: currency + amount only. No payee, no side effects, no expiry. */
|
|
600
|
-
estimate(input: EstimateInput): Promise<CashEstimate>;
|
|
622
|
+
estimate(input: EstimateInput, options?: EstimateOptions): Promise<CashEstimate>;
|
|
601
623
|
/** 2 - Cash out: payee registration + deposit params + submission happen here. */
|
|
602
624
|
cashout(input: CashoutInput, opts: CashoutOptions): Promise<CashoutResult>;
|
|
603
625
|
/** 2b - Unsigned path: `txs[]` for agent wallets, AA, server keys, policy layers. */
|
|
@@ -640,4 +662,4 @@ interface CashClient {
|
|
|
640
662
|
}
|
|
641
663
|
declare function createCashClient(options: CashClientOptions): CashClient;
|
|
642
664
|
|
|
643
|
-
export { type
|
|
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
|
@@ -403,6 +403,26 @@ function parseCompositeDepositId(compositeId) {
|
|
|
403
403
|
const onchainDepositId = BigInt(rawDepositId);
|
|
404
404
|
return { escrowAddress: canonicalEscrowAddress, onchainDepositId };
|
|
405
405
|
}
|
|
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
|
|
406
426
|
var MIN_CASHOUT_AMOUNT = 10000n;
|
|
407
427
|
var RECOMMENDED_MIN_CASHOUT_AMOUNT = 1000000n;
|
|
408
428
|
var PAYEE_HINTS = {
|
|
@@ -421,13 +441,20 @@ var PAYEE_HINTS = {
|
|
|
421
441
|
var IDENTITY_ATTESTATION_PLATFORMS = /* @__PURE__ */ new Set(["wise", "paypal"]);
|
|
422
442
|
function buildCapabilities(environment) {
|
|
423
443
|
const catalog = sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
|
|
424
|
-
const
|
|
444
|
+
const currenciesByPlatform = /* @__PURE__ */ new Map();
|
|
445
|
+
for (const [method, entry] of Object.entries(catalog)) {
|
|
446
|
+
const platform = basePlatformForMethod(method);
|
|
425
447
|
const currencies2 = (entry.currencies ?? []).map((hash) => sdk.getCurrencyCodeFromHash(hash)).filter(
|
|
426
448
|
(code) => code != null && isMarketRateSupported(code)
|
|
427
449
|
);
|
|
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]) => {
|
|
428
455
|
return {
|
|
429
456
|
platform,
|
|
430
|
-
currencies: [...
|
|
457
|
+
currencies: [...currencies2].sort(),
|
|
431
458
|
payeeHint: PAYEE_HINTS[platform] ?? "Your payment handle for this platform",
|
|
432
459
|
requiresIdentityAttestation: IDENTITY_ATTESTATION_PLATFORMS.has(platform)
|
|
433
460
|
};
|
|
@@ -809,11 +836,8 @@ function mapChainError(verb, err, context = {}) {
|
|
|
809
836
|
}
|
|
810
837
|
return errors.chainCallFailed(verb, err);
|
|
811
838
|
}
|
|
812
|
-
var
|
|
813
|
-
var
|
|
814
|
-
var ETA_PAGE_LIMIT = 250;
|
|
815
|
-
var ETA_MAX_DEPOSIT_SCAN = 2e3;
|
|
816
|
-
var FULFILLED = /* @__PURE__ */ new Set(["FULFILLED", "MANUALLY_RELEASED"]);
|
|
839
|
+
var FILL_STATS_WINDOW_SECONDS = 30 * 24 * 60 * 60;
|
|
840
|
+
var FILL_STATS_PAGE_LIMIT = 250;
|
|
817
841
|
function toUnixSeconds2(value) {
|
|
818
842
|
if (value === null || value === void 0 || value === "") return void 0;
|
|
819
843
|
if (value instanceof Date) {
|
|
@@ -824,12 +848,22 @@ function toUnixSeconds2(value) {
|
|
|
824
848
|
const parsed = Date.parse(value);
|
|
825
849
|
if (Number.isFinite(parsed)) return Math.floor(parsed / 1e3);
|
|
826
850
|
}
|
|
827
|
-
const
|
|
828
|
-
return Number.isFinite(
|
|
851
|
+
const numeric = Number(value);
|
|
852
|
+
return Number.isFinite(numeric) && numeric > 0 ? numeric : void 0;
|
|
853
|
+
}
|
|
854
|
+
function normalizeCurrencyCode(value) {
|
|
855
|
+
const raw = value?.trim();
|
|
856
|
+
if (!raw) return void 0;
|
|
857
|
+
if (!raw.toLowerCase().startsWith("0x")) return raw.toUpperCase();
|
|
858
|
+
try {
|
|
859
|
+
return sdk.getCurrencyCodeFromHash(raw)?.toUpperCase();
|
|
860
|
+
} catch {
|
|
861
|
+
return void 0;
|
|
862
|
+
}
|
|
829
863
|
}
|
|
830
864
|
function median(values) {
|
|
831
865
|
if (values.length === 0) return void 0;
|
|
832
|
-
const sorted = [...values].sort((
|
|
866
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
833
867
|
const mid = Math.floor(sorted.length / 2);
|
|
834
868
|
return sorted.length % 2 === 1 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
|
|
835
869
|
}
|
|
@@ -841,52 +875,102 @@ function etaLabel(seconds) {
|
|
|
841
875
|
const hours = Math.max(1, Math.round(minutes / 60));
|
|
842
876
|
return `Usually starts in about ${hours} hr`;
|
|
843
877
|
}
|
|
844
|
-
function
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
);
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
878
|
+
function computeFillStatsSample(deposits, nowSeconds, environment) {
|
|
879
|
+
const catalog = sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
|
|
880
|
+
const windowStart = nowSeconds - FILL_STATS_WINDOW_SECONDS;
|
|
881
|
+
const fillCounts = /* @__PURE__ */ new Map();
|
|
882
|
+
const latenciesByPair = /* @__PURE__ */ new Map();
|
|
883
|
+
const latenciesByCurrency = /* @__PURE__ */ new Map();
|
|
884
|
+
for (const deposit of deposits) {
|
|
885
|
+
const createdAt = toUnixSeconds2(deposit.createdAt ?? deposit.timestamp);
|
|
886
|
+
const firstFillByPair = /* @__PURE__ */ new Map();
|
|
887
|
+
const firstFillByCurrency = /* @__PURE__ */ new Map();
|
|
888
|
+
for (const intent of deposit.intents ?? []) {
|
|
889
|
+
const fulfilledAt = toUnixSeconds2(intent.fulfillTimestamp);
|
|
890
|
+
if (fulfilledAt === void 0 || fulfilledAt < windowStart) continue;
|
|
891
|
+
let method;
|
|
892
|
+
try {
|
|
893
|
+
method = intent.paymentMethodHash ? sdk.resolvePaymentMethodNameFromHash(intent.paymentMethodHash, catalog) : void 0;
|
|
894
|
+
} catch {
|
|
895
|
+
method = void 0;
|
|
896
|
+
}
|
|
897
|
+
const currency = normalizeCurrencyCode(intent.fiatCurrency);
|
|
898
|
+
if (!method || !currency) continue;
|
|
899
|
+
const pair = `${basePlatformForMethod(method)}:${currency}`;
|
|
900
|
+
fillCounts.set(pair, (fillCounts.get(pair) ?? 0) + 1);
|
|
901
|
+
if (createdAt === void 0 || createdAt < windowStart || fulfilledAt < createdAt) continue;
|
|
902
|
+
const previousPairFill = firstFillByPair.get(pair);
|
|
903
|
+
if (previousPairFill === void 0 || fulfilledAt < previousPairFill) {
|
|
904
|
+
firstFillByPair.set(pair, fulfilledAt);
|
|
905
|
+
}
|
|
906
|
+
const previousCurrencyFill = firstFillByCurrency.get(currency);
|
|
907
|
+
if (previousCurrencyFill === void 0 || fulfilledAt < previousCurrencyFill) {
|
|
908
|
+
firstFillByCurrency.set(currency, fulfilledAt);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
if (createdAt === void 0) continue;
|
|
912
|
+
for (const [pair, firstFill] of firstFillByPair) {
|
|
913
|
+
const latencies = latenciesByPair.get(pair) ?? [];
|
|
914
|
+
latencies.push(firstFill - createdAt);
|
|
915
|
+
latenciesByPair.set(pair, latencies);
|
|
916
|
+
}
|
|
917
|
+
for (const [currency, firstFill] of firstFillByCurrency) {
|
|
918
|
+
const latencies = latenciesByCurrency.get(currency) ?? [];
|
|
919
|
+
latencies.push(firstFill - createdAt);
|
|
920
|
+
latenciesByCurrency.set(currency, latencies);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const stats = {};
|
|
924
|
+
for (const [pair, fills] of fillCounts) {
|
|
925
|
+
const medianFillSeconds = median(latenciesByPair.get(pair) ?? []);
|
|
926
|
+
stats[pair] = {
|
|
927
|
+
fills,
|
|
928
|
+
...medianFillSeconds !== void 0 ? { medianFillSeconds } : {}
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
const medianFillSecondsByCurrency = /* @__PURE__ */ new Map();
|
|
932
|
+
for (const [currency, latencies] of latenciesByCurrency) {
|
|
933
|
+
const value = median(latencies);
|
|
934
|
+
if (value !== void 0) medianFillSecondsByCurrency.set(currency, value);
|
|
935
|
+
}
|
|
936
|
+
return { stats, medianFillSecondsByCurrency };
|
|
853
937
|
}
|
|
854
|
-
async function
|
|
938
|
+
async function readFillStatsSample(client, environment) {
|
|
855
939
|
const now = Math.floor(Date.now() / 1e3);
|
|
856
|
-
const windowStart = now -
|
|
940
|
+
const windowStart = now - FILL_STATS_WINDOW_SECONDS;
|
|
857
941
|
const deposits = [];
|
|
858
|
-
for (let offset = 0;
|
|
942
|
+
for (let offset = 0; ; offset += FILL_STATS_PAGE_LIMIT) {
|
|
859
943
|
const page = await client.indexer.getDepositsWithRelations(
|
|
860
944
|
{ chainId: BASE_CHAIN_ID },
|
|
861
|
-
{
|
|
945
|
+
{
|
|
946
|
+
limit: FILL_STATS_PAGE_LIMIT,
|
|
947
|
+
offset,
|
|
948
|
+
orderBy: "updatedAt",
|
|
949
|
+
orderDirection: "desc"
|
|
950
|
+
},
|
|
862
951
|
{ includeIntents: true, intentStatuses: ["FULFILLED", "MANUALLY_RELEASED"] }
|
|
863
952
|
);
|
|
864
953
|
deposits.push(...page);
|
|
865
|
-
if (page.length <
|
|
866
|
-
const
|
|
867
|
-
...page.map((deposit) => toUnixSeconds2(deposit.
|
|
954
|
+
if (page.length < FILL_STATS_PAGE_LIMIT) break;
|
|
955
|
+
const oldestUpdatedAt = Math.min(
|
|
956
|
+
...page.map((deposit) => toUnixSeconds2(deposit.updatedAt) ?? Infinity)
|
|
868
957
|
);
|
|
869
|
-
if (
|
|
958
|
+
if (oldestUpdatedAt < windowStart) break;
|
|
870
959
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
const fulfilled = (deposit.intents ?? []).filter((intent) => intent.status != null && FULFILLED.has(intent.status)).map((intent) => ({
|
|
877
|
-
fulfilledAt: toUnixSeconds2(intent.fulfillTimestamp)
|
|
878
|
-
})).filter(
|
|
879
|
-
(intent) => intent.fulfilledAt !== void 0 && intent.fulfilledAt >= createdAt
|
|
880
|
-
).sort((a, b) => a.fulfilledAt - b.fulfilledAt);
|
|
881
|
-
if (fulfilled.length === 0) continue;
|
|
882
|
-
firstFillLatencies.push(fulfilled[0].fulfilledAt - createdAt);
|
|
883
|
-
}
|
|
884
|
-
const seconds = median(firstFillLatencies);
|
|
960
|
+
return computeFillStatsSample(deposits, now, environment);
|
|
961
|
+
}
|
|
962
|
+
function fillEtaFromSample(sample, input) {
|
|
963
|
+
const currency = input.currency.toUpperCase();
|
|
964
|
+
const seconds = input.platform ? sample.stats[`${basePlatformForMethod(input.platform)}:${currency}`]?.medianFillSeconds : sample.medianFillSecondsByCurrency.get(currency);
|
|
885
965
|
return {
|
|
886
966
|
...seconds !== void 0 ? { seconds } : {},
|
|
887
967
|
label: etaLabel(seconds)
|
|
888
968
|
};
|
|
889
969
|
}
|
|
970
|
+
async function readFillEta(client, input) {
|
|
971
|
+
const sample = await readFillStatsSample(client, input.environment);
|
|
972
|
+
return fillEtaFromSample(sample, input);
|
|
973
|
+
}
|
|
890
974
|
var RELAY_API_URL = relaySdk.MAINNET_RELAY_API;
|
|
891
975
|
var NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
892
976
|
var BASE_USDC_ASSET = {
|
|
@@ -1422,13 +1506,18 @@ async function readEstimate(publicClient, input, context = {}) {
|
|
|
1422
1506
|
}
|
|
1423
1507
|
} : {}
|
|
1424
1508
|
};
|
|
1425
|
-
if (context.
|
|
1509
|
+
if (context.includeEta !== false && context.environment) {
|
|
1426
1510
|
try {
|
|
1427
|
-
|
|
1511
|
+
const etaInput = {
|
|
1428
1512
|
environment: context.environment,
|
|
1429
1513
|
currency,
|
|
1430
1514
|
...input.platform ? { platform: input.platform } : {}
|
|
1431
|
-
}
|
|
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
|
+
}
|
|
1432
1521
|
} catch {
|
|
1433
1522
|
}
|
|
1434
1523
|
}
|
|
@@ -1437,6 +1526,7 @@ async function readEstimate(publicClient, input, context = {}) {
|
|
|
1437
1526
|
|
|
1438
1527
|
// src/client/createCashClient.ts
|
|
1439
1528
|
var DEFAULT_RPC_URL = "https://mainnet.base.org";
|
|
1529
|
+
var FILL_STATS_CACHE_MS = 15 * 60 * 1e3;
|
|
1440
1530
|
var CASH_ATTRIBUTION_CODE = "peer-cash";
|
|
1441
1531
|
var DEFAULT_CURATOR_URLS = {
|
|
1442
1532
|
preproduction: "https://api-preprod.zkp2p.xyz",
|
|
@@ -1535,6 +1625,25 @@ function createCashClient(options) {
|
|
|
1535
1625
|
});
|
|
1536
1626
|
}
|
|
1537
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
|
+
}
|
|
1538
1647
|
const signingClients = /* @__PURE__ */ new WeakMap();
|
|
1539
1648
|
async function signingClient(verb, opts) {
|
|
1540
1649
|
const signer = opts?.signer;
|
|
@@ -1549,6 +1658,7 @@ function createCashClient(options) {
|
|
|
1549
1658
|
}
|
|
1550
1659
|
function validatePayout(input) {
|
|
1551
1660
|
const { receive } = input;
|
|
1661
|
+
const catalog = sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
|
|
1552
1662
|
const platform = buildCapabilities(environment).platforms.find(
|
|
1553
1663
|
(capability) => capability.platform === receive.platform
|
|
1554
1664
|
);
|
|
@@ -1559,14 +1669,13 @@ function createCashClient(options) {
|
|
|
1559
1669
|
if (!platform.currencies.includes(receive.currency)) {
|
|
1560
1670
|
throw errors.unsupportedPlatformCurrency(receive.platform, receive.currency);
|
|
1561
1671
|
}
|
|
1672
|
+
const paymentMethods = paymentMethodsForPlatform(receive.platform, catalog);
|
|
1562
1673
|
return {
|
|
1563
|
-
payouts:
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
}
|
|
1569
|
-
]
|
|
1674
|
+
payouts: paymentMethods.map((processorName) => ({
|
|
1675
|
+
processorName,
|
|
1676
|
+
currency: receive.currency,
|
|
1677
|
+
payeeData: receive.payee
|
|
1678
|
+
}))
|
|
1570
1679
|
};
|
|
1571
1680
|
}
|
|
1572
1681
|
function validateDepositInput(amount, input, payoutInput = validatePayout(input)) {
|
|
@@ -1837,13 +1946,21 @@ function createCashClient(options) {
|
|
|
1837
1946
|
async relayStatus(requestId) {
|
|
1838
1947
|
return readRelayStatus(requestId, options.relay);
|
|
1839
1948
|
},
|
|
1840
|
-
async estimate(input) {
|
|
1949
|
+
async estimate(input, estimateOptions) {
|
|
1841
1950
|
return readEstimate(readClient.publicClient, input, {
|
|
1842
|
-
indexerClient: readClient,
|
|
1843
1951
|
environment,
|
|
1952
|
+
...estimateOptions?.includeEta !== void 0 ? { includeEta: estimateOptions.includeEta } : {},
|
|
1953
|
+
etaReader: async (etaInput) => fillEtaFromSample(await getFillStatsSample(), etaInput),
|
|
1844
1954
|
...options.relay ? { relay: options.relay } : {}
|
|
1845
1955
|
});
|
|
1846
1956
|
},
|
|
1957
|
+
async fillStats() {
|
|
1958
|
+
try {
|
|
1959
|
+
return (await getFillStatsSample()).stats;
|
|
1960
|
+
} catch (err) {
|
|
1961
|
+
throw errors.indexerUnavailable("fill stats", err);
|
|
1962
|
+
}
|
|
1963
|
+
},
|
|
1847
1964
|
async cashout(input, opts) {
|
|
1848
1965
|
const client = await signingClient("cashout", opts);
|
|
1849
1966
|
const owner = opts.signer.account.address;
|
|
@@ -2443,6 +2560,11 @@ var cashEstimateJsonSchema = zod.z.object({
|
|
|
2443
2560
|
label: zod.z.string()
|
|
2444
2561
|
}).optional()
|
|
2445
2562
|
});
|
|
2563
|
+
var cashPairFillStatsJsonSchema = zod.z.object({
|
|
2564
|
+
fills: zod.z.number().int().nonnegative(),
|
|
2565
|
+
medianFillSeconds: zod.z.number().int().nonnegative().optional()
|
|
2566
|
+
}).strict();
|
|
2567
|
+
var cashFillStatsJsonSchema = zod.z.record(zod.z.string(), cashPairFillStatsJsonSchema);
|
|
2446
2568
|
var preparedTransactionJsonSchema = zod.z.object({
|
|
2447
2569
|
to: zod.z.string(),
|
|
2448
2570
|
data: zod.z.string(),
|
|
@@ -2747,6 +2869,21 @@ function estimateFromJson(json) {
|
|
|
2747
2869
|
} : void 0
|
|
2748
2870
|
});
|
|
2749
2871
|
}
|
|
2872
|
+
function fillStatsToJson(stats) {
|
|
2873
|
+
return cashFillStatsJsonSchema.parse(stats);
|
|
2874
|
+
}
|
|
2875
|
+
function fillStatsFromJson(json) {
|
|
2876
|
+
const parsed = cashFillStatsJsonSchema.parse(json);
|
|
2877
|
+
return Object.fromEntries(
|
|
2878
|
+
Object.entries(parsed).map(([pair, stats]) => [
|
|
2879
|
+
pair,
|
|
2880
|
+
{
|
|
2881
|
+
fills: stats.fills,
|
|
2882
|
+
...stats.medianFillSeconds !== void 0 ? { medianFillSeconds: stats.medianFillSeconds } : {}
|
|
2883
|
+
}
|
|
2884
|
+
])
|
|
2885
|
+
);
|
|
2886
|
+
}
|
|
2750
2887
|
function cashAssetFromJson(asset) {
|
|
2751
2888
|
return {
|
|
2752
2889
|
chainId: asset.chainId,
|
|
@@ -3055,9 +3192,11 @@ exports.cashErrorRecoveryJsonSchema = cashErrorRecoveryJsonSchema;
|
|
|
3055
3192
|
exports.cashErrorToJson = cashErrorToJson;
|
|
3056
3193
|
exports.cashEstimateJsonSchema = cashEstimateJsonSchema;
|
|
3057
3194
|
exports.cashFillJsonSchema = cashFillJsonSchema;
|
|
3195
|
+
exports.cashFillStatsJsonSchema = cashFillStatsJsonSchema;
|
|
3058
3196
|
exports.cashNextActionSchema = cashNextActionSchema;
|
|
3059
3197
|
exports.cashOrderJsonSchema = cashOrderJsonSchema;
|
|
3060
3198
|
exports.cashOrderStateSchema = cashOrderStateSchema;
|
|
3199
|
+
exports.cashPairFillStatsJsonSchema = cashPairFillStatsJsonSchema;
|
|
3061
3200
|
exports.cashPayoutInfoJsonSchema = cashPayoutInfoJsonSchema;
|
|
3062
3201
|
exports.cashPayoutPricingJsonSchema = cashPayoutPricingJsonSchema;
|
|
3063
3202
|
exports.cashPreparedStepJsonSchema = cashPreparedStepJsonSchema;
|
|
@@ -3077,6 +3216,8 @@ exports.explainOrder = explainOrder;
|
|
|
3077
3216
|
exports.fiatFromUsdc = fiatFromUsdc;
|
|
3078
3217
|
exports.fiatToNumber = fiatToNumber;
|
|
3079
3218
|
exports.fillFromJson = fillFromJson;
|
|
3219
|
+
exports.fillStatsFromJson = fillStatsFromJson;
|
|
3220
|
+
exports.fillStatsToJson = fillStatsToJson;
|
|
3080
3221
|
exports.fillToJson = fillToJson;
|
|
3081
3222
|
exports.formatUsdc = formatUsdc;
|
|
3082
3223
|
exports.intentStatusSchema = intentStatusSchema;
|