@zkp2p/cash 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -1
- package/README.md +20 -5
- package/dist/{createCashClient-BIzOLHjF.d.cts → createCashClient-CTEXn9FF.d.cts} +12 -5
- package/dist/{createCashClient-BIzOLHjF.d.ts → createCashClient-CTEXn9FF.d.ts} +12 -5
- package/dist/index.cjs +70 -61
- package/dist/index.d.cts +22 -20
- package/dist/index.d.ts +22 -20
- package/dist/index.js +70 -61
- 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 +1 -1
- package/dist/tools.js +1 -1
- package/docs/lifecycle-and-recovery.md +12 -2
- package/llms.txt +9 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -287,35 +287,41 @@ function toPricing(tuple) {
|
|
|
287
287
|
};
|
|
288
288
|
}
|
|
289
289
|
function derivePayouts(paymentMethods, currencies, catalog) {
|
|
290
|
-
|
|
290
|
+
const payouts = [];
|
|
291
|
+
for (const method of paymentMethods) {
|
|
291
292
|
const platformHash = method.paymentMethodHash ?? "";
|
|
292
293
|
if (!platformHash) return [];
|
|
293
294
|
let platform;
|
|
294
295
|
try {
|
|
295
296
|
platform = resolvePaymentMethodNameFromHash(platformHash, catalog);
|
|
296
297
|
} catch {
|
|
297
|
-
|
|
298
|
+
return [];
|
|
298
299
|
}
|
|
300
|
+
if (!platform) return [];
|
|
299
301
|
const tuples = currencies.filter(
|
|
300
302
|
(c) => (c.paymentMethodHash ?? "").toLowerCase() === platformHash.toLowerCase()
|
|
301
303
|
);
|
|
302
304
|
const base2 = {
|
|
303
|
-
|
|
305
|
+
platform,
|
|
304
306
|
platformHash,
|
|
305
307
|
payeeHash: method.payeeDetailsHash ?? "",
|
|
306
308
|
active: method.active ?? true
|
|
307
309
|
};
|
|
308
|
-
if (tuples.length === 0)
|
|
309
|
-
|
|
310
|
+
if (tuples.length === 0) {
|
|
311
|
+
payouts.push({ ...base2, pricing: toPricing(void 0) });
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
for (const tuple of tuples) {
|
|
310
315
|
const currency = tuple.currencyCode != null ? getCurrencyCodeFromHash(tuple.currencyCode) : void 0;
|
|
311
|
-
|
|
316
|
+
payouts.push({
|
|
312
317
|
...base2,
|
|
313
318
|
...currency !== void 0 ? { currency } : {},
|
|
314
319
|
...tuple.currencyCode != null ? { currencyHash: tuple.currencyCode } : {},
|
|
315
320
|
pricing: toPricing(tuple)
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return payouts;
|
|
319
325
|
}
|
|
320
326
|
|
|
321
327
|
// src/engine/buyerProfile.ts
|
|
@@ -387,26 +393,6 @@ function parseCompositeDepositId(compositeId) {
|
|
|
387
393
|
const onchainDepositId = BigInt(rawDepositId);
|
|
388
394
|
return { escrowAddress: canonicalEscrowAddress, onchainDepositId };
|
|
389
395
|
}
|
|
390
|
-
|
|
391
|
-
// src/client/platformGroups.ts
|
|
392
|
-
var PLATFORM_METHOD_GROUPS = {
|
|
393
|
-
zelle: ["zelle", "zelle-chase", "zelle-bofa", "zelle-citi"]
|
|
394
|
-
};
|
|
395
|
-
var METHOD_TO_BASE_PLATFORM = new Map(
|
|
396
|
-
Object.entries(PLATFORM_METHOD_GROUPS).flatMap(
|
|
397
|
-
([platform, methods]) => methods.map((method) => [method, platform])
|
|
398
|
-
)
|
|
399
|
-
);
|
|
400
|
-
function basePlatformForMethod(method) {
|
|
401
|
-
return METHOD_TO_BASE_PLATFORM.get(method) ?? method;
|
|
402
|
-
}
|
|
403
|
-
function paymentMethodsForPlatform(platform, catalog) {
|
|
404
|
-
const configured = PLATFORM_METHOD_GROUPS[platform];
|
|
405
|
-
const methods = configured ?? [platform];
|
|
406
|
-
return methods.filter((method) => catalog[method] !== void 0);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// src/client/capabilities.ts
|
|
410
396
|
var MIN_CASHOUT_AMOUNT = 10000n;
|
|
411
397
|
var RECOMMENDED_MIN_CASHOUT_AMOUNT = 1000000n;
|
|
412
398
|
var PAYEE_HINTS = {
|
|
@@ -425,20 +411,13 @@ var PAYEE_HINTS = {
|
|
|
425
411
|
var IDENTITY_ATTESTATION_PLATFORMS = /* @__PURE__ */ new Set(["wise", "paypal"]);
|
|
426
412
|
function buildCapabilities(environment) {
|
|
427
413
|
const catalog = getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
|
|
428
|
-
const
|
|
429
|
-
for (const [method, entry] of Object.entries(catalog)) {
|
|
430
|
-
const platform = basePlatformForMethod(method);
|
|
414
|
+
const platforms = Object.entries(catalog).map(([platform, entry]) => {
|
|
431
415
|
const currencies2 = (entry.currencies ?? []).map((hash) => getCurrencyCodeFromHash(hash)).filter(
|
|
432
416
|
(code) => code != null && isMarketRateSupported(code)
|
|
433
417
|
);
|
|
434
|
-
const aggregate = currenciesByPlatform.get(platform) ?? /* @__PURE__ */ new Set();
|
|
435
|
-
for (const currency of currencies2) aggregate.add(currency);
|
|
436
|
-
currenciesByPlatform.set(platform, aggregate);
|
|
437
|
-
}
|
|
438
|
-
const platforms = [...currenciesByPlatform.entries()].map(([platform, currencies2]) => {
|
|
439
418
|
return {
|
|
440
419
|
platform,
|
|
441
|
-
currencies: [...currencies2].sort(),
|
|
420
|
+
currencies: [...new Set(currencies2)].sort(),
|
|
442
421
|
payeeHint: PAYEE_HINTS[platform] ?? "Your payment handle for this platform",
|
|
443
422
|
requiresIdentityAttestation: IDENTITY_ATTESTATION_PLATFORMS.has(platform)
|
|
444
423
|
};
|
|
@@ -517,7 +496,7 @@ function computeFillStatsSample(deposits, nowSeconds, environment) {
|
|
|
517
496
|
}
|
|
518
497
|
const currency = normalizeCurrencyCode(intent.fiatCurrency);
|
|
519
498
|
if (!method || !currency) continue;
|
|
520
|
-
const pair = `${
|
|
499
|
+
const pair = `${method}:${currency}`;
|
|
521
500
|
fillCounts.set(pair, (fillCounts.get(pair) ?? 0) + 1);
|
|
522
501
|
if (createdAt === void 0 || createdAt < windowStart || fulfilledAt < createdAt) continue;
|
|
523
502
|
const previousPairFill = firstFillByPair.get(pair);
|
|
@@ -580,18 +559,18 @@ async function readFillStatsSample(client, environment) {
|
|
|
580
559
|
}
|
|
581
560
|
return computeFillStatsSample(deposits, now, environment);
|
|
582
561
|
}
|
|
583
|
-
|
|
584
|
-
return (await readFillStatsSample(client, environment)).stats;
|
|
585
|
-
}
|
|
586
|
-
async function readFillEta(client, input) {
|
|
587
|
-
const sample = await readFillStatsSample(client, input.environment);
|
|
562
|
+
function fillEtaFromSample(sample, input) {
|
|
588
563
|
const currency = input.currency.toUpperCase();
|
|
589
|
-
const seconds = input.platform ? sample.stats[`${
|
|
564
|
+
const seconds = input.platform ? sample.stats[`${input.platform}:${currency}`]?.medianFillSeconds : sample.medianFillSecondsByCurrency.get(currency);
|
|
590
565
|
return {
|
|
591
566
|
...seconds !== void 0 ? { seconds } : {},
|
|
592
567
|
label: etaLabel(seconds)
|
|
593
568
|
};
|
|
594
569
|
}
|
|
570
|
+
async function readFillEta(client, input) {
|
|
571
|
+
const sample = await readFillStatsSample(client, input.environment);
|
|
572
|
+
return fillEtaFromSample(sample, input);
|
|
573
|
+
}
|
|
595
574
|
var RELAY_API_URL = MAINNET_RELAY_API;
|
|
596
575
|
var NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
597
576
|
var BASE_USDC_ASSET = {
|
|
@@ -1127,13 +1106,18 @@ async function readEstimate(publicClient, input, context = {}) {
|
|
|
1127
1106
|
}
|
|
1128
1107
|
} : {}
|
|
1129
1108
|
};
|
|
1130
|
-
if (context.
|
|
1109
|
+
if (context.includeEta !== false && context.environment) {
|
|
1131
1110
|
try {
|
|
1132
|
-
|
|
1111
|
+
const etaInput = {
|
|
1133
1112
|
environment: context.environment,
|
|
1134
1113
|
currency,
|
|
1135
1114
|
...input.platform ? { platform: input.platform } : {}
|
|
1136
|
-
}
|
|
1115
|
+
};
|
|
1116
|
+
if (context.etaReader) {
|
|
1117
|
+
estimate.eta = await context.etaReader(etaInput);
|
|
1118
|
+
} else if (context.indexerClient) {
|
|
1119
|
+
estimate.eta = await readFillEta(context.indexerClient, etaInput);
|
|
1120
|
+
}
|
|
1137
1121
|
} catch {
|
|
1138
1122
|
}
|
|
1139
1123
|
}
|
|
@@ -1142,6 +1126,7 @@ async function readEstimate(publicClient, input, context = {}) {
|
|
|
1142
1126
|
|
|
1143
1127
|
// src/client/createCashClient.ts
|
|
1144
1128
|
var DEFAULT_RPC_URL = "https://mainnet.base.org";
|
|
1129
|
+
var FILL_STATS_CACHE_MS = 15 * 60 * 1e3;
|
|
1145
1130
|
var CASH_ATTRIBUTION_CODE = "peer-cash";
|
|
1146
1131
|
var DEFAULT_CURATOR_URLS = {
|
|
1147
1132
|
preproduction: "https://api-preprod.zkp2p.xyz",
|
|
@@ -1240,6 +1225,25 @@ function createCashClient(options) {
|
|
|
1240
1225
|
});
|
|
1241
1226
|
}
|
|
1242
1227
|
const readClient = buildSdkClient(createWalletClient({ chain: base, transport }));
|
|
1228
|
+
let fillStatsCache = null;
|
|
1229
|
+
let fillStatsRequest = null;
|
|
1230
|
+
async function getFillStatsSample() {
|
|
1231
|
+
if (fillStatsCache && fillStatsCache.expiresAt > Date.now()) {
|
|
1232
|
+
return fillStatsCache.sample;
|
|
1233
|
+
}
|
|
1234
|
+
if (fillStatsRequest) return fillStatsRequest;
|
|
1235
|
+
fillStatsRequest = readFillStatsSample(readClient, environment);
|
|
1236
|
+
try {
|
|
1237
|
+
const sample = await fillStatsRequest;
|
|
1238
|
+
fillStatsCache = {
|
|
1239
|
+
sample,
|
|
1240
|
+
expiresAt: Date.now() + FILL_STATS_CACHE_MS
|
|
1241
|
+
};
|
|
1242
|
+
return sample;
|
|
1243
|
+
} finally {
|
|
1244
|
+
fillStatsRequest = null;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1243
1247
|
const signingClients = /* @__PURE__ */ new WeakMap();
|
|
1244
1248
|
async function signingClient(verb, opts) {
|
|
1245
1249
|
const signer = opts?.signer;
|
|
@@ -1254,7 +1258,6 @@ function createCashClient(options) {
|
|
|
1254
1258
|
}
|
|
1255
1259
|
function validatePayout(input) {
|
|
1256
1260
|
const { receive } = input;
|
|
1257
|
-
const catalog = getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
|
|
1258
1261
|
const platform = buildCapabilities(environment).platforms.find(
|
|
1259
1262
|
(capability) => capability.platform === receive.platform
|
|
1260
1263
|
);
|
|
@@ -1265,13 +1268,14 @@ function createCashClient(options) {
|
|
|
1265
1268
|
if (!platform.currencies.includes(receive.currency)) {
|
|
1266
1269
|
throw errors.unsupportedPlatformCurrency(receive.platform, receive.currency);
|
|
1267
1270
|
}
|
|
1268
|
-
const paymentMethods = paymentMethodsForPlatform(receive.platform, catalog);
|
|
1269
1271
|
return {
|
|
1270
|
-
payouts:
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1272
|
+
payouts: [
|
|
1273
|
+
{
|
|
1274
|
+
processorName: receive.platform,
|
|
1275
|
+
currency: receive.currency,
|
|
1276
|
+
payeeData: receive.payee
|
|
1277
|
+
}
|
|
1278
|
+
]
|
|
1275
1279
|
};
|
|
1276
1280
|
}
|
|
1277
1281
|
function validateDepositInput(amount, input, payoutInput = validatePayout(input)) {
|
|
@@ -1288,6 +1292,9 @@ function createCashClient(options) {
|
|
|
1288
1292
|
...range ? { intentAmountRange: range } : {}
|
|
1289
1293
|
};
|
|
1290
1294
|
}
|
|
1295
|
+
function isCashPayoutSet(payouts) {
|
|
1296
|
+
return payouts.length === 1 && payouts.every((payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0);
|
|
1297
|
+
}
|
|
1291
1298
|
async function buildDepositParams(client, depositInput) {
|
|
1292
1299
|
try {
|
|
1293
1300
|
return await prepareCashDepositParams(client, depositInput);
|
|
@@ -1342,9 +1349,10 @@ function createCashClient(options) {
|
|
|
1342
1349
|
deposit.currencies ?? [],
|
|
1343
1350
|
getPaymentMethodsCatalog(BASE_CHAIN_ID, environment)
|
|
1344
1351
|
);
|
|
1352
|
+
if (!isCashPayoutSet(payouts)) throw errors.orderNotFound(compositeId);
|
|
1345
1353
|
return deriveCashOrder(compositeId, deposit.intents ?? [], {
|
|
1346
1354
|
...depositOrderOptions(deposit),
|
|
1347
|
-
|
|
1355
|
+
payouts
|
|
1348
1356
|
});
|
|
1349
1357
|
}
|
|
1350
1358
|
function escrowContext(depositId) {
|
|
@@ -1542,16 +1550,17 @@ function createCashClient(options) {
|
|
|
1542
1550
|
async relayStatus(requestId) {
|
|
1543
1551
|
return readRelayStatus(requestId, options.relay);
|
|
1544
1552
|
},
|
|
1545
|
-
async estimate(input) {
|
|
1553
|
+
async estimate(input, estimateOptions) {
|
|
1546
1554
|
return readEstimate(readClient.publicClient, input, {
|
|
1547
|
-
indexerClient: readClient,
|
|
1548
1555
|
environment,
|
|
1556
|
+
...estimateOptions?.includeEta !== void 0 ? { includeEta: estimateOptions.includeEta } : {},
|
|
1557
|
+
etaReader: async (etaInput) => fillEtaFromSample(await getFillStatsSample(), etaInput),
|
|
1549
1558
|
...options.relay ? { relay: options.relay } : {}
|
|
1550
1559
|
});
|
|
1551
1560
|
},
|
|
1552
1561
|
async fillStats() {
|
|
1553
1562
|
try {
|
|
1554
|
-
return await
|
|
1563
|
+
return (await getFillStatsSample()).stats;
|
|
1555
1564
|
} catch (err) {
|
|
1556
1565
|
throw errors.indexerUnavailable("fill stats", err);
|
|
1557
1566
|
}
|
|
@@ -1790,7 +1799,7 @@ function createCashClient(options) {
|
|
|
1790
1799
|
deposit.currencies ?? [],
|
|
1791
1800
|
catalog
|
|
1792
1801
|
);
|
|
1793
|
-
if (
|
|
1802
|
+
if (!isCashPayoutSet(payouts)) {
|
|
1794
1803
|
return [];
|
|
1795
1804
|
}
|
|
1796
1805
|
return [
|
|
@@ -2059,7 +2068,7 @@ var cashPayoutPricingJsonSchema = z.object({
|
|
|
2059
2068
|
marketRate: z.boolean()
|
|
2060
2069
|
});
|
|
2061
2070
|
var cashPayoutInfoJsonSchema = z.object({
|
|
2062
|
-
platform: z.string()
|
|
2071
|
+
platform: z.string(),
|
|
2063
2072
|
platformHash: z.string(),
|
|
2064
2073
|
currency: z.string().optional(),
|
|
2065
2074
|
currencyHash: z.string().optional(),
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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-
|
|
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-CTEXn9FF.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-
|
|
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-CTEXn9FF.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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
package/dist/tools.js
CHANGED
|
@@ -130,11 +130,16 @@ measure buyer signal to fulfillment; that would miss the buyer-arrival wait
|
|
|
130
130
|
that users actually care about. The public shape is small: `{ seconds, label }`.
|
|
131
131
|
|
|
132
132
|
`fillStats()` exposes the sampler's raw evidence for catalog filtering as
|
|
133
|
-
`Record<"platform:currency", { fills, medianFillSeconds? }>`.
|
|
134
|
-
|
|
133
|
+
`Record<"platform:currency", { fills, medianFillSeconds? }>`. Generic Zelle
|
|
134
|
+
fills are reported as `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.
|
|
@@ -190,6 +195,11 @@ platform, currency, payee hash, and a pricing proof (`spreadBps: 0`,
|
|
|
190
195
|
`kind: 'oracle_chainlink'`, `marketRate: true`): the zero-spread claim is a
|
|
191
196
|
queryable fact, not marketing copy.
|
|
192
197
|
|
|
198
|
+
Reconstruction is fail-closed: every payment method on the indexed deposit
|
|
199
|
+
must resolve through the active SDK catalog, and the result must be exactly
|
|
200
|
+
one zero-spread oracle payout. `orders()` excludes unsupported or mixed rows;
|
|
201
|
+
`order()` returns `ORDER_NOT_FOUND` rather than partially reclassifying them.
|
|
202
|
+
|
|
193
203
|
## Who is this buyer?
|
|
194
204
|
|
|
195
205
|
`buyer(address)` aggregates the matched buyer's full intent history into a
|
package/llms.txt
CHANGED
|
@@ -24,8 +24,13 @@ 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
|
-
|
|
28
|
-
|
|
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.
|
|
32
|
+
- capabilities() exposes one Zelle platform. A zelle cashout attaches only the
|
|
33
|
+
generic Zelle payment method to the on-chain deposit.
|
|
29
34
|
- Resume any order from its depositId alone (composite escrow_onchainId).
|
|
30
35
|
- One unwind verb: withdraw(depositId) - prunes expired intents automatically;
|
|
31
36
|
pass amount for a partial withdrawal of the unlocked balance.
|
|
@@ -51,6 +56,8 @@ Key facts:
|
|
|
51
56
|
payment id, released USDC, and fill latency after the proof.
|
|
52
57
|
- Orders carry their payout legs (platform, currency, payee hash) plus a
|
|
53
58
|
verifiable pricing proof (spreadBps: 0, oracle kind) from indexed data.
|
|
59
|
+
- Order reads fail closed when any deposit method is absent from the active
|
|
60
|
+
catalog; mixed historical deposits are never partially reclassified.
|
|
54
61
|
- buyer(address) aggregates a buyer's track record (fulfilled/pruned/success
|
|
55
62
|
rate) from their full intent history.
|
|
56
63
|
- Default path is same-chain Base USDC. Optional `source` on `cashout()` runs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zkp2p/cash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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)",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
},
|
|
107
107
|
"dependencies": {
|
|
108
108
|
"@relayprotocol/relay-sdk": "^6.1.3",
|
|
109
|
-
"@zkp2p/sdk": "^0.
|
|
109
|
+
"@zkp2p/sdk": "^0.9.0",
|
|
110
110
|
"zod": "^3.25.76"
|
|
111
111
|
},
|
|
112
112
|
"peerDependencies": {
|