@zkp2p/cash 0.1.1 → 0.1.3

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/dist/index.js CHANGED
@@ -1,8 +1,10 @@
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 } from './chunk-4DRZRWWS.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-4DRZRWWS.js';
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 } from './chunk-FKVPZVFH.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-FKVPZVFH.js';
3
3
  import { parseAbi, parseEventLogs, 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';
6
+ import { createClient, MAINNET_RELAY_API } from '@relayprotocol/relay-sdk';
7
+ import { fetchChainConfigs, configureDynamicChains } from '@relayprotocol/relay-sdk/chain-utils';
6
8
  import { z } from 'zod';
7
9
 
8
10
  function isMarketRateSupported(currency, adapters) {
@@ -414,16 +416,317 @@ function buildCapabilities(environment) {
414
416
  };
415
417
  }).filter((p) => p.currencies.length > 0).sort((a, b) => a.platform.localeCompare(b.platform));
416
418
  const currencies = [...new Set(platforms.flatMap((p) => p.currencies))].sort();
419
+ const baseUsdc = { address: BASE_USDC_ADDRESS, symbol: "USDC", decimals: USDC_DECIMALS };
417
420
  return {
418
421
  chainId: BASE_CHAIN_ID,
419
- token: { address: BASE_USDC_ADDRESS, symbol: "USDC", decimals: USDC_DECIMALS },
422
+ token: baseUsdc,
420
423
  environment,
424
+ destination: { chainId: BASE_CHAIN_ID, token: baseUsdc },
425
+ source: { default: { chainId: BASE_CHAIN_ID, token: baseUsdc } },
421
426
  platforms,
422
427
  currencies,
423
428
  amount: { min: MIN_CASHOUT_AMOUNT, recommendedMin: RECOMMENDED_MIN_CASHOUT_AMOUNT, max: null },
424
429
  pricing: { kind: "oracle-market-rate", spreadBps: 0 }
425
430
  };
426
431
  }
