@zkp2p/cash 0.1.6 → 0.1.8
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 +19 -5
- package/README.md +23 -3
- package/dist/{chunk-P3KYZ2FX.js → chunk-TR6JVYYF.js} +6 -0
- package/dist/{createCashClient-jUA_GNdh.d.cts → createCashClient-BIzOLHjF.d.cts} +16 -1
- package/dist/{createCashClient-jUA_GNdh.d.ts → createCashClient-BIzOLHjF.d.ts} +16 -1
- package/dist/index.cjs +179 -48
- package/dist/index.d.cts +33 -9
- package/dist/index.d.ts +33 -9
- package/dist/index.js +172 -51
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- 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 +32 -6
- package/llms.txt +7 -3
- package/package.json +4 -2
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
|
+
// Optional: raw demand + speed evidence per offered platform:currency pair.
|
|
58
|
+
const stats = await cash.fillStats();
|
|
59
|
+
|
|
57
60
|
// 3. Execute.
|
|
58
61
|
const { depositId } = await cash.cashout(
|
|
59
62
|
{
|
|
@@ -101,19 +104,28 @@ console.log(routed.source?.transactions?.origin, routed.source?.transactions?.de
|
|
|
101
104
|
binding rate resolves at the oracle when a buyer fills. Do not display or
|
|
102
105
|
log it as a locked price.
|
|
103
106
|
- **Do not invent an ETA.** Use `estimate().eta`: `{ seconds, label }` backed
|
|
104
|
-
by rolling 30-day
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
by the same rolling 30-day, intent-attributed pair sample as `fillStats()`,
|
|
108
|
+
measured from deposit creation to first fill. Use `order.explain()` for live
|
|
109
|
+
order state.
|
|
107
110
|
- **Do not hardcode Relay source assets.** Use Relay SDK-backed EVM
|
|
108
111
|
`capabilities({ includeRelaySources: true })` and `cashout({ source, ... })`.
|
|
109
112
|
Destination is always Base USDC. Non-Base source chains require
|
|
110
113
|
`sourceSigner`. Use `EXACT_INPUT` in high-level cash-out flows so `amount`
|
|
111
114
|
remains source-token base units. `source.amount` is Relay's guaranteed
|
|
112
115
|
minimum output and the exact Base USDC deposit amount.
|
|
116
|
+
- **Use a nonce-managed source signer for routed cashouts.** Relay routes
|
|
117
|
+
with more than one source-chain transaction (approve, then route) are
|
|
118
|
+
refused preflight with `SOURCE_NONCE_MANAGER_REQUIRED` on plain local
|
|
119
|
+
accounts - create the source signer with
|
|
120
|
+
`privateKeyToAccount(pk, { nonceManager })`. Browser (`json-rpc`) wallets
|
|
121
|
+
are unaffected.
|
|
113
122
|
- **Persist source evidence.** A routed result carries `requestId`, flat
|
|
114
123
|
`txHashes`, and chain-aware `transactions.origin` / `.destination` arrays.
|
|
115
124
|
- **Never repeat a completed or uncertain route.** On `SOURCE_EXECUTION_FAILED`,
|
|
116
|
-
inspect its Relay request and transaction recovery evidence.
|
|
125
|
+
inspect its Relay request and transaction recovery evidence. A failed
|
|
126
|
+
approval-only route can sit in `relayStatus` `waiting` indefinitely - decide
|
|
127
|
+
from the recovery payload and origin transactions, never by waiting for a
|
|
128
|
+
terminal Relay status. On
|
|
117
129
|
`SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED`, retry without `source` using
|
|
118
130
|
`BigInt(err.recovery.amount)`. On `SOURCE_CASHOUT_SUBMISSION_UNKNOWN`, inspect
|
|
119
131
|
Base wallet activity and `orders(err.recovery.depositor)`. On
|
|
@@ -128,7 +140,8 @@ console.log(routed.source?.transactions?.origin, routed.source?.transactions?.de
|
|
|
128
140
|
deposit. The tx receipt you hold is the truth. Retry; `watch()` absorbs
|
|
129
141
|
this automatically.
|
|
130
142
|
- **Unwind with `withdraw()` only.** It is state-aware (prunes expired
|
|
131
|
-
intents first). Do not call escrow functions directly.
|
|
143
|
+
intents first). Do not call escrow functions directly. Partial withdrawals
|
|
144
|
+
increment `returnedAmount`; `totalAmount` is historical and never shrinks.
|
|
132
145
|
- **Read fills as receipts.** `fiatOwed` is the buyer's obligation at the
|
|
133
146
|
locked rate; after the proof, `fiatPaid`/`paymentId`/`releasedAmount` are
|
|
134
147
|
the verified outcome. Reconcile against those, not your own math.
|
|
@@ -161,6 +174,7 @@ Every `CashError` carries `code`, `retryable`, `remediation`. Behavior:
|
|
|
161
174
|
| `SOURCE_RECIPIENT_MISMATCH` | no | Route Base USDC to the cashout depositor |
|
|
162
175
|
| `SOURCE_CAPABILITIES_FAILED` | yes | Retry discovery or fall back to Base USDC |
|
|
163
176
|
| `SOURCE_QUOTE_FAILED` | yes | Refresh capabilities and request a new canonical Base-USDC quote |
|
|
177
|
+
| `SOURCE_NONCE_MANAGER_REQUIRED` | no | Preflight; recreate the source signer with viem's `nonceManager`, then quote again |
|
|
164
178
|
| `SOURCE_EXECUTION_FAILED` | no | Inspect source transactions and Relay status before any retry |
|
|
165
179
|
| `SOURCE_STATUS_FAILED` | yes | Retry only the status read |
|
|
166
180
|
| `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED` | no | Do not route again; retry Base-only with `recovery.amount` |
|
package/README.md
CHANGED
|
@@ -76,6 +76,7 @@ console.log(source?.transactions?.origin, source?.transactions?.destination);
|
|
|
76
76
|
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
77
77
|
| `capabilities()` | Sync discovery: Base USDC destination/default source, platforms × currencies × payee hints × amount bounds |
|
|
78
78
|
| `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 |
|
|
79
80
|
| `quoteSource(input)` / `executeSourceQuote(quote, { signer })` | Relay SDK EVM source routing into Base USDC before cashout |
|
|
80
81
|
| `relayStatus(requestId)` | Relay request status from the Relay SDK request path |
|
|
81
82
|
| `estimate({ amount, currency })` | Base USDC oracle estimate plus simple recent-fill ETA |
|
|
@@ -97,6 +98,11 @@ must execute and confirm its Relay route before preparing the Base-USDC
|
|
|
97
98
|
cashout. Every Peer Cash transaction, including approves, carries ERC-8021
|
|
98
99
|
attribution: `peer-cash` first, your own `referrer` code(s) after it.
|
|
99
100
|
|
|
101
|
+
`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.
|
|
105
|
+
|
|
100
106
|
The default/minimal flow is unchanged: pass Base USDC base units to
|
|
101
107
|
`estimate()` and `cashout()`. For any other source asset, pass `source` to
|
|
102
108
|
`cashout()` with a source-chain signer. The SDK settles the Base allowance,
|
|
@@ -107,6 +113,13 @@ The destination is always canonical Base USDC
|
|
|
107
113
|
discovered and quoted by `@relayprotocol/relay-sdk`, not a static token
|
|
108
114
|
allowlist.
|
|
109
115
|
|
|
116
|
+
Routes that submit more than one source-chain transaction (approve, then
|
|
117
|
+
route) require a nonce-managed source signer -
|
|
118
|
+
`privateKeyToAccount(pk, { nonceManager })` from viem. Without one the SDK
|
|
119
|
+
refuses the route preflight with `SOURCE_NONCE_MANAGER_REQUIRED` instead of
|
|
120
|
+
letting the route transaction reuse the approval's nonce and revert
|
|
121
|
+
mid-route. Browser wallets are unaffected.
|
|
122
|
+
|
|
110
123
|
`capabilities()` tells you which platforms need a verified identity for a new
|
|
111
124
|
payee registration (`requiresIdentityAttestation` - Wise and PayPal today).
|
|
112
125
|
An already-registered Wise or PayPal handle can be reused with bare payee data.
|
|
@@ -120,6 +133,10 @@ they are available. A source-routed result includes both a flat
|
|
|
120
133
|
`source.txHashes` list and chain-aware `source.transactions.origin` /
|
|
121
134
|
`.destination` entries.
|
|
122
135
|
|
|
136
|
+
- `SOURCE_EXECUTION_FAILED` where only the approval landed: the Relay request
|
|
137
|
+
can stay in `relayStatus` `waiting` indefinitely. Decide from the error's
|
|
138
|
+
recovery payload and origin transactions, never by waiting for a terminal
|
|
139
|
+
Relay status.
|
|
123
140
|
- `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED`: Relay completed, but the Base
|
|
124
141
|
cashout was not created. Do not route again. Retry a Base-USDC-only
|
|
125
142
|
`cashout()` with `BigInt(error.recovery.amount)`.
|
|
@@ -161,9 +178,12 @@ awaiting-buyer ──────────► matched ───────
|
|
|
161
178
|
fills. `estimate()` says "approximately"; nothing in this API pretends to
|
|
162
179
|
lock a price.
|
|
163
180
|
- **ETA is historical.** `estimate().eta` is just `{ seconds, label }`, backed
|
|
164
|
-
by rolling 30-day
|
|
165
|
-
|
|
166
|
-
|
|
181
|
+
by the same rolling 30-day, intent-attributed pair sampler as `fillStats()`,
|
|
182
|
+
measured from deposit creation to the first fulfilled fill through the pair.
|
|
183
|
+
- **Availability thresholds belong to the consumer.** `fillStats()` returns raw
|
|
184
|
+
evidence. A recommended gate is `fills >= 10 && medianFillSeconds <= 48h`.
|
|
185
|
+
Fail open to the full `capabilities()` catalog when stats are unavailable or
|
|
186
|
+
the gate would empty the offered catalog.
|
|
167
187
|
- **Everything is resumable.** An order is reconstructed from the chain by
|
|
168
188
|
`depositId` alone. Close the tab, switch devices, crash the process - then
|
|
169
189
|
call `order(depositId)`.
|
|
@@ -189,6 +189,12 @@ var errors = {
|
|
|
189
189
|
},
|
|
190
190
|
{ cause }
|
|
191
191
|
),
|
|
192
|
+
sourceNonceManagerRequired: (transactionCount) => new CashError({
|
|
193
|
+
code: "SOURCE_NONCE_MANAGER_REQUIRED",
|
|
194
|
+
message: `This Relay route submits ${transactionCount} source-chain transactions, but the local signer has no nonce manager; the route transaction would reuse the approval nonce and revert.`,
|
|
195
|
+
retryable: false,
|
|
196
|
+
remediation: `Create the source signer with viem's nonce manager - privateKeyToAccount(pk, { nonceManager }) - then request a fresh quote and retry. No transaction was submitted.`
|
|
197
|
+
}),
|
|
192
198
|
sourceExecutionFailed: (cause, evidence) => new CashError(
|
|
193
199
|
{
|
|
194
200
|
code: "SOURCE_EXECUTION_FAILED",
|
|
@@ -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;
|
|
@@ -581,6 +589,13 @@ interface CashClient {
|
|
|
581
589
|
capabilities(options: {
|
|
582
590
|
includeRelaySources: true;
|
|
583
591
|
}): Promise<CashCapabilities>;
|
|
592
|
+
/**
|
|
593
|
+
* 0c - Raw 30-day demand and first-fill speed evidence keyed by
|
|
594
|
+
* `platform:currency`. A recommended consumer gate is `fills >= 10 &&
|
|
595
|
+
* medianFillSeconds <= 48h`; fail open to the full capability catalog when
|
|
596
|
+
* stats are unavailable or the gate would remove every pair.
|
|
597
|
+
*/
|
|
598
|
+
fillStats(): Promise<CashFillStats>;
|
|
584
599
|
/** Relay-only source discovery helper. */
|
|
585
600
|
sourceCapabilities(): Promise<CashSourceCapabilities>;
|
|
586
601
|
/** Quote any Relay-supported EVM source asset into Base USDC. */
|
|
@@ -640,4 +655,4 @@ interface CashClient {
|
|
|
640
655
|
}
|
|
641
656
|
declare function createCashClient(options: CashClientOptions): CashClient;
|
|
642
657
|
|
|
643
|
-
export { type
|
|
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 };
|
|
@@ -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;
|
|
@@ -581,6 +589,13 @@ interface CashClient {
|
|
|
581
589
|
capabilities(options: {
|
|
582
590
|
includeRelaySources: true;
|
|
583
591
|
}): Promise<CashCapabilities>;
|
|
592
|
+
/**
|
|
593
|
+
* 0c - Raw 30-day demand and first-fill speed evidence keyed by
|
|
594
|
+
* `platform:currency`. A recommended consumer gate is `fills >= 10 &&
|
|
595
|
+
* medianFillSeconds <= 48h`; fail open to the full capability catalog when
|
|
596
|
+
* stats are unavailable or the gate would remove every pair.
|
|
597
|
+
*/
|
|
598
|
+
fillStats(): Promise<CashFillStats>;
|
|
584
599
|
/** Relay-only source discovery helper. */
|
|
585
600
|
sourceCapabilities(): Promise<CashSourceCapabilities>;
|
|
586
601
|
/** Quote any Relay-supported EVM source asset into Base USDC. */
|
|
@@ -640,4 +655,4 @@ interface CashClient {
|
|
|
640
655
|
}
|
|
641
656
|
declare function createCashClient(options: CashClientOptions): CashClient;
|
|
642
657
|
|
|
643
|
-
export { type
|
|
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 };
|
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
|
};
|
|
@@ -623,6 +650,12 @@ var errors = {
|
|
|
623
650
|
},
|
|
624
651
|
{ cause }
|
|
625
652
|
),
|
|
653
|
+
sourceNonceManagerRequired: (transactionCount) => new CashError({
|
|
654
|
+
code: "SOURCE_NONCE_MANAGER_REQUIRED",
|
|
655
|
+
message: `This Relay route submits ${transactionCount} source-chain transactions, but the local signer has no nonce manager; the route transaction would reuse the approval nonce and revert.`,
|
|
656
|
+
retryable: false,
|
|
657
|
+
remediation: `Create the source signer with viem's nonce manager - privateKeyToAccount(pk, { nonceManager }) - then request a fresh quote and retry. No transaction was submitted.`
|
|
658
|
+
}),
|
|
626
659
|
sourceExecutionFailed: (cause, evidence) => new CashError(
|
|
627
660
|
{
|
|
628
661
|
code: "SOURCE_EXECUTION_FAILED",
|
|
@@ -803,11 +836,8 @@ function mapChainError(verb, err, context = {}) {
|
|
|
803
836
|
}
|
|
804
837
|
return errors.chainCallFailed(verb, err);
|
|
805
838
|
}
|
|
806
|
-
var
|
|
807
|
-
var
|
|
808
|
-
var ETA_PAGE_LIMIT = 250;
|
|
809
|
-
var ETA_MAX_DEPOSIT_SCAN = 2e3;
|
|
810
|
-
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;
|
|
811
841
|
function toUnixSeconds2(value) {
|
|
812
842
|
if (value === null || value === void 0 || value === "") return void 0;
|
|
813
843
|
if (value instanceof Date) {
|
|
@@ -818,12 +848,22 @@ function toUnixSeconds2(value) {
|
|
|
818
848
|
const parsed = Date.parse(value);
|
|
819
849
|
if (Number.isFinite(parsed)) return Math.floor(parsed / 1e3);
|
|
820
850
|
}
|
|
821
|
-
const
|
|
822
|
-
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
|
+
}
|
|
823
863
|
}
|
|
824
864
|
function median(values) {
|
|
825
865
|
if (values.length === 0) return void 0;
|
|
826
|
-
const sorted = [...values].sort((
|
|
866
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
827
867
|
const mid = Math.floor(sorted.length / 2);
|
|
828
868
|
return sorted.length % 2 === 1 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
|
|
829
869
|
}
|
|
@@ -835,47 +875,97 @@ function etaLabel(seconds) {
|
|
|
835
875
|
const hours = Math.max(1, Math.round(minutes / 60));
|
|
836
876
|
return `Usually starts in about ${hours} hr`;
|
|
837
877
|
}
|
|
838
|
-
function
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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 };
|
|
847
937
|
}
|
|
848
|
-
async function
|
|
938
|
+
async function readFillStatsSample(client, environment) {
|
|
849
939
|
const now = Math.floor(Date.now() / 1e3);
|
|
850
|
-
const windowStart = now -
|
|
940
|
+
const windowStart = now - FILL_STATS_WINDOW_SECONDS;
|
|
851
941
|
const deposits = [];
|
|
852
|
-
for (let offset = 0;
|
|
942
|
+
for (let offset = 0; ; offset += FILL_STATS_PAGE_LIMIT) {
|
|
853
943
|
const page = await client.indexer.getDepositsWithRelations(
|
|
854
944
|
{ chainId: BASE_CHAIN_ID },
|
|
855
|
-
{
|
|
945
|
+
{
|
|
946
|
+
limit: FILL_STATS_PAGE_LIMIT,
|
|
947
|
+
offset,
|
|
948
|
+
orderBy: "updatedAt",
|
|
949
|
+
orderDirection: "desc"
|
|
950
|
+
},
|
|
856
951
|
{ includeIntents: true, intentStatuses: ["FULFILLED", "MANUALLY_RELEASED"] }
|
|
857
952
|
);
|
|
858
953
|
deposits.push(...page);
|
|
859
|
-
if (page.length <
|
|
860
|
-
const
|
|
861
|
-
...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)
|
|
862
957
|
);
|
|
863
|
-
if (
|
|
958
|
+
if (oldestUpdatedAt < windowStart) break;
|
|
864
959
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
).sort((a, b) => a.fulfilledAt - b.fulfilledAt);
|
|
875
|
-
if (fulfilled.length === 0) continue;
|
|
876
|
-
firstFillLatencies.push(fulfilled[0].fulfilledAt - createdAt);
|
|
877
|
-
}
|
|
878
|
-
const seconds = median(firstFillLatencies);
|
|
960
|
+
return computeFillStatsSample(deposits, now, environment);
|
|
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);
|
|
967
|
+
const currency = input.currency.toUpperCase();
|
|
968
|
+
const seconds = input.platform ? sample.stats[`${basePlatformForMethod(input.platform)}:${currency}`]?.medianFillSeconds : sample.medianFillSecondsByCurrency.get(currency);
|
|
879
969
|
return {
|
|
880
970
|
...seconds !== void 0 ? { seconds } : {},
|
|
881
971
|
label: etaLabel(seconds)
|
|
@@ -1048,6 +1138,14 @@ async function assertRelayExecutionIdentity(quote, wallet, expectedRecipient) {
|
|
|
1048
1138
|
throw new Error("Relay quote recipient does not match the expected Base recipient");
|
|
1049
1139
|
}
|
|
1050
1140
|
}
|
|
1141
|
+
function assertRelayNonceManagement(quote, wallet) {
|
|
1142
|
+
const account = wallet.account;
|
|
1143
|
+
if (!account || account.type !== "local" || account.nonceManager !== void 0) return;
|
|
1144
|
+
const transactionCount = quote.steps.flatMap(
|
|
1145
|
+
(step) => step.items.filter((item) => asString(asRecord(item.data).to) !== void 0)
|
|
1146
|
+
).length;
|
|
1147
|
+
if (transactionCount > 1) throw errors.sourceNonceManagerRequired(transactionCount);
|
|
1148
|
+
}
|
|
1051
1149
|
function isRelaySecretKey(key) {
|
|
1052
1150
|
const normalized = key.toLowerCase();
|
|
1053
1151
|
return normalized === "headers" || normalized === "apikey";
|
|
@@ -1249,6 +1347,7 @@ async function executeRelayQuote(quote, wallet, options = {}) {
|
|
|
1249
1347
|
observedRequestId = quoteRequestId(rawQuote);
|
|
1250
1348
|
assertCanonicalRelayDestination(rawQuote);
|
|
1251
1349
|
await assertRelayExecutionIdentity(rawQuote, wallet, options.recipient);
|
|
1350
|
+
assertRelayNonceManagement(rawQuote, wallet);
|
|
1252
1351
|
const client = relayClient(options.relay);
|
|
1253
1352
|
const sourceChainId = quoteSourceChainId(rawQuote);
|
|
1254
1353
|
if (sourceChainId !== void 0 && !(client.chains ?? []).some((chain) => chain.id === sourceChainId)) {
|
|
@@ -1534,6 +1633,7 @@ function createCashClient(options) {
|
|
|
1534
1633
|
}
|
|
1535
1634
|
function validatePayout(input) {
|
|
1536
1635
|
const { receive } = input;
|
|
1636
|
+
const catalog = sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
|
|
1537
1637
|
const platform = buildCapabilities(environment).platforms.find(
|
|
1538
1638
|
(capability) => capability.platform === receive.platform
|
|
1539
1639
|
);
|
|
@@ -1544,14 +1644,13 @@ function createCashClient(options) {
|
|
|
1544
1644
|
if (!platform.currencies.includes(receive.currency)) {
|
|
1545
1645
|
throw errors.unsupportedPlatformCurrency(receive.platform, receive.currency);
|
|
1546
1646
|
}
|
|
1647
|
+
const paymentMethods = paymentMethodsForPlatform(receive.platform, catalog);
|
|
1547
1648
|
return {
|
|
1548
|
-
payouts:
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
}
|
|
1554
|
-
]
|
|
1649
|
+
payouts: paymentMethods.map((processorName) => ({
|
|
1650
|
+
processorName,
|
|
1651
|
+
currency: receive.currency,
|
|
1652
|
+
payeeData: receive.payee
|
|
1653
|
+
}))
|
|
1555
1654
|
};
|
|
1556
1655
|
}
|
|
1557
1656
|
function validateDepositInput(amount, input, payoutInput = validatePayout(input)) {
|
|
@@ -1829,6 +1928,13 @@ function createCashClient(options) {
|
|
|
1829
1928
|
...options.relay ? { relay: options.relay } : {}
|
|
1830
1929
|
});
|
|
1831
1930
|
},
|
|
1931
|
+
async fillStats() {
|
|
1932
|
+
try {
|
|
1933
|
+
return await readFillStats(readClient, environment);
|
|
1934
|
+
} catch (err) {
|
|
1935
|
+
throw errors.indexerUnavailable("fill stats", err);
|
|
1936
|
+
}
|
|
1937
|
+
},
|
|
1832
1938
|
async cashout(input, opts) {
|
|
1833
1939
|
const client = await signingClient("cashout", opts);
|
|
1834
1940
|
const owner = opts.signer.account.address;
|
|
@@ -2428,6 +2534,11 @@ var cashEstimateJsonSchema = zod.z.object({
|
|
|
2428
2534
|
label: zod.z.string()
|
|
2429
2535
|
}).optional()
|
|
2430
2536
|
});
|
|
2537
|
+
var cashPairFillStatsJsonSchema = zod.z.object({
|
|
2538
|
+
fills: zod.z.number().int().nonnegative(),
|
|
2539
|
+
medianFillSeconds: zod.z.number().int().nonnegative().optional()
|
|
2540
|
+
}).strict();
|
|
2541
|
+
var cashFillStatsJsonSchema = zod.z.record(zod.z.string(), cashPairFillStatsJsonSchema);
|
|
2431
2542
|
var preparedTransactionJsonSchema = zod.z.object({
|
|
2432
2543
|
to: zod.z.string(),
|
|
2433
2544
|
data: zod.z.string(),
|
|
@@ -2559,6 +2670,7 @@ var CASH_ERROR_CODES = defineCashErrorCodes([
|
|
|
2559
2670
|
"SOURCE_RECIPIENT_MISMATCH",
|
|
2560
2671
|
"SOURCE_CAPABILITIES_FAILED",
|
|
2561
2672
|
"SOURCE_QUOTE_FAILED",
|
|
2673
|
+
"SOURCE_NONCE_MANAGER_REQUIRED",
|
|
2562
2674
|
"SOURCE_EXECUTION_FAILED",
|
|
2563
2675
|
"SOURCE_STATUS_FAILED",
|
|
2564
2676
|
"SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED",
|
|
@@ -2731,6 +2843,21 @@ function estimateFromJson(json) {
|
|
|
2731
2843
|
} : void 0
|
|
2732
2844
|
});
|
|
2733
2845
|
}
|
|
2846
|
+
function fillStatsToJson(stats) {
|
|
2847
|
+
return cashFillStatsJsonSchema.parse(stats);
|
|
2848
|
+
}
|
|
2849
|
+
function fillStatsFromJson(json) {
|
|
2850
|
+
const parsed = cashFillStatsJsonSchema.parse(json);
|
|
2851
|
+
return Object.fromEntries(
|
|
2852
|
+
Object.entries(parsed).map(([pair, stats]) => [
|
|
2853
|
+
pair,
|
|
2854
|
+
{
|
|
2855
|
+
fills: stats.fills,
|
|
2856
|
+
...stats.medianFillSeconds !== void 0 ? { medianFillSeconds: stats.medianFillSeconds } : {}
|
|
2857
|
+
}
|
|
2858
|
+
])
|
|
2859
|
+
);
|
|
2860
|
+
}
|
|
2734
2861
|
function cashAssetFromJson(asset) {
|
|
2735
2862
|
return {
|
|
2736
2863
|
chainId: asset.chainId,
|
|
@@ -3039,9 +3166,11 @@ exports.cashErrorRecoveryJsonSchema = cashErrorRecoveryJsonSchema;
|
|
|
3039
3166
|
exports.cashErrorToJson = cashErrorToJson;
|
|
3040
3167
|
exports.cashEstimateJsonSchema = cashEstimateJsonSchema;
|
|
3041
3168
|
exports.cashFillJsonSchema = cashFillJsonSchema;
|
|
3169
|
+
exports.cashFillStatsJsonSchema = cashFillStatsJsonSchema;
|
|
3042
3170
|
exports.cashNextActionSchema = cashNextActionSchema;
|
|
3043
3171
|
exports.cashOrderJsonSchema = cashOrderJsonSchema;
|
|
3044
3172
|
exports.cashOrderStateSchema = cashOrderStateSchema;
|
|
3173
|
+
exports.cashPairFillStatsJsonSchema = cashPairFillStatsJsonSchema;
|
|
3045
3174
|
exports.cashPayoutInfoJsonSchema = cashPayoutInfoJsonSchema;
|
|
3046
3175
|
exports.cashPayoutPricingJsonSchema = cashPayoutPricingJsonSchema;
|
|
3047
3176
|
exports.cashPreparedStepJsonSchema = cashPreparedStepJsonSchema;
|
|
@@ -3061,6 +3190,8 @@ exports.explainOrder = explainOrder;
|
|
|
3061
3190
|
exports.fiatFromUsdc = fiatFromUsdc;
|
|
3062
3191
|
exports.fiatToNumber = fiatToNumber;
|
|
3063
3192
|
exports.fillFromJson = fillFromJson;
|
|
3193
|
+
exports.fillStatsFromJson = fillStatsFromJson;
|
|
3194
|
+
exports.fillStatsToJson = fillStatsToJson;
|
|
3064
3195
|
exports.fillToJson = fillToJson;
|
|
3065
3196
|
exports.formatUsdc = formatUsdc;
|
|
3066
3197
|
exports.intentStatusSchema = intentStatusSchema;
|