@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/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, CASH_RETAIN_ON_EMPTY, BASE_USDC_ADDRESS, USDC_DECIMALS, BASE_CHAIN_ID, errors, isCashError, CASH_ORDER_STATUSES, mapChainError, CashError } from './chunk-
|
|
2
|
-
export { BASE_CHAIN_ID, BASE_USDC_ADDRESS, CASH_ORDER_POLL_INTERVAL_MS, CASH_ORDER_STATUSES, CASH_RETAIN_ON_EMPTY, CashError, MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, USDC_DECIMALS, errors, isCashError } from './chunk-
|
|
1
|
+
import { MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, CASH_RETAIN_ON_EMPTY, BASE_USDC_ADDRESS, USDC_DECIMALS, BASE_CHAIN_ID, errors, isCashError, CASH_ORDER_STATUSES, mapChainError, CashError } from './chunk-TR6JVYYF.js';
|
|
2
|
+
export { BASE_CHAIN_ID, BASE_USDC_ADDRESS, CASH_ORDER_POLL_INTERVAL_MS, CASH_ORDER_STATUSES, CASH_RETAIN_ON_EMPTY, CashError, MARKET_SPREAD_BPS, ORACLE_MIN_CONVERSION_RATE_SENTINEL, USDC_DECIMALS, errors, isCashError } from './chunk-TR6JVYYF.js';
|
|
3
3
|
import { parseAbi, parseEventLogs, isAddress, http, createWalletClient, encodeFunctionData } from 'viem';
|
|
4
4
|
import { base } from 'viem/chains';
|
|
5
5
|
import { getSpreadOracleConfig, currencyInfo, getPaymentMethodsCatalog, getGatingServiceAddress, resolvePaymentMethodHashFromCatalog, resolvePaymentMethodNameFromHash, getCurrencyCodeFromHash, createCompositeDepositId, appendAttributionToCalldata, Zkp2pClient, CHAINLINK_ORACLE_FEEDS } from '@zkp2p/sdk';
|
|
@@ -387,6 +387,26 @@ function parseCompositeDepositId(compositeId) {
|
|
|
387
387
|
const onchainDepositId = BigInt(rawDepositId);
|
|
388
388
|
return { escrowAddress: canonicalEscrowAddress, onchainDepositId };
|
|
389
389
|
}
|
|
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
|
|
390
410
|
var MIN_CASHOUT_AMOUNT = 10000n;
|
|
391
411
|
var RECOMMENDED_MIN_CASHOUT_AMOUNT = 1000000n;
|
|
392
412
|
var PAYEE_HINTS = {
|
|
@@ -405,13 +425,20 @@ var PAYEE_HINTS = {
|
|
|
405
425
|
var IDENTITY_ATTESTATION_PLATFORMS = /* @__PURE__ */ new Set(["wise", "paypal"]);
|
|
406
426
|
function buildCapabilities(environment) {
|
|
407
427
|
const catalog = getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
|
|
408
|
-
const
|
|
428
|
+
const currenciesByPlatform = /* @__PURE__ */ new Map();
|
|
429
|
+
for (const [method, entry] of Object.entries(catalog)) {
|
|
430
|
+
const platform = basePlatformForMethod(method);
|
|
409
431
|
const currencies2 = (entry.currencies ?? []).map((hash) => getCurrencyCodeFromHash(hash)).filter(
|
|
410
432
|
(code) => code != null && isMarketRateSupported(code)
|
|
411
433
|
);
|
|
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]) => {
|
|
412
439
|
return {
|
|
413
440
|
platform,
|
|
414
|
-
currencies: [...
|
|
441
|
+
currencies: [...currencies2].sort(),
|
|
415
442
|
payeeHint: PAYEE_HINTS[platform] ?? "Your payment handle for this platform",
|
|
416
443
|
requiresIdentityAttestation: IDENTITY_ATTESTATION_PLATFORMS.has(platform)
|
|
417
444
|
};
|
|
@@ -430,11 +457,8 @@ function buildCapabilities(environment) {
|
|
|
430
457
|
pricing: { kind: "oracle-market-rate", spreadBps: 0 }
|
|
431
458
|
};
|
|
432
459
|
}
|
|
433
|
-
var
|
|
434
|
-
var
|
|
435
|
-
var ETA_PAGE_LIMIT = 250;
|
|
436
|
-
var ETA_MAX_DEPOSIT_SCAN = 2e3;
|
|
437
|
-
var FULFILLED = /* @__PURE__ */ new Set(["FULFILLED", "MANUALLY_RELEASED"]);
|
|
460
|
+
var FILL_STATS_WINDOW_SECONDS = 30 * 24 * 60 * 60;
|
|
461
|
+
var FILL_STATS_PAGE_LIMIT = 250;
|
|
438
462
|
function toUnixSeconds2(value) {
|
|
439
463
|
if (value === null || value === void 0 || value === "") return void 0;
|
|
440
464
|
if (value instanceof Date) {
|
|
@@ -445,12 +469,22 @@ function toUnixSeconds2(value) {
|
|
|
445
469
|
const parsed = Date.parse(value);
|
|
446
470
|
if (Number.isFinite(parsed)) return Math.floor(parsed / 1e3);
|
|
447
471
|
}
|
|
448
|
-
const
|
|
449
|
-
return Number.isFinite(
|
|
472
|
+
const numeric = Number(value);
|
|
473
|
+
return Number.isFinite(numeric) && numeric > 0 ? numeric : void 0;
|
|
474
|
+
}
|
|
475
|
+
function normalizeCurrencyCode(value) {
|
|
476
|
+
const raw = value?.trim();
|
|
477
|
+
if (!raw) return void 0;
|
|
478
|
+
if (!raw.toLowerCase().startsWith("0x")) return raw.toUpperCase();
|
|
479
|
+
try {
|
|
480
|
+
return getCurrencyCodeFromHash(raw)?.toUpperCase();
|
|
481
|
+
} catch {
|
|
482
|
+
return void 0;
|
|
483
|
+
}
|
|
450
484
|
}
|
|
451
485
|
function median(values) {
|
|
452
486
|
if (values.length === 0) return void 0;
|
|
453
|
-
const sorted = [...values].sort((
|
|
487
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
454
488
|
const mid = Math.floor(sorted.length / 2);
|
|
455
489
|
return sorted.length % 2 === 1 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
|
|
456
490
|
}
|
|
@@ -462,47 +496,97 @@ function etaLabel(seconds) {
|
|
|
462
496
|
const hours = Math.max(1, Math.round(minutes / 60));
|
|
463
497
|
return `Usually starts in about ${hours} hr`;
|
|
464
498
|
}
|
|
465
|
-
function
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
499
|
+
function computeFillStatsSample(deposits, nowSeconds, environment) {
|
|
500
|
+
const catalog = getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
|
|
501
|
+
const windowStart = nowSeconds - FILL_STATS_WINDOW_SECONDS;
|
|
502
|
+
const fillCounts = /* @__PURE__ */ new Map();
|
|
503
|
+
const latenciesByPair = /* @__PURE__ */ new Map();
|
|
504
|
+
const latenciesByCurrency = /* @__PURE__ */ new Map();
|
|
505
|
+
for (const deposit of deposits) {
|
|
506
|
+
const createdAt = toUnixSeconds2(deposit.createdAt ?? deposit.timestamp);
|
|
507
|
+
const firstFillByPair = /* @__PURE__ */ new Map();
|
|
508
|
+
const firstFillByCurrency = /* @__PURE__ */ new Map();
|
|
509
|
+
for (const intent of deposit.intents ?? []) {
|
|
510
|
+
const fulfilledAt = toUnixSeconds2(intent.fulfillTimestamp);
|
|
511
|
+
if (fulfilledAt === void 0 || fulfilledAt < windowStart) continue;
|
|
512
|
+
let method;
|
|
513
|
+
try {
|
|
514
|
+
method = intent.paymentMethodHash ? resolvePaymentMethodNameFromHash(intent.paymentMethodHash, catalog) : void 0;
|
|
515
|
+
} catch {
|
|
516
|
+
method = void 0;
|
|
517
|
+
}
|
|
518
|
+
const currency = normalizeCurrencyCode(intent.fiatCurrency);
|
|
519
|
+
if (!method || !currency) continue;
|
|
520
|
+
const pair = `${basePlatformForMethod(method)}:${currency}`;
|
|
521
|
+
fillCounts.set(pair, (fillCounts.get(pair) ?? 0) + 1);
|
|
522
|
+
if (createdAt === void 0 || createdAt < windowStart || fulfilledAt < createdAt) continue;
|
|
523
|
+
const previousPairFill = firstFillByPair.get(pair);
|
|
524
|
+
if (previousPairFill === void 0 || fulfilledAt < previousPairFill) {
|
|
525
|
+
firstFillByPair.set(pair, fulfilledAt);
|
|
526
|
+
}
|
|
527
|
+
const previousCurrencyFill = firstFillByCurrency.get(currency);
|
|
528
|
+
if (previousCurrencyFill === void 0 || fulfilledAt < previousCurrencyFill) {
|
|
529
|
+
firstFillByCurrency.set(currency, fulfilledAt);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (createdAt === void 0) continue;
|
|
533
|
+
for (const [pair, firstFill] of firstFillByPair) {
|
|
534
|
+
const latencies = latenciesByPair.get(pair) ?? [];
|
|
535
|
+
latencies.push(firstFill - createdAt);
|
|
536
|
+
latenciesByPair.set(pair, latencies);
|
|
537
|
+
}
|
|
538
|
+
for (const [currency, firstFill] of firstFillByCurrency) {
|
|
539
|
+
const latencies = latenciesByCurrency.get(currency) ?? [];
|
|
540
|
+
latencies.push(firstFill - createdAt);
|
|
541
|
+
latenciesByCurrency.set(currency, latencies);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const stats = {};
|
|
545
|
+
for (const [pair, fills] of fillCounts) {
|
|
546
|
+
const medianFillSeconds = median(latenciesByPair.get(pair) ?? []);
|
|
547
|
+
stats[pair] = {
|
|
548
|
+
fills,
|
|
549
|
+
...medianFillSeconds !== void 0 ? { medianFillSeconds } : {}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const medianFillSecondsByCurrency = /* @__PURE__ */ new Map();
|
|
553
|
+
for (const [currency, latencies] of latenciesByCurrency) {
|
|
554
|
+
const value = median(latencies);
|
|
555
|
+
if (value !== void 0) medianFillSecondsByCurrency.set(currency, value);
|
|
556
|
+
}
|
|
557
|
+
return { stats, medianFillSecondsByCurrency };
|
|
474
558
|
}
|
|
475
|
-
async function
|
|
559
|
+
async function readFillStatsSample(client, environment) {
|
|
476
560
|
const now = Math.floor(Date.now() / 1e3);
|
|
477
|
-
const windowStart = now -
|
|
561
|
+
const windowStart = now - FILL_STATS_WINDOW_SECONDS;
|
|
478
562
|
const deposits = [];
|
|
479
|
-
for (let offset = 0;
|
|
563
|
+
for (let offset = 0; ; offset += FILL_STATS_PAGE_LIMIT) {
|
|
480
564
|
const page = await client.indexer.getDepositsWithRelations(
|
|
481
565
|
{ chainId: BASE_CHAIN_ID },
|
|
482
|
-
{
|
|
566
|
+
{
|
|
567
|
+
limit: FILL_STATS_PAGE_LIMIT,
|
|
568
|
+
offset,
|
|
569
|
+
orderBy: "updatedAt",
|
|
570
|
+
orderDirection: "desc"
|
|
571
|
+
},
|
|
483
572
|
{ includeIntents: true, intentStatuses: ["FULFILLED", "MANUALLY_RELEASED"] }
|
|
484
573
|
);
|
|
485
574
|
deposits.push(...page);
|
|
486
|
-
if (page.length <
|
|
487
|
-
const
|
|
488
|
-
...page.map((deposit) => toUnixSeconds2(deposit.
|
|
575
|
+
if (page.length < FILL_STATS_PAGE_LIMIT) break;
|
|
576
|
+
const oldestUpdatedAt = Math.min(
|
|
577
|
+
...page.map((deposit) => toUnixSeconds2(deposit.updatedAt) ?? Infinity)
|
|
489
578
|
);
|
|
490
|
-
if (
|
|
579
|
+
if (oldestUpdatedAt < windowStart) break;
|
|
491
580
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
).sort((a, b) => a.fulfilledAt - b.fulfilledAt);
|
|
502
|
-
if (fulfilled.length === 0) continue;
|
|
503
|
-
firstFillLatencies.push(fulfilled[0].fulfilledAt - createdAt);
|
|
504
|
-
}
|
|
505
|
-
const seconds = median(firstFillLatencies);
|
|
581
|
+
return computeFillStatsSample(deposits, now, environment);
|
|
582
|
+
}
|
|
583
|
+
async function readFillStats(client, environment) {
|
|
584
|
+
return (await readFillStatsSample(client, environment)).stats;
|
|
585
|
+
}
|
|
586
|
+
async function readFillEta(client, input) {
|
|
587
|
+
const sample = await readFillStatsSample(client, input.environment);
|
|
588
|
+
const currency = input.currency.toUpperCase();
|
|
589
|
+
const seconds = input.platform ? sample.stats[`${basePlatformForMethod(input.platform)}:${currency}`]?.medianFillSeconds : sample.medianFillSecondsByCurrency.get(currency);
|
|
506
590
|
return {
|
|
507
591
|
...seconds !== void 0 ? { seconds } : {},
|
|
508
592
|
label: etaLabel(seconds)
|
|
@@ -675,6 +759,14 @@ async function assertRelayExecutionIdentity(quote, wallet, expectedRecipient) {
|
|
|
675
759
|
throw new Error("Relay quote recipient does not match the expected Base recipient");
|
|
676
760
|
}
|
|
677
761
|
}
|
|
762
|
+
function assertRelayNonceManagement(quote, wallet) {
|
|
763
|
+
const account = wallet.account;
|
|
764
|
+
if (!account || account.type !== "local" || account.nonceManager !== void 0) return;
|
|
765
|
+
const transactionCount = quote.steps.flatMap(
|
|
766
|
+
(step) => step.items.filter((item) => asString(asRecord(item.data).to) !== void 0)
|
|
767
|
+
).length;
|
|
768
|
+
if (transactionCount > 1) throw errors.sourceNonceManagerRequired(transactionCount);
|
|
769
|
+
}
|
|
678
770
|
function isRelaySecretKey(key) {
|
|
679
771
|
const normalized = key.toLowerCase();
|
|
680
772
|
return normalized === "headers" || normalized === "apikey";
|
|
@@ -876,6 +968,7 @@ async function executeRelayQuote(quote, wallet, options = {}) {
|
|
|
876
968
|
observedRequestId = quoteRequestId(rawQuote);
|
|
877
969
|
assertCanonicalRelayDestination(rawQuote);
|
|
878
970
|
await assertRelayExecutionIdentity(rawQuote, wallet, options.recipient);
|
|
971
|
+
assertRelayNonceManagement(rawQuote, wallet);
|
|
879
972
|
const client = relayClient(options.relay);
|
|
880
973
|
const sourceChainId = quoteSourceChainId(rawQuote);
|
|
881
974
|
if (sourceChainId !== void 0 && !(client.chains ?? []).some((chain) => chain.id === sourceChainId)) {
|
|
@@ -1161,6 +1254,7 @@ function createCashClient(options) {
|
|
|
1161
1254
|
}
|
|
1162
1255
|
function validatePayout(input) {
|
|
1163
1256
|
const { receive } = input;
|
|
1257
|
+
const catalog = getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
|
|
1164
1258
|
const platform = buildCapabilities(environment).platforms.find(
|
|
1165
1259
|
(capability) => capability.platform === receive.platform
|
|
1166
1260
|
);
|
|
@@ -1171,14 +1265,13 @@ function createCashClient(options) {
|
|
|
1171
1265
|
if (!platform.currencies.includes(receive.currency)) {
|
|
1172
1266
|
throw errors.unsupportedPlatformCurrency(receive.platform, receive.currency);
|
|
1173
1267
|
}
|
|
1268
|
+
const paymentMethods = paymentMethodsForPlatform(receive.platform, catalog);
|
|
1174
1269
|
return {
|
|
1175
|
-
payouts:
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
}
|
|
1181
|
-
]
|
|
1270
|
+
payouts: paymentMethods.map((processorName) => ({
|
|
1271
|
+
processorName,
|
|
1272
|
+
currency: receive.currency,
|
|
1273
|
+
payeeData: receive.payee
|
|
1274
|
+
}))
|
|
1182
1275
|
};
|
|
1183
1276
|
}
|
|
1184
1277
|
function validateDepositInput(amount, input, payoutInput = validatePayout(input)) {
|
|
@@ -1456,6 +1549,13 @@ function createCashClient(options) {
|
|
|
1456
1549
|
...options.relay ? { relay: options.relay } : {}
|
|
1457
1550
|
});
|
|
1458
1551
|
},
|
|
1552
|
+
async fillStats() {
|
|
1553
|
+
try {
|
|
1554
|
+
return await readFillStats(readClient, environment);
|
|
1555
|
+
} catch (err) {
|
|
1556
|
+
throw errors.indexerUnavailable("fill stats", err);
|
|
1557
|
+
}
|
|
1558
|
+
},
|
|
1459
1559
|
async cashout(input, opts) {
|
|
1460
1560
|
const client = await signingClient("cashout", opts);
|
|
1461
1561
|
const owner = opts.signer.account.address;
|
|
@@ -2055,6 +2155,11 @@ var cashEstimateJsonSchema = z.object({
|
|
|
2055
2155
|
label: z.string()
|
|
2056
2156
|
}).optional()
|
|
2057
2157
|
});
|
|
2158
|
+
var cashPairFillStatsJsonSchema = z.object({
|
|
2159
|
+
fills: z.number().int().nonnegative(),
|
|
2160
|
+
medianFillSeconds: z.number().int().nonnegative().optional()
|
|
2161
|
+
}).strict();
|
|
2162
|
+
var cashFillStatsJsonSchema = z.record(z.string(), cashPairFillStatsJsonSchema);
|
|
2058
2163
|
var preparedTransactionJsonSchema = z.object({
|
|
2059
2164
|
to: z.string(),
|
|
2060
2165
|
data: z.string(),
|
|
@@ -2186,6 +2291,7 @@ var CASH_ERROR_CODES = defineCashErrorCodes([
|
|
|
2186
2291
|
"SOURCE_RECIPIENT_MISMATCH",
|
|
2187
2292
|
"SOURCE_CAPABILITIES_FAILED",
|
|
2188
2293
|
"SOURCE_QUOTE_FAILED",
|
|
2294
|
+
"SOURCE_NONCE_MANAGER_REQUIRED",
|
|
2189
2295
|
"SOURCE_EXECUTION_FAILED",
|
|
2190
2296
|
"SOURCE_STATUS_FAILED",
|
|
2191
2297
|
"SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED",
|
|
@@ -2358,6 +2464,21 @@ function estimateFromJson(json) {
|
|
|
2358
2464
|
} : void 0
|
|
2359
2465
|
});
|
|
2360
2466
|
}
|
|
2467
|
+
function fillStatsToJson(stats) {
|
|
2468
|
+
return cashFillStatsJsonSchema.parse(stats);
|
|
2469
|
+
}
|
|
2470
|
+
function fillStatsFromJson(json) {
|
|
2471
|
+
const parsed = cashFillStatsJsonSchema.parse(json);
|
|
2472
|
+
return Object.fromEntries(
|
|
2473
|
+
Object.entries(parsed).map(([pair, stats]) => [
|
|
2474
|
+
pair,
|
|
2475
|
+
{
|
|
2476
|
+
fills: stats.fills,
|
|
2477
|
+
...stats.medianFillSeconds !== void 0 ? { medianFillSeconds: stats.medianFillSeconds } : {}
|
|
2478
|
+
}
|
|
2479
|
+
])
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2361
2482
|
function cashAssetFromJson(asset) {
|
|
2362
2483
|
return {
|
|
2363
2484
|
chainId: asset.chainId,
|
|
@@ -2635,4 +2756,4 @@ function cashErrorFromJson(json) {
|
|
|
2635
2756
|
});
|
|
2636
2757
|
}
|
|
2637
2758
|
|
|
2638
|
-
export { CASH_ATTRIBUTION_CODE, MIN_CASHOUT_AMOUNT, RATE_PRECISION, RECOMMENDED_MIN_CASHOUT_AMOUNT, bigintString, buildCapabilities, buildIntentAmountRange, buildMarketRateCurrencyOverride, buyerProfileFromJson, buyerProfileToJson, capabilitiesFromJson, capabilitiesToJson, cashAssetJsonSchema, cashBuyerProfileJsonSchema, cashCapabilitiesJsonSchema, cashChainJsonSchema, cashErrorFromJson, cashErrorJsonSchema, cashErrorRecoveryJsonSchema, cashErrorToJson, cashEstimateJsonSchema, cashFillJsonSchema, cashNextActionSchema, cashOrderJsonSchema, cashOrderStateSchema, cashPayoutInfoJsonSchema, cashPayoutPricingJsonSchema, cashPreparedStepJsonSchema, cashSourceCapabilitiesJsonSchema, cashoutResultFromJson, cashoutResultJsonSchema, cashoutResultToJson, centsToNumber, createCashClient, deriveBuyerProfile, deriveCashOrder, derivePayouts, estimateFromJson, estimateToJson, explainOrder, fiatFromUsdc, fiatToNumber, fillFromJson, fillToJson, formatUsdc, intentStatusSchema, isFillLive, isMarketRateSupported, nonNegativeBigintString, orderFromJson, orderToJson, parseCompositeDepositId, prepareCashDepositParams, prepareResultFromJson, prepareResultJsonSchema, prepareResultToJson, preparedStepFromJson, preparedStepToJson, preparedTransactionJsonSchema, preparedTxFromJson, preparedTxToJson, rateToNumber, relayExecutionResultFromJson, relayExecutionResultJsonSchema, relayExecutionResultToJson, relayQuoteFromJson, relayQuoteJsonSchema, relayQuoteToJson, relayStatusFromJson, relayStatusJsonSchema, relayStatusToJson, relayTransactionJsonSchema, relayTransactionsJsonSchema, resolveCashDepositId, sourceCapabilitiesFromJson, sourceCapabilitiesToJson, topUpResultFromJson, topUpResultJsonSchema, topUpResultToJson, usdc, withExplain, withdrawResultFromJson, withdrawResultJsonSchema, withdrawResultToJson };
|
|
2759
|
+
export { CASH_ATTRIBUTION_CODE, MIN_CASHOUT_AMOUNT, RATE_PRECISION, RECOMMENDED_MIN_CASHOUT_AMOUNT, bigintString, buildCapabilities, buildIntentAmountRange, buildMarketRateCurrencyOverride, buyerProfileFromJson, buyerProfileToJson, capabilitiesFromJson, capabilitiesToJson, cashAssetJsonSchema, cashBuyerProfileJsonSchema, cashCapabilitiesJsonSchema, cashChainJsonSchema, cashErrorFromJson, cashErrorJsonSchema, cashErrorRecoveryJsonSchema, cashErrorToJson, cashEstimateJsonSchema, cashFillJsonSchema, cashFillStatsJsonSchema, cashNextActionSchema, cashOrderJsonSchema, cashOrderStateSchema, cashPairFillStatsJsonSchema, cashPayoutInfoJsonSchema, cashPayoutPricingJsonSchema, cashPreparedStepJsonSchema, cashSourceCapabilitiesJsonSchema, cashoutResultFromJson, cashoutResultJsonSchema, cashoutResultToJson, centsToNumber, createCashClient, deriveBuyerProfile, deriveCashOrder, derivePayouts, estimateFromJson, estimateToJson, explainOrder, fiatFromUsdc, fiatToNumber, fillFromJson, fillStatsFromJson, fillStatsToJson, fillToJson, formatUsdc, intentStatusSchema, isFillLive, isMarketRateSupported, nonNegativeBigintString, orderFromJson, orderToJson, parseCompositeDepositId, prepareCashDepositParams, prepareResultFromJson, prepareResultJsonSchema, prepareResultToJson, preparedStepFromJson, preparedStepToJson, preparedTransactionJsonSchema, preparedTxFromJson, preparedTxToJson, rateToNumber, relayExecutionResultFromJson, relayExecutionResultJsonSchema, relayExecutionResultToJson, relayQuoteFromJson, relayQuoteJsonSchema, relayQuoteToJson, relayStatusFromJson, relayStatusJsonSchema, relayStatusToJson, relayTransactionJsonSchema, relayTransactionsJsonSchema, resolveCashDepositId, sourceCapabilitiesFromJson, sourceCapabilitiesToJson, topUpResultFromJson, topUpResultJsonSchema, topUpResultToJson, usdc, withExplain, withdrawResultFromJson, withdrawResultJsonSchema, withdrawResultToJson };
|
package/dist/react.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CurrencyType } from '@zkp2p/sdk';
|
|
2
|
-
import {
|
|
2
|
+
import { r as CashClient, G as EstimateInput, i as CashEstimate, E as CashoutOptions, h as CashoutResult, D as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-BIzOLHjF.cjs';
|
|
3
3
|
import { WalletClient } from 'viem';
|
|
4
4
|
import '@relayprotocol/relay-sdk';
|
|
5
5
|
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CurrencyType } from '@zkp2p/sdk';
|
|
2
|
-
import {
|
|
2
|
+
import { r as CashClient, G as EstimateInput, i as CashEstimate, E as CashoutOptions, h as CashoutResult, D as CashoutInput, T as TopUpResult, W as WithdrawResult, e as CashOrder } from './createCashClient-BIzOLHjF.js';
|
|
3
3
|
import { WalletClient } from 'viem';
|
|
4
4
|
import '@relayprotocol/relay-sdk';
|
|
5
5
|
|
package/dist/react.js
CHANGED
package/dist/tools.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
|
-
version: "0.1.
|
|
5
|
+
version: "0.1.8"};
|
|
6
6
|
|
|
7
7
|
// src/tools/index.ts
|
|
8
8
|
var bigintString = {
|
|
@@ -112,6 +112,15 @@ var builtInCashTools = [
|
|
|
112
112
|
additionalProperties: false
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
|
+
{
|
|
116
|
+
name: "cash_fill_stats",
|
|
117
|
+
description: "Read raw 30-day demand and first-fill speed evidence for every observed platform:currency pair. Consumers should apply their own threshold and fail open to cash_capabilities when stats are unavailable or filtering would empty the catalog.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {},
|
|
121
|
+
additionalProperties: false
|
|
122
|
+
}
|
|
123
|
+
},
|
|
115
124
|
{
|
|
116
125
|
name: "cash_cashout",
|
|
117
126
|
description: "Start a Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.",
|
package/dist/tools.d.cts
CHANGED
|
@@ -135,6 +135,14 @@ declare const builtInCashTools: readonly [{
|
|
|
135
135
|
readonly required: readonly ["amount", "currency"];
|
|
136
136
|
readonly additionalProperties: false;
|
|
137
137
|
};
|
|
138
|
+
}, {
|
|
139
|
+
readonly name: "cash_fill_stats";
|
|
140
|
+
readonly description: "Read raw 30-day demand and first-fill speed evidence for every observed platform:currency pair. Consumers should apply their own threshold and fail open to cash_capabilities when stats are unavailable or filtering would empty the catalog.";
|
|
141
|
+
readonly inputSchema: {
|
|
142
|
+
readonly type: "object";
|
|
143
|
+
readonly properties: {};
|
|
144
|
+
readonly additionalProperties: false;
|
|
145
|
+
};
|
|
138
146
|
}, {
|
|
139
147
|
readonly name: "cash_cashout";
|
|
140
148
|
readonly description: "Start a Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.";
|
package/dist/tools.d.ts
CHANGED
|
@@ -135,6 +135,14 @@ declare const builtInCashTools: readonly [{
|
|
|
135
135
|
readonly required: readonly ["amount", "currency"];
|
|
136
136
|
readonly additionalProperties: false;
|
|
137
137
|
};
|
|
138
|
+
}, {
|
|
139
|
+
readonly name: "cash_fill_stats";
|
|
140
|
+
readonly description: "Read raw 30-day demand and first-fill speed evidence for every observed platform:currency pair. Consumers should apply their own threshold and fail open to cash_capabilities when stats are unavailable or filtering would empty the catalog.";
|
|
141
|
+
readonly inputSchema: {
|
|
142
|
+
readonly type: "object";
|
|
143
|
+
readonly properties: {};
|
|
144
|
+
readonly additionalProperties: false;
|
|
145
|
+
};
|
|
138
146
|
}, {
|
|
139
147
|
readonly name: "cash_cashout";
|
|
140
148
|
readonly description: "Start a Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.";
|
package/dist/tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
|
-
version: "0.1.
|
|
3
|
+
version: "0.1.8"};
|
|
4
4
|
|
|
5
5
|
// src/tools/index.ts
|
|
6
6
|
var bigintString = {
|
|
@@ -110,6 +110,15 @@ var builtInCashTools = [
|
|
|
110
110
|
additionalProperties: false
|
|
111
111
|
}
|
|
112
112
|
},
|
|
113
|
+
{
|
|
114
|
+
name: "cash_fill_stats",
|
|
115
|
+
description: "Read raw 30-day demand and first-fill speed evidence for every observed platform:currency pair. Consumers should apply their own threshold and fail open to cash_capabilities when stats are unavailable or filtering would empty the catalog.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {},
|
|
119
|
+
additionalProperties: false
|
|
120
|
+
}
|
|
121
|
+
},
|
|
113
122
|
{
|
|
114
123
|
name: "cash_cashout",
|
|
115
124
|
description: "Start a Base-USDC cash-out using the custody-separated prepare path. Returns UNSIGNED transactions plus same-index steps [approve, createDeposit]; signing and ordered submission stay host-side. For another source asset, complete cash_source_quote and cash_source_status first, then pass the guaranteed Base USDC output amount here.",
|
|
@@ -36,6 +36,16 @@ For any other EVM source asset, Peer Cash uses `@relayprotocol/relay-sdk`:
|
|
|
36
36
|
`executeSourceQuote()` remains available for apps that want a separate
|
|
37
37
|
bridge step.
|
|
38
38
|
|
|
39
|
+
Routes that need more than one source-chain transaction (an ERC-20 `approve`,
|
|
40
|
+
then the route transaction) are submitted back-to-back by the Relay SDK. On a
|
|
41
|
+
plain local account the route transaction reuses the approval's nonce and
|
|
42
|
+
reverts mid-route, so `executeSourceQuote()` and `cashout({ source })` refuse
|
|
43
|
+
multi-transaction routes up front with `SOURCE_NONCE_MANAGER_REQUIRED` unless
|
|
44
|
+
the source signer carries a viem nonce manager:
|
|
45
|
+
`privateKeyToAccount(pk, { nonceManager })`. The check fires before anything
|
|
46
|
+
is submitted. Browser (`json-rpc`) wallets are unaffected - the node
|
|
47
|
+
allocates their nonces.
|
|
48
|
+
|
|
39
49
|
Cash-out interfaces should use `tradeType: 'EXACT_INPUT'` (also the default),
|
|
40
50
|
so `amount` always means source-token base units. A `RelayQuote.outputAmount`
|
|
41
51
|
is the guaranteed minimum Base USDC output. The same value becomes
|
|
@@ -62,10 +72,16 @@ destination constant.
|
|
|
62
72
|
|
|
63
73
|
Do not retry a source route merely because the Base cashout did not finish:
|
|
64
74
|
|
|
75
|
+
- `SOURCE_NONCE_MANAGER_REQUIRED` is preflight: nothing was submitted.
|
|
76
|
+
Recreate the source signer with viem's nonce manager and execute a fresh
|
|
77
|
+
quote.
|
|
65
78
|
- `SOURCE_EXECUTION_FAILED` means Relay execution did not report success. Check
|
|
66
79
|
its `inspect-relay-route` recovery evidence, submitted wallet transactions,
|
|
67
80
|
and `relayStatus(requestId)` before taking another action; a blind retry can
|
|
68
|
-
route twice.
|
|
81
|
+
route twice. A failed route whose only landed transaction is the approval
|
|
82
|
+
can sit in `relayStatus` `waiting` indefinitely - it never becomes terminal,
|
|
83
|
+
so decide from the persisted recovery payload and the origin transactions,
|
|
84
|
+
not from Relay reaching a final status.
|
|
69
85
|
- `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED` means Relay completed, but the Base
|
|
70
86
|
cashout was not created. Its recovery payload has
|
|
71
87
|
`kind: 'retry-base-usdc-cashout'`, the guaranteed Base USDC `amount`, Relay
|
|
@@ -108,10 +124,17 @@ passes through `delivering` until the last one completes. `filledAmount`,
|
|
|
108
124
|
## The ETA principle
|
|
109
125
|
|
|
110
126
|
`estimate().eta` is historical, not a promise. It uses rolling 30-day indexer
|
|
111
|
-
data from deposit/order creation to the first fulfilled fill
|
|
112
|
-
|
|
113
|
-
buyer
|
|
114
|
-
`{ seconds, label }`.
|
|
127
|
+
data from deposit/order creation to the first fulfilled fill through the
|
|
128
|
+
intent's actual platform and currency pair. It deliberately does **not**
|
|
129
|
+
measure buyer signal to fulfillment; that would miss the buyer-arrival wait
|
|
130
|
+
that users actually care about. The public shape is small: `{ seconds, label }`.
|
|
131
|
+
|
|
132
|
+
`fillStats()` exposes the sampler's raw evidence for catalog filtering as
|
|
133
|
+
`Record<"platform:currency", { fills, medianFillSeconds? }>`. Bank-scoped Zelle
|
|
134
|
+
methods aggregate to `zelle:USD`. Consumers own thresholding; the recommended
|
|
135
|
+
gate is `fills >= 10 && medianFillSeconds <= 48h`, with a fail-open fallback to
|
|
136
|
+
the full capability catalog when the read fails or filtering would empty it.
|
|
137
|
+
Medians are per-deposit first-fill latencies, never means or censored cohorts.
|
|
115
138
|
|
|
116
139
|
- **Buyer arrival time is market-driven.** A deposit at market rate should
|
|
117
140
|
fill fast, but the ETA is only a recent historical sample.
|
|
@@ -132,7 +155,9 @@ UI built on this SDK is a bug in that UI.
|
|
|
132
155
|
- **Partial withdrawal** - `withdraw(depositId, { amount })` pulls part of
|
|
133
156
|
the _unlocked_ balance back out. A live buyer intent does not block it
|
|
134
157
|
(their locked portion is untouched); asking for more than the unlocked
|
|
135
|
-
balance fails with `INSUFFICIENT_AVAILABLE_FUNDS`.
|
|
158
|
+
balance fails with `INSUFFICIENT_AVAILABLE_FUNDS`. Accounting note: a
|
|
159
|
+
partial withdrawal increments `returnedAmount`; `totalAmount` records
|
|
160
|
+
everything the order has ever held and does not shrink.
|
|
136
161
|
- There is no retain-on-empty or rate knob to manage - a cash order cleans
|
|
137
162
|
itself up when fully filled, and the market rate is not configurable.
|
|
138
163
|
|
|
@@ -252,6 +277,7 @@ explicit override.
|
|
|
252
277
|
| `SOURCE_RECIPIENT_MISMATCH` | no | Relay output recipient differs from the cashout depositor. Use the depositor address. |
|
|
253
278
|
| `SOURCE_CAPABILITIES_FAILED` | yes | Relay source discovery failed. Retry or use Base USDC. |
|
|
254
279
|
| `SOURCE_QUOTE_FAILED` | yes | Relay returned no valid canonical Base-USDC route. Refresh capabilities and quote again. |
|
|
280
|
+
| `SOURCE_NONCE_MANAGER_REQUIRED` | no | Preflight; nothing was submitted. Recreate the source signer with viem's `nonceManager` and execute a fresh quote. |
|
|
255
281
|
| `SOURCE_EXECUTION_FAILED` | no | Route execution did not report success. Inspect source transactions and Relay status before retrying. |
|
|
256
282
|
| `SOURCE_STATUS_FAILED` | yes | Relay status is temporarily unavailable. Retry the status read without resubmitting. |
|
|
257
283
|
| `SOURCE_ROUTE_COMPLETED_CASHOUT_FAILED` | no | Relay completed but no Base cashout was created. Use the recovery amount for a Base-USDC-only retry; never repeat Relay. |
|
package/llms.txt
CHANGED
|
@@ -19,9 +19,13 @@ Key facts:
|
|
|
19
19
|
source.amount is Relay's guaranteed minimum Base USDC output and the exact
|
|
20
20
|
order deposit amount, not the route's actual output.
|
|
21
21
|
- There is NO locked fiat quote. estimate() reads the oracle; the binding rate
|
|
22
|
-
resolves at fill time. ETA is `{ seconds, label }` from rolling
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
resolves at fill time. ETA is `{ seconds, label }` from the same rolling
|
|
23
|
+
30-day, intent-attributed pair sampler as fillStats(), not a guarantee.
|
|
24
|
+
- fillStats() returns raw `{ fills, medianFillSeconds? }` evidence keyed by
|
|
25
|
+
`platform:currency`. Recommended consumer gate: fills >= 10 and median <=
|
|
26
|
+
48h; fail open to capabilities() if unavailable or filtering empties it.
|
|
27
|
+
- capabilities() exposes one Zelle platform. A zelle cashout internally attaches
|
|
28
|
+
the generic method plus Chase, Bank of America, and Citi buyer routes.
|
|
25
29
|
- Resume any order from its depositId alone (composite escrow_onchainId).
|
|
26
30
|
- One unwind verb: withdraw(depositId) - prunes expired intents automatically;
|
|
27
31
|
pass amount for a partial withdrawal of the unlocked balance.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zkp2p/cash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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)",
|
|
@@ -92,13 +92,15 @@
|
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "tsup",
|
|
94
94
|
"typecheck": "tsc --noEmit",
|
|
95
|
-
"lint": "eslint src test examples",
|
|
95
|
+
"lint": "eslint src test examples scripts",
|
|
96
96
|
"format": "prettier --write .",
|
|
97
97
|
"format:check": "prettier --check .",
|
|
98
98
|
"test": "vitest run",
|
|
99
99
|
"test:watch": "vitest",
|
|
100
100
|
"audit": "bun audit --production",
|
|
101
101
|
"pack:check": "bun scripts/check-packed-package.ts",
|
|
102
|
+
"verify:production-crosschain-relay": "bun scripts/verify-production-crosschain-relay.ts",
|
|
103
|
+
"verify:production-maker": "bun scripts/verify-production-maker.ts",
|
|
102
104
|
"prepack": "bun run build",
|
|
103
105
|
"ci": "bun run typecheck && bun run lint && bun run format:check && bun run test && bun run audit && bun run build && bun run pack:check"
|
|
104
106
|
},
|