432
+ var ETA_WINDOW_DAYS = 30;
433
+ var ETA_WINDOW_SECONDS = ETA_WINDOW_DAYS * 24 * 60 * 60;
434
+ var ETA_PAGE_LIMIT = 250;
435
+ var ETA_MAX_DEPOSIT_SCAN = 2e3;
436
+ var FULFILLED = /* @__PURE__ */ new Set(["FULFILLED", "MANUALLY_RELEASED"]);
437
+ function toUnixSeconds2(value) {
438
+ if (value === null || value === void 0 || value === "") return void 0;
439
+ if (value instanceof Date) {
440
+ const seconds = Math.floor(value.getTime() / 1e3);
441
+ return Number.isFinite(seconds) && seconds > 0 ? seconds : void 0;
442
+ }
443
+ if (typeof value === "string" && /[TZ:-]/.test(value)) {
444
+ const parsed = Date.parse(value);
445
+ if (Number.isFinite(parsed)) return Math.floor(parsed / 1e3);
446
+ }
447
+ const n = Number(value);
448
+ return Number.isFinite(n) && n > 0 ? n : void 0;
449
+ }
450
+ function median(values) {
451
+ if (values.length === 0) return void 0;
452
+ const sorted = [...values].sort((a, b) => a - b);
453
+ const mid = Math.floor(sorted.length / 2);
454
+ return sorted.length % 2 === 1 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
455
+ }
456
+ function etaLabel(seconds) {
457
+ if (seconds === void 0) return "Recent fill time unavailable";
458
+ if (seconds < 60) return "Usually starts in under a minute";
459
+ const minutes = Math.max(1, Math.round(seconds / 60));
460
+ if (minutes < 60) return `Usually starts in about ${minutes} min`;
461
+ const hours = Math.max(1, Math.round(minutes / 60));
462
+ return `Usually starts in about ${hours} hr`;
463
+ }
464
+ function matchesPayout(deposit, environment, platform, currency) {
465
+ const payouts = derivePayouts(
466
+ deposit.paymentMethods ?? [],
467
+ deposit.currencies ?? [],
468
+ getPaymentMethodsCatalog(BASE_CHAIN_ID, environment)
469
+ );
470
+ return payouts.some(
471
+ (payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 && (platform === void 0 || payout.platform === platform) && (currency === void 0 || payout.currency === currency)
472
+ );
473
+ }
474
+ async function readFillEta(client, input) {
475
+ const now = Math.floor(Date.now() / 1e3);
476
+ const windowStart = now - ETA_WINDOW_SECONDS;
477
+ const deposits = [];
478
+ for (let offset = 0; offset < ETA_MAX_DEPOSIT_SCAN; offset += ETA_PAGE_LIMIT) {
479
+ const page = await client.indexer.getDepositsWithRelations(
480
+ { chainId: BASE_CHAIN_ID },
481
+ { limit: ETA_PAGE_LIMIT, offset, orderBy: "timestamp", orderDirection: "desc" },
482
+ { includeIntents: true, intentStatuses: ["FULFILLED", "MANUALLY_RELEASED"] }
483
+ );
484
+ deposits.push(...page);
485
+ if (page.length < ETA_PAGE_LIMIT) break;
486
+ const oldestCreatedAt = Math.min(
487
+ ...page.map((deposit) => toUnixSeconds2(deposit.createdAt ?? deposit.timestamp) ?? Infinity)
488
+ );
489
+ if (oldestCreatedAt < windowStart) break;
490
+ }
491
+ const firstFillLatencies = [];
492
+ for (const deposit of deposits) {
493
+ const createdAt = toUnixSeconds2(deposit.createdAt ?? deposit.timestamp);
494
+ if (createdAt === void 0 || createdAt < windowStart) continue;
495
+ if (!matchesPayout(deposit, input.environment, input.platform, input.currency)) continue;
496
+ const fulfilled = (deposit.intents ?? []).filter((intent) => intent.status != null && FULFILLED.has(intent.status)).map((intent) => ({
497
+ fulfilledAt: toUnixSeconds2(intent.fulfillTimestamp)
498
+ })).filter(
499
+ (intent) => intent.fulfilledAt !== void 0 && intent.fulfilledAt >= createdAt
500
+ ).sort((a, b) => a.fulfilledAt - b.fulfilledAt);
501
+ if (fulfilled.length === 0) continue;
502
+ firstFillLatencies.push(fulfilled[0].fulfilledAt - createdAt);
503
+ }
504
+ const seconds = median(firstFillLatencies);
505
+ return {
506
+ ...seconds !== void 0 ? { seconds } : {},
507
+ label: etaLabel(seconds)
508
+ };
509
+ }
510
+ var RELAY_API_URL = MAINNET_RELAY_API;
511
+ var NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
512
+ var BASE_USDC_ASSET = {
513
+ chainId: BASE_CHAIN_ID,
514
+ address: BASE_USDC_ADDRESS,
515
+ symbol: "USDC",
516
+ decimals: USDC_DECIMALS,
517
+ name: "USD Coin"
518
+ };
519
+ function relayClient(options = {}) {
520
+ if (options.client) return options.client;
521
+ return createClient({
522
+ baseApiUrl: options.apiUrl ?? RELAY_API_URL,
523
+ source: options.source ?? "peer-cash",
524
+ ...options.apiKey ? { apiKey: options.apiKey } : {},
525
+ ...options.chains ? { chains: options.chains } : {}
526
+ });
527
+ }
528
+ function asRecord(value) {
529
+ return value !== null && typeof value === "object" ? value : {};
530
+ }
531
+ function asString(value) {
532
+ return typeof value === "string" && value.length > 0 ? value : void 0;
533
+ }
534
+ function asNumber(value) {
535
+ const n = Number(value);
536
+ return Number.isFinite(n) ? n : void 0;
537
+ }
538
+ function normalizeToken(chainId, token) {
539
+ const row = asRecord(token);
540
+ const address = asString(row.address);
541
+ const symbol = asString(row.symbol);
542
+ const decimals = asNumber(row.decimals);
543
+ const name = asString(row.name);
544
+ if (!address || !symbol || decimals === void 0) return null;
545
+ const metadata = asRecord(row.metadata);
546
+ return {
547
+ chainId,
548
+ address,
549
+ symbol,
550
+ decimals,
551
+ ...name ? { name } : {},
552
+ ...metadata.isNative === true || address.toLowerCase() === NATIVE_TOKEN_ADDRESS ? { isNative: true } : {}
553
+ };
554
+ }
555
+ function normalizeTx(data, chainId) {
556
+ const row = asRecord(data);
557
+ const to = asString(row.to);
558
+ const calldata = asString(row.data) ?? "0x";
559
+ if (!to) return null;
560
+ return {
561
+ to,
562
+ data: calldata,
563
+ value: BigInt(String(row.value ?? "0")),
564
+ chainId: asNumber(row.chainId) ?? chainId
565
+ };
566
+ }
567
+ function normalizeChain(chain) {
568
+ const row = asRecord(chain);
569
+ const tokenRows = [
570
+ chain.currency,
571
+ ...chain.featuredTokens ?? [],
572
+ ...chain.erc20Currencies ?? [],
573
+ ...chain.solverCurrencies ?? []
574
+ ];
575
+ const tokens = /* @__PURE__ */ new Map();
576
+ for (const token of tokenRows) {
577
+ const normalizedToken = normalizeToken(chain.id, token);
578
+ if (normalizedToken) tokens.set(normalizedToken.address.toLowerCase(), normalizedToken);
579
+ }
580
+ return {
581
+ id: chain.id,
582
+ name: chain.name,
583
+ displayName: chain.displayName,
584
+ disabled: row.disabled === true,
585
+ depositEnabled: chain.depositEnabled ?? false,
586
+ blockProductionLagging: chain.blockProductionLagging ?? false,
587
+ ...chain.vmType ? { vmType: chain.vmType } : {},
588
+ tokens: [...tokens.values()].sort((a, b) => a.symbol.localeCompare(b.symbol))
589
+ };
590
+ }
591
+ function isSupportedEvmChain(chain) {
592
+ return chain.vmType === void 0 || chain.vmType === "evm";
593
+ }
594
+ function quoteRequestId(quote) {
595
+ return quote.steps.map((step) => step.requestId).find((id) => id !== void 0);
596
+ }
597
+ function quoteSourceChainId(quote) {
598
+ const details = asRecord(quote.details);
599
+ const currencyIn = asRecord(details.currencyIn);
600
+ const sourceCurrency = asRecord(currencyIn.currency);
601
+ return asNumber(sourceCurrency.chainId);
602
+ }
603
+ function sanitizeRelayQuoteRaw(quote) {
604
+ if (!quote.request) return quote;
605
+ const request = { ...quote.request };
606
+ delete request.headers;
607
+ return { ...quote, request };
608
+ }
609
+ async function resolveRelayChains(options, client, config = {}) {
610
+ const preferInjectedClientChains = config.preferInjectedClientChains ?? true;
611
+ const chains = options.chains ?? (preferInjectedClientChains && options.client?.chains?.length ? options.client.chains : void 0) ?? (options.client ? await fetchChainConfigs(client.baseApiUrl, client.source, client.apiKey) : await configureDynamicChains());
612
+ client.chains = chains;
613
+ return chains;
614
+ }
615
+ function relayQuoteFromExecute(input, quote) {
616
+ const details = asRecord(quote.details);
617
+ const currencyIn = asRecord(details.currencyIn);
618
+ const currencyOut = asRecord(details.currencyOut);
619
+ const sourceCurrency = asRecord(currencyIn.currency);
620
+ const destinationCurrency = asRecord(currencyOut.currency);
621
+ const source = normalizeToken(input.source.chainId, sourceCurrency) ?? {
622
+ chainId: input.source.chainId,
623
+ address: input.source.currency,
624
+ symbol: "TOKEN",
625
+ decimals: 0
626
+ };
627
+ const destination = normalizeToken(BASE_CHAIN_ID, destinationCurrency) ?? BASE_USDC_ASSET;
628
+ const txs = quote.steps.flatMap(
629
+ (step) => step.items.map((item) => normalizeTx(item.data, input.source.chainId)).filter((tx) => tx !== null)
630
+ );
631
+ const outputAmount = BigInt(
632
+ String(currencyOut.minimumAmount ?? currencyOut.amount ?? input.amount.toString())
633
+ );
634
+ const requestId = quoteRequestId(quote);
635
+ const rate = asNumber(details.rate);
636
+ const timeEstimateSeconds = asNumber(details.timeEstimate);
637
+ return {
638
+ ...requestId ? { requestId } : {},
639
+ source,
640
+ destination,
641
+ inputAmount: BigInt(String(currencyIn.amount ?? input.amount.toString())),
642
+ outputAmount,
643
+ ...rate !== void 0 ? { rate } : {},
644
+ ...timeEstimateSeconds !== void 0 ? { timeEstimateSeconds } : {},
645
+ ...quote.fees !== void 0 ? { fees: quote.fees } : {},
646
+ txs,
647
+ raw: sanitizeRelayQuoteRaw(quote)
648
+ };
649
+ }
650
+ async function readRelaySourceCapabilities(options = {}) {
651
+ const client = relayClient(options);
652
+ const chains = await resolveRelayChains(options, client);
653
+ return {
654
+ destination: BASE_USDC_ASSET,
655
+ chains: chains.map(normalizeChain).filter((chain) => chain.tokens.length > 0 && isSupportedEvmChain(chain)).sort((a, b) => a.displayName.localeCompare(b.displayName)),
656
+ source: "relay-sdk",
657
+ asOf: Math.floor(Date.now() / 1e3)
658
+ };
659
+ }
660
+ async function quoteRelayToBaseUsdc(input, options = {}) {
661
+ const client = relayClient(options);
662
+ const quote = await client.actions.getQuote(
663
+ {
664
+ chainId: input.source.chainId,
665
+ currency: input.source.currency,
666
+ toChainId: BASE_CHAIN_ID,
667
+ toCurrency: BASE_USDC_ADDRESS,
668
+ user: input.user,
669
+ recipient: input.recipient ?? input.user,
670
+ amount: input.amount.toString(),
671
+ tradeType: input.tradeType ?? "EXACT_INPUT"
672
+ },
673
+ false
674
+ );
675
+ return relayQuoteFromExecute(input, quote);
676
+ }
677
+ async function executeRelayQuote(quote, wallet, options = {}) {
678
+ const client = relayClient(options.relay);
679
+ const sourceChainId = quoteSourceChainId(quote);
680
+ if (sourceChainId !== void 0 && !(client.chains ?? []).some((chain) => chain.id === sourceChainId)) {
681
+ await resolveRelayChains(options.relay ?? {}, client, { preferInjectedClientChains: false });
682
+ }
683
+ const { data } = await client.actions.execute({
684
+ quote,
685
+ wallet,
686
+ ...options.onProgress ? { onProgress: options.onProgress } : {},
687
+ ...options.disableCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: options.disableCapabilitiesCheck } : {}
688
+ });
689
+ const requestId = quoteRequestId(data);
690
+ return {
691
+ ...requestId ? { requestId } : {},
692
+ txHashes: data.steps.flatMap(
693
+ (step) => step.items.flatMap((item) => (item.txHashes ?? []).map((tx) => tx.txHash))
694
+ ),
695
+ quote: data
696
+ };
697
+ }
698
+ async function readRelayStatus(requestId, options = {}) {
699
+ const client = relayClient(options);
700
+ const response = await client.utils.request({
701
+ url: `${client.baseApiUrl}/intents/status/v3`,
702
+ method: "get",
703
+ params: { requestId }
704
+ });
705
+ const root = asRecord(response.data);
706
+ const status = asString(root.status);
707
+ if (status !== "refund" && status !== "waiting" && status !== "depositing" && status !== "failure" && status !== "pending" && status !== "submitted" && status !== "success") {
708
+ throw new Error(`Relay returned unknown status: ${String(root.status)}`);
709
+ }
710
+ const details = asString(root.details);
711
+ const updatedAt = asNumber(root.updatedAt);
712
+ const originChainId = asNumber(root.originChainId);
713
+ const destinationChainId = asNumber(root.destinationChainId);
714
+ const quoteCreatedAt = asNumber(root.quoteCreatedAt);
715
+ return {
716
+ requestId,
717
+ status,
718
+ ...details ? { details } : {},
719
+ inTxHashes: Array.isArray(root.inTxHashes) ? root.inTxHashes.map(String) : [],
720
+ txHashes: Array.isArray(root.txHashes) ? root.txHashes.map(String) : [],
721
+ ...updatedAt !== void 0 ? { updatedAt } : {},
722
+ ...originChainId !== void 0 ? { originChainId } : {},
723
+ ...destinationChainId !== void 0 ? { destinationChainId } : {},
724
+ ...quoteCreatedAt !== void 0 ? { quoteCreatedAt } : {},
725
+ raw: response.data
726
+ };
727
+ }
728
+
729
+ // src/client/estimate.ts
427
730
  var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
428
731
  var CHAINLINK_LATEST_ROUND_ABI = [
429
732
  {
@@ -441,14 +744,25 @@ var CHAINLINK_LATEST_ROUND_ABI = [
441
744
  }
442
745
  ];
443
746
  var DEFAULT_MAX_STALENESS_SECONDS = 86400;
444
- async function readEstimate(publicClient, input) {
445
- const { amount, currency } = input;
446
- if (amount < MIN_CASHOUT_AMOUNT) {
447
- throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
448
- }
747
+ async function readEstimate(publicClient, input, context = {}) {
748
+ const { currency } = input;
449
749
  if (!isMarketRateSupported(currency)) {
450
750
  throw errors.oracleUnsupportedCurrency(currency);
451
751
  }
752
+ const relayQuote = input.source !== void 0 ? await quoteRelayToBaseUsdc(
753
+ {
754
+ user: input.source.user,
755
+ amount: input.amount,
756
+ source: { chainId: input.source.chainId, currency: input.source.currency },
757
+ ...input.source.recipient ? { recipient: input.source.recipient } : {},
758
+ ...input.source.tradeType ? { tradeType: input.source.tradeType } : {}
759
+ },
760
+ context.relay
761
+ ) : void 0;
762
+ const amount = relayQuote?.outputAmount ?? input.amount;
763
+ if (amount < MIN_CASHOUT_AMOUNT) {
764
+ throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
765
+ }
452
766
  const feedConfig = CHAINLINK_ORACLE_FEEDS[currency];
453
767
  const asOf = Math.floor(Date.now() / 1e3);
454
768
  let rate;
@@ -471,7 +785,7 @@ async function readEstimate(publicClient, input) {
471
785
  rate = feedConfig.invert ? 1 / price : price;
472
786
  }
473
787
  const stale = oracleUpdatedAt !== void 0 && asOf - oracleUpdatedAt > DEFAULT_MAX_STALENESS_SECONDS;
474
- return {
788
+ const estimate = {
475
789
  kind: "oracle-estimate",
476
790
  currency,
477
791
  amount,
@@ -479,8 +793,27 @@ async function readEstimate(publicClient, input) {
479
793
  receiveAmount: Number(amount) / 10 ** USDC_DECIMALS * rate,
480
794
  asOf,
481
795
  ...oracleUpdatedAt !== void 0 ? { oracleUpdatedAt } : {},
482
- ...stale ? { stale: true } : {}
796
+ ...stale ? { stale: true } : {},
797
+ ...relayQuote ? {
798
+ source: {
799
+ kind: "relay",
800
+ asset: relayQuote.source,
801
+ inputAmount: relayQuote.inputAmount,
802
+ relayQuote
803
+ }
804
+ } : {}
483
805
  };
806
+ if (context.indexerClient && context.environment) {
807
+ try {
808
+ estimate.eta = await readFillEta(context.indexerClient, {
809
+ environment: context.environment,
810
+ currency,
811
+ ...input.platform ? { platform: input.platform } : {}
812
+ });
813
+ } catch {
814
+ }
815
+ }
816
+ return estimate;
484
817
  }
485
818
 
486
819
  // src/client/createCashClient.ts
@@ -576,9 +909,8 @@ function createCashClient(options) {
576
909
  }
577
910
  return client;
578
911
  }
579
- function validateInput(input) {
580
- const { amount, receive } = input;
581
- if (amount < MIN_CASHOUT_AMOUNT) throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
912
+ function validatePayout(input) {
913
+ const { receive } = input;
582
914
  const catalog = getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
583
915
  if (!catalog[receive.platform]) throw errors.unsupportedPlatform(receive.platform);
584
916
  if (!isMarketRateSupported(receive.currency)) {
@@ -588,7 +920,6 @@ function createCashClient(options) {
588
920
  throw errors.payeeVerificationRequired(receive.platform);
589
921
  }
590
922
  return {
591
- amount,
592
923
  payouts: [
593
924
  {
594
925
  processorName: receive.platform,
@@ -599,6 +930,12 @@ function createCashClient(options) {
599
930
  ...input.intentAmountRange ? { intentAmountRange: input.intentAmountRange } : {}
600
931
  };
601
932
  }
933
+ function validateInput(input) {
934
+ if (input.amount < MIN_CASHOUT_AMOUNT) {
935
+ throw errors.amountBelowMinimum(input.amount, MIN_CASHOUT_AMOUNT);
936
+ }
937
+ return { amount: input.amount, ...validatePayout(input) };
938
+ }
602
939
  async function buildDepositParams(client, depositInput) {
603
940
  try {
604
941
  return await prepareCashDepositParams(client, depositInput);
@@ -675,6 +1012,14 @@ function createCashClient(options) {
675
1012
  if (!order.isInFlight) throw errors.orderNotActive(depositId);
676
1013
  return escrowContext(depositId);
677
1014
  }
1015
+ function capabilities(capabilityOptions) {
1016
+ const baseCapabilities = buildCapabilities(environment);
1017
+ if (!capabilityOptions?.includeRelaySources) return baseCapabilities;
1018
+ return readRelaySourceCapabilities(options.relay).then((relay) => ({
1019
+ ...baseCapabilities,
1020
+ source: { ...baseCapabilities.source, relay }
1021
+ }));
1022
+ }
678
1023
  async function settleAllowance(client, token, owner, escrow, amount) {
679
1024
  let allowance;
680
1025
  try {
@@ -703,18 +1048,109 @@ function createCashClient(options) {
703
1048
  throw errors.allowanceNotVisible(amount);
704
1049
  }
705
1050
  return {
706
- capabilities() {
707
- return buildCapabilities(environment);
1051
+ capabilities,
1052
+ async sourceCapabilities() {
1053
+ return readRelaySourceCapabilities(options.relay);
1054
+ },
1055
+ async quoteSource(input) {
1056
+ return quoteRelayToBaseUsdc(input, options.relay);
1057
+ },
1058
+ async executeSourceQuote(quote, opts) {
1059
+ return executeRelayQuote(quote, opts.signer, {
1060
+ ...options.relay ? { relay: options.relay } : {},
1061
+ ...opts.onProgress ? { onProgress: opts.onProgress } : {},
1062
+ ...opts.disableCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: opts.disableCapabilitiesCheck } : {}
1063
+ });
1064
+ },
1065
+ async relayStatus(requestId) {
1066
+ return readRelayStatus(requestId, options.relay);
708
1067
  },
709
1068
  async estimate(input) {
710
- return readEstimate(readClient.publicClient, input);
1069
+ return readEstimate(readClient.publicClient, input, {
1070
+ indexerClient: readClient,
1071
+ environment,
1072
+ ...options.relay ? { relay: options.relay } : {}
1073
+ });
711
1074
  },
712
1075
  async cashout(input, opts) {
713
- const depositInput = validateInput(input);
714
1076
  const client = signingClient("cashout", opts);
1077
+ const owner = opts.signer.account.address;
1078
+ const payoutInput = validatePayout(input);
1079
+ let sourceResult;
1080
+ let cashoutAmount = input.amount;
1081
+ if (input.source) {
1082
+ const sourceSigner = opts.sourceSigner ?? (input.source.chainId === BASE_CHAIN_ID ? opts.signer : void 0);
1083
+ if (!sourceSigner?.account) throw errors.signerRequired("source cashout");
1084
+ if (input.source.recipient !== void 0 && input.source.recipient.toLowerCase() !== owner.toLowerCase()) {
1085
+ throw errors.sourceRecipientMismatch(input.source.recipient, owner);
1086
+ }
1087
+ const relayQuote = await quoteRelayToBaseUsdc(
1088
+ {
1089
+ user: sourceSigner.account.address,
1090
+ amount: input.amount,
1091
+ source: { chainId: input.source.chainId, currency: input.source.currency },
1092
+ recipient: owner,
1093
+ ...input.source.tradeType ? { tradeType: input.source.tradeType } : {}
1094
+ },
1095
+ options.relay
1096
+ );
1097
+ if (relayQuote.outputAmount < MIN_CASHOUT_AMOUNT) {
1098
+ throw errors.amountBelowMinimum(relayQuote.outputAmount, MIN_CASHOUT_AMOUNT);
1099
+ }
1100
+ cashoutAmount = relayQuote.outputAmount;
1101
+ const depositInput2 = { amount: cashoutAmount, ...payoutInput };
1102
+ const params2 = await buildDepositParams(client, depositInput2);
1103
+ const escrow2 = client.escrowV2Address ?? client.escrowAddress;
1104
+ await settleAllowance(client, params2.token, owner, escrow2, depositInput2.amount);
1105
+ const executed = await executeRelayQuote(relayQuote.raw, sourceSigner, {
1106
+ ...options.relay ? { relay: options.relay } : {},
1107
+ ...opts.onSourceProgress ? { onProgress: opts.onSourceProgress } : {},
1108
+ ...opts.disableSourceCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: opts.disableSourceCapabilitiesCheck } : {}
1109
+ });
1110
+ sourceResult = {
1111
+ amount: cashoutAmount,
1112
+ ...executed.requestId ? { requestId: executed.requestId } : {},
1113
+ txHashes: executed.txHashes
1114
+ };
1115
+ const attributedParams2 = { ...params2, txOverrides: attribution };
1116
+ const send2 = async () => {
1117
+ try {
1118
+ return (await client.createDeposit(attributedParams2)).hash;
1119
+ } catch (err) {
1120
+ if (err instanceof Error && /exceeds allowance/i.test(err.message)) {
1121
+ await sleep(2e3);
1122
+ return (await client.createDeposit(attributedParams2)).hash;
1123
+ }
1124
+ throw err;
1125
+ }
1126
+ };
1127
+ let hash2;
1128
+ try {
1129
+ hash2 = await send2();
1130
+ } catch (err) {
1131
+ throw mapChainError("createDeposit", err);
1132
+ }
1133
+ const receipt2 = await client.publicClient.waitForTransactionReceipt({ hash: hash2 });
1134
+ if (receipt2.status === "reverted") throw errors.transactionFailed(hash2);
1135
+ const abi2 = client.escrowV2Abi ?? client.escrowAbi;
1136
+ const resolved2 = resolveCashDepositId({ logs: receipt2.logs, abi: abi2 });
1137
+ if (!resolved2) throw errors.depositResolutionFailed(hash2);
1138
+ const order2 = deriveCashOrder(resolved2.compositeId, [], {
1139
+ remainingAmount: depositInput2.amount,
1140
+ status: "ACTIVE"
1141
+ });
1142
+ return {
1143
+ depositId: resolved2.compositeId,
1144
+ txHash: hash2,
1145
+ escrowAddress: resolved2.escrowAddress,
1146
+ onchainDepositId: resolved2.onchainDepositId,
1147
+ order: order2,
1148
+ source: sourceResult
1149
+ };
1150
+ }
1151
+ const depositInput = validateInput(input);
715
1152
  const params = await buildDepositParams(client, depositInput);
716
1153
  const escrow = client.escrowV2Address ?? client.escrowAddress;
717
- const owner = opts.signer.account.address;
718
1154
  await settleAllowance(client, params.token, owner, escrow, depositInput.amount);
719
1155
  const attributedParams = { ...params, txOverrides: attribution };
720
1156
  const send = async () => {
@@ -748,10 +1184,12 @@ function createCashClient(options) {
748
1184
  txHash: hash,
749
1185
  escrowAddress: resolved.escrowAddress,
750
1186
  onchainDepositId: resolved.onchainDepositId,
751
- order
1187
+ order,
1188
+ ...sourceResult ? { source: sourceResult } : {}
752
1189
  };
753
1190
  },
754
1191
  async prepare(input) {
1192
+ if (input.source) throw errors.sourceRouteUnsupportedInPrepare();
755
1193
  const depositInput = validateInput(input);
756
1194
  const params = await buildDepositParams(readClient, depositInput);
757
1195
  const { prepared } = await readClient.prepareCreateDeposit({
@@ -1065,7 +1503,56 @@ var cashEstimateJsonSchema = z.object({
1065
1503
  receiveAmount: z.number(),
1066
1504
  asOf: z.number(),
1067
1505
  oracleUpdatedAt: z.number().optional(),
1068
- stale: z.boolean().optional()
1506
+ stale: z.boolean().optional(),
1507
+ source: z.object({
1508
+ kind: z.literal("relay"),
1509
+ asset: z.object({
1510
+ chainId: z.number(),
1511
+ address: z.string(),
1512
+ symbol: z.string(),
1513
+ decimals: z.number(),
1514
+ name: z.string().optional(),
1515
+ isNative: z.boolean().optional()
1516
+ }),
1517
+ inputAmount: bigintString,
1518
+ relayQuote: z.object({
1519
+ requestId: z.string().optional(),
1520
+ source: z.object({
1521
+ chainId: z.number(),
1522
+ address: z.string(),
1523
+ symbol: z.string(),
1524
+ decimals: z.number(),
1525
+ name: z.string().optional(),
1526
+ isNative: z.boolean().optional()
1527
+ }),
1528
+ destination: z.object({
1529
+ chainId: z.number(),
1530
+ address: z.string(),
1531
+ symbol: z.string(),
1532
+ decimals: z.number(),
1533
+ name: z.string().optional(),
1534
+ isNative: z.boolean().optional()
1535
+ }),
1536
+ inputAmount: bigintString,
1537
+ outputAmount: bigintString,
1538
+ rate: z.number().optional(),
1539
+ timeEstimateSeconds: z.number().optional(),
1540
+ fees: z.unknown().optional(),
1541
+ txs: z.array(
1542
+ z.object({
1543
+ to: z.string(),
1544
+ data: z.string(),
1545
+ value: bigintString,
1546
+ chainId: z.number()
1547
+ })
1548
+ ),
1549
+ raw: z.unknown()
1550
+ })
1551
+ }).optional(),
1552
+ eta: z.object({
1553
+ seconds: z.number().optional(),
1554
+ label: z.string()
1555
+ }).optional()
1069
1556
  });
1070
1557
  var preparedTransactionJsonSchema = z.object({
1071
1558
  to: z.string(),
@@ -1089,7 +1576,12 @@ var cashoutResultJsonSchema = z.object({
1089
1576
  txHash: z.string(),
1090
1577
  escrowAddress: z.string(),
1091
1578
  onchainDepositId: bigintString,
1092
- order: cashOrderJsonSchema
1579
+ order: cashOrderJsonSchema,
1580
+ source: z.object({
1581
+ amount: bigintString,
1582
+ requestId: z.string().optional(),
1583
+ txHashes: z.array(z.string())
1584
+ }).optional()
1093
1585
  });
1094
1586
  var prepareResultJsonSchema = z.object({
1095
1587
  txs: z.array(preparedTransactionJsonSchema),
@@ -1109,6 +1601,49 @@ var cashCapabilitiesJsonSchema = z.object({
1109
1601
  chainId: z.number(),
1110
1602
  token: z.object({ address: z.string(), symbol: z.literal("USDC"), decimals: z.number() }),
1111
1603
  environment: z.enum(["production", "preproduction", "staging"]),
1604
+ destination: z.object({
1605
+ chainId: z.number(),
1606
+ token: z.object({ address: z.string(), symbol: z.literal("USDC"), decimals: z.number() })
1607
+ }),
1608
+ source: z.object({
1609
+ default: z.object({
1610
+ chainId: z.number(),
1611
+ token: z.object({ address: z.string(), symbol: z.literal("USDC"), decimals: z.number() })
1612
+ }),
1613
+ relay: z.object({
1614
+ destination: z.object({
1615
+ chainId: z.number(),
1616
+ address: z.string(),
1617
+ symbol: z.string(),
1618
+ decimals: z.number(),
1619
+ name: z.string().optional(),
1620
+ isNative: z.boolean().optional()
1621
+ }),
1622
+ chains: z.array(
1623
+ z.object({
1624
+ id: z.number(),
1625
+ name: z.string(),
1626
+ displayName: z.string(),
1627
+ disabled: z.boolean(),
1628
+ depositEnabled: z.boolean(),
1629
+ blockProductionLagging: z.boolean(),
1630
+ vmType: z.string().optional(),
1631
+ tokens: z.array(
1632
+ z.object({
1633
+ chainId: z.number(),
1634
+ address: z.string(),
1635
+ symbol: z.string(),
1636
+ decimals: z.number(),
1637
+ name: z.string().optional(),
1638
+ isNative: z.boolean().optional()
1639
+ })
1640
+ )
1641
+ })
1642
+ ),
1643
+ source: z.literal("relay-sdk"),
1644
+ asOf: z.number()
1645
+ }).optional()
1646
+ }),
1112
1647
  platforms: z.array(
1113
1648
  z.object({
1114
1649
  platform: z.string(),
@@ -1200,14 +1735,38 @@ function orderFromJson(json) {
1200
1735
  return withExplain(data);
1201
1736
  }
1202
1737
  function estimateToJson(estimate) {
1203
- return { ...estimate, amount: estimate.amount.toString() };
1738
+ return omitUndefined({
1739
+ ...estimate,
1740
+ amount: estimate.amount.toString(),
1741
+ source: estimate.source ? {
1742
+ ...estimate.source,
1743
+ inputAmount: estimate.source.inputAmount.toString(),
1744
+ relayQuote: {
1745
+ ...estimate.source.relayQuote,
1746
+ inputAmount: estimate.source.relayQuote.inputAmount.toString(),
1747
+ outputAmount: estimate.source.relayQuote.outputAmount.toString(),
1748
+ txs: estimate.source.relayQuote.txs.map(preparedTxToJson),
1749
+ raw: sanitizeRelayQuoteRaw(estimate.source.relayQuote.raw)
1750
+ }
1751
+ } : void 0
1752
+ });
1204
1753
  }
1205
1754
  function estimateFromJson(json) {
1206
1755
  const parsed = cashEstimateJsonSchema.parse(json);
1207
1756
  return omitUndefined({
1208
1757
  ...parsed,
1209
1758
  currency: parsed.currency,
1210
- amount: BigInt(parsed.amount)
1759
+ amount: BigInt(parsed.amount),
1760
+ source: parsed.source ? {
1761
+ ...parsed.source,
1762
+ inputAmount: BigInt(parsed.source.inputAmount),
1763
+ relayQuote: {
1764
+ ...parsed.source.relayQuote,
1765
+ inputAmount: BigInt(parsed.source.relayQuote.inputAmount),
1766
+ outputAmount: BigInt(parsed.source.relayQuote.outputAmount),
1767
+ txs: parsed.source.relayQuote.txs.map(preparedTxFromJson)
1768
+ }
1769
+ } : void 0
1211
1770
  });
1212
1771
  }
1213
1772
  function preparedTxToJson(tx) {
@@ -1229,23 +1788,31 @@ function preparedStepFromJson(json) {
1229
1788
  return cashPreparedStepJsonSchema.parse(json);
1230
1789
  }
1231
1790
  function cashoutResultToJson(result) {
1232
- return {
1791
+ return omitUndefined({
1233
1792
  depositId: result.depositId,
1234
1793
  txHash: result.txHash,
1235
1794
  escrowAddress: result.escrowAddress,
1236
1795
  onchainDepositId: result.onchainDepositId.toString(),
1237
- order: orderToJson(result.order)
1238
- };
1796
+ order: orderToJson(result.order),
1797
+ source: result.source ? {
1798
+ ...result.source,
1799
+ amount: result.source.amount.toString()
1800
+ } : void 0
1801
+ });
1239
1802
  }
1240
1803
  function cashoutResultFromJson(json) {
1241
1804
  const parsed = cashoutResultJsonSchema.parse(json);
1242
- return {
1805
+ return omitUndefined({
1243
1806
  depositId: parsed.depositId,
1244
1807
  txHash: parsed.txHash,
1245
1808
  escrowAddress: parsed.escrowAddress,
1246
1809
  onchainDepositId: BigInt(parsed.onchainDepositId),
1247
- order: orderFromJson(parsed.order)
1248
- };
1810
+ order: orderFromJson(parsed.order),
1811
+ source: parsed.source ? {
1812
+ ...parsed.source,
1813
+ amount: BigInt(parsed.source.amount)
1814
+ } : void 0
1815
+ });
1249
1816
  }
1250
1817
  function prepareResultToJson(result) {
1251
1818
  return {
@@ -1304,6 +1871,10 @@ function capabilitiesFromJson(json) {
1304
1871
  const parsed = cashCapabilitiesJsonSchema.parse(json);
1305
1872
  return {
1306
1873
  ...parsed,
1874
+ source: {
1875
+ default: parsed.source.default,
1876
+ ...parsed.source.relay ? { relay: parsed.source.relay } : {}
1877
+ },
1307
1878
  platforms: parsed.platforms.map((p) => ({
1308
1879
  ...p,
1309
1880
  currencies: p.currencies