@zkp2p/cash 0.1.0-dev.0 → 0.1.2

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,308 @@ 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 = 7;
433
+ var ETA_WINDOW_SECONDS = ETA_WINDOW_DAYS * 24 * 60 * 60;
434
+ var ETA_SAMPLE_LIMIT = 250;
435
+ var FULFILLED = /* @__PURE__ */ new Set(["FULFILLED", "MANUALLY_RELEASED"]);
436
+ function toUnixSeconds2(value) {
437
+ if (value === null || value === void 0 || value === "") return void 0;
438
+ if (value instanceof Date) {
439
+ const seconds = Math.floor(value.getTime() / 1e3);
440
+ return Number.isFinite(seconds) && seconds > 0 ? seconds : void 0;
441
+ }
442
+ if (typeof value === "string" && /[TZ:-]/.test(value)) {
443
+ const parsed = Date.parse(value);
444
+ if (Number.isFinite(parsed)) return Math.floor(parsed / 1e3);
445
+ }
446
+ const n = Number(value);
447
+ return Number.isFinite(n) && n > 0 ? n : void 0;
448
+ }
449
+ function median(values) {
450
+ if (values.length === 0) return void 0;
451
+ const sorted = [...values].sort((a, b) => a - b);
452
+ const mid = Math.floor(sorted.length / 2);
453
+ return sorted.length % 2 === 1 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
454
+ }
455
+ function etaLabel(seconds) {
456
+ if (seconds === void 0) return "Recent fill time unavailable";
457
+ if (seconds < 60) return "Usually starts in under a minute";
458
+ const minutes = Math.max(1, Math.round(seconds / 60));
459
+ if (minutes < 60) return `Usually starts in about ${minutes} min`;
460
+ const hours = Math.max(1, Math.round(minutes / 60));
461
+ return `Usually starts in about ${hours} hr`;
462
+ }
463
+ function matchesPayout(deposit, environment, platform, currency) {
464
+ const payouts = derivePayouts(
465
+ deposit.paymentMethods ?? [],
466
+ deposit.currencies ?? [],
467
+ getPaymentMethodsCatalog(BASE_CHAIN_ID, environment)
468
+ );
469
+ if (payouts.length === 0) return true;
470
+ return payouts.some(
471
+ (payout) => (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 = await client.indexer.getDepositsWithRelations(
478
+ { chainId: BASE_CHAIN_ID },
479
+ { limit: ETA_SAMPLE_LIMIT, orderBy: "updatedAt", orderDirection: "desc" },
480
+ { includeIntents: true, intentStatuses: ["FULFILLED", "MANUALLY_RELEASED"] }
481
+ );
482
+ const firstFillLatencies = [];
483
+ for (const deposit of deposits) {
484
+ const createdAt = toUnixSeconds2(deposit.createdAt ?? deposit.timestamp);
485
+ if (createdAt === void 0 || createdAt < windowStart) continue;
486
+ if (!matchesPayout(deposit, input.environment, input.platform, input.currency)) continue;
487
+ const fulfilled = (deposit.intents ?? []).filter((intent) => intent.status != null && FULFILLED.has(intent.status)).map((intent) => ({
488
+ fulfilledAt: toUnixSeconds2(intent.fulfillTimestamp)
489
+ })).filter(
490
+ (intent) => intent.fulfilledAt !== void 0 && intent.fulfilledAt >= createdAt
491
+ ).sort((a, b) => a.fulfilledAt - b.fulfilledAt);
492
+ if (fulfilled.length === 0) continue;
493
+ firstFillLatencies.push(fulfilled[0].fulfilledAt - createdAt);
494
+ }
495
+ const seconds = median(firstFillLatencies);
496
+ return {
497
+ ...seconds !== void 0 ? { seconds } : {},
498
+ label: etaLabel(seconds)
499
+ };
500
+ }
501
+ var RELAY_API_URL = MAINNET_RELAY_API;
502
+ var NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
503
+ var BASE_USDC_ASSET = {
504
+ chainId: BASE_CHAIN_ID,
505
+ address: BASE_USDC_ADDRESS,
506
+ symbol: "USDC",
507
+ decimals: USDC_DECIMALS,
508
+ name: "USD Coin"
509
+ };
510
+ function relayClient(options = {}) {
511
+ if (options.client) return options.client;
512
+ return createClient({
513
+ baseApiUrl: options.apiUrl ?? RELAY_API_URL,
514
+ source: options.source ?? "peer-cash",
515
+ ...options.apiKey ? { apiKey: options.apiKey } : {},
516
+ ...options.chains ? { chains: options.chains } : {}
517
+ });
518
+ }
519
+ function asRecord(value) {
520
+ return value !== null && typeof value === "object" ? value : {};
521
+ }
522
+ function asString(value) {
523
+ return typeof value === "string" && value.length > 0 ? value : void 0;
524
+ }
525
+ function asNumber(value) {
526
+ const n = Number(value);
527
+ return Number.isFinite(n) ? n : void 0;
528
+ }
529
+ function normalizeToken(chainId, token) {
530
+ const row = asRecord(token);
531
+ const address = asString(row.address);
532
+ const symbol = asString(row.symbol);
533
+ const decimals = asNumber(row.decimals);
534
+ const name = asString(row.name);
535
+ if (!address || !symbol || decimals === void 0) return null;
536
+ const metadata = asRecord(row.metadata);
537
+ return {
538
+ chainId,
539
+ address,
540
+ symbol,
541
+ decimals,
542
+ ...name ? { name } : {},
543
+ ...metadata.isNative === true || address.toLowerCase() === NATIVE_TOKEN_ADDRESS ? { isNative: true } : {}
544
+ };
545
+ }
546
+ function normalizeTx(data, chainId) {
547
+ const row = asRecord(data);
548
+ const to = asString(row.to);
549
+ const calldata = asString(row.data) ?? "0x";
550
+ if (!to) return null;
551
+ return {
552
+ to,
553
+ data: calldata,
554
+ value: BigInt(String(row.value ?? "0")),
555
+ chainId: asNumber(row.chainId) ?? chainId
556
+ };
557
+ }
558
+ function normalizeChain(chain) {
559
+ const row = asRecord(chain);
560
+ const tokenRows = [
561
+ chain.currency,
562
+ ...chain.featuredTokens ?? [],
563
+ ...chain.erc20Currencies ?? [],
564
+ ...chain.solverCurrencies ?? []
565
+ ];
566
+ const tokens = /* @__PURE__ */ new Map();
567
+ for (const token of tokenRows) {
568
+ const normalizedToken = normalizeToken(chain.id, token);
569
+ if (normalizedToken) tokens.set(normalizedToken.address.toLowerCase(), normalizedToken);
570
+ }
571
+ return {
572
+ id: chain.id,
573
+ name: chain.name,
574
+ displayName: chain.displayName,
575
+ disabled: row.disabled === true,
576
+ depositEnabled: chain.depositEnabled ?? false,
577
+ blockProductionLagging: chain.blockProductionLagging ?? false,
578
+ ...chain.vmType ? { vmType: chain.vmType } : {},
579
+ tokens: [...tokens.values()].sort((a, b) => a.symbol.localeCompare(b.symbol))
580
+ };
581
+ }
582
+ function isSupportedEvmChain(chain) {
583
+ return chain.vmType === void 0 || chain.vmType === "evm";
584
+ }
585
+ function quoteRequestId(quote) {
586
+ return quote.steps.map((step) => step.requestId).find((id) => id !== void 0);
587
+ }
588
+ function quoteSourceChainId(quote) {
589
+ const details = asRecord(quote.details);
590
+ const currencyIn = asRecord(details.currencyIn);
591
+ const sourceCurrency = asRecord(currencyIn.currency);
592
+ return asNumber(sourceCurrency.chainId);
593
+ }
594
+ function sanitizeRelayQuoteRaw(quote) {
595
+ if (!quote.request) return quote;
596
+ const request = { ...quote.request };
597
+ delete request.headers;
598
+ return { ...quote, request };
599
+ }
600
+ async function resolveRelayChains(options, client, config = {}) {
601
+ const preferInjectedClientChains = config.preferInjectedClientChains ?? true;
602
+ 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());
603
+ client.chains = chains;
604
+ return chains;
605
+ }
606
+ function relayQuoteFromExecute(input, quote) {
607
+ const details = asRecord(quote.details);
608
+ const currencyIn = asRecord(details.currencyIn);
609
+ const currencyOut = asRecord(details.currencyOut);
610
+ const sourceCurrency = asRecord(currencyIn.currency);
611
+ const destinationCurrency = asRecord(currencyOut.currency);
612
+ const source = normalizeToken(input.source.chainId, sourceCurrency) ?? {
613
+ chainId: input.source.chainId,
614
+ address: input.source.currency,
615
+ symbol: "TOKEN",
616
+ decimals: 0
617
+ };
618
+ const destination = normalizeToken(BASE_CHAIN_ID, destinationCurrency) ?? BASE_USDC_ASSET;
619
+ const txs = quote.steps.flatMap(
620
+ (step) => step.items.map((item) => normalizeTx(item.data, input.source.chainId)).filter((tx) => tx !== null)
621
+ );
622
+ const outputAmount = BigInt(
623
+ String(currencyOut.minimumAmount ?? currencyOut.amount ?? input.amount.toString())
624
+ );
625
+ const requestId = quoteRequestId(quote);
626
+ const rate = asNumber(details.rate);
627
+ const timeEstimateSeconds = asNumber(details.timeEstimate);
628
+ return {
629
+ ...requestId ? { requestId } : {},
630
+ source,
631
+ destination,
632
+ inputAmount: BigInt(String(currencyIn.amount ?? input.amount.toString())),
633
+ outputAmount,
634
+ ...rate !== void 0 ? { rate } : {},
635
+ ...timeEstimateSeconds !== void 0 ? { timeEstimateSeconds } : {},
636
+ ...quote.fees !== void 0 ? { fees: quote.fees } : {},
637
+ txs,
638
+ raw: sanitizeRelayQuoteRaw(quote)
639
+ };
640
+ }
641
+ async function readRelaySourceCapabilities(options = {}) {
642
+ const client = relayClient(options);
643
+ const chains = await resolveRelayChains(options, client);
644
+ return {
645
+ destination: BASE_USDC_ASSET,
646
+ chains: chains.map(normalizeChain).filter((chain) => chain.tokens.length > 0 && isSupportedEvmChain(chain)).sort((a, b) => a.displayName.localeCompare(b.displayName)),
647
+ source: "relay-sdk",
648
+ asOf: Math.floor(Date.now() / 1e3)
649
+ };
650
+ }
651
+ async function quoteRelayToBaseUsdc(input, options = {}) {
652
+ const client = relayClient(options);
653
+ const quote = await client.actions.getQuote(
654
+ {
655
+ chainId: input.source.chainId,
656
+ currency: input.source.currency,
657
+ toChainId: BASE_CHAIN_ID,
658
+ toCurrency: BASE_USDC_ADDRESS,
659
+ user: input.user,
660
+ recipient: input.recipient ?? input.user,
661
+ amount: input.amount.toString(),
662
+ tradeType: input.tradeType ?? "EXACT_INPUT"
663
+ },
664
+ false
665
+ );
666
+ return relayQuoteFromExecute(input, quote);
667
+ }
668
+ async function executeRelayQuote(quote, wallet, options = {}) {
669
+ const client = relayClient(options.relay);
670
+ const sourceChainId = quoteSourceChainId(quote);
671
+ if (sourceChainId !== void 0 && !(client.chains ?? []).some((chain) => chain.id === sourceChainId)) {
672
+ await resolveRelayChains(options.relay ?? {}, client, { preferInjectedClientChains: false });
673
+ }
674
+ const { data } = await client.actions.execute({
675
+ quote,
676
+ wallet,
677
+ ...options.onProgress ? { onProgress: options.onProgress } : {},
678
+ ...options.disableCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: options.disableCapabilitiesCheck } : {}
679
+ });
680
+ const requestId = quoteRequestId(data);
681
+ return {
682
+ ...requestId ? { requestId } : {},
683
+ txHashes: data.steps.flatMap(
684
+ (step) => step.items.flatMap((item) => (item.txHashes ?? []).map((tx) => tx.txHash))
685
+ ),
686
+ quote: data
687
+ };
688
+ }
689
+ async function readRelayStatus(requestId, options = {}) {
690
+ const client = relayClient(options);
691
+ const response = await client.utils.request({
692
+ url: `${client.baseApiUrl}/intents/status/v3`,
693
+ method: "get",
694
+ params: { requestId }
695
+ });
696
+ const root = asRecord(response.data);
697
+ const status = asString(root.status);
698
+ if (status !== "refund" && status !== "waiting" && status !== "depositing" && status !== "failure" && status !== "pending" && status !== "submitted" && status !== "success") {
699
+ throw new Error(`Relay returned unknown status: ${String(root.status)}`);
700
+ }
701
+ const details = asString(root.details);
702
+ const updatedAt = asNumber(root.updatedAt);
703
+ const originChainId = asNumber(root.originChainId);
704
+ const destinationChainId = asNumber(root.destinationChainId);
705
+ const quoteCreatedAt = asNumber(root.quoteCreatedAt);
706
+ return {
707
+ requestId,
708
+ status,
709
+ ...details ? { details } : {},
710
+ inTxHashes: Array.isArray(root.inTxHashes) ? root.inTxHashes.map(String) : [],
711
+ txHashes: Array.isArray(root.txHashes) ? root.txHashes.map(String) : [],
712
+ ...updatedAt !== void 0 ? { updatedAt } : {},
713
+ ...originChainId !== void 0 ? { originChainId } : {},
714
+ ...destinationChainId !== void 0 ? { destinationChainId } : {},
715
+ ...quoteCreatedAt !== void 0 ? { quoteCreatedAt } : {},
716
+ raw: response.data
717
+ };
718
+ }
719
+
720
+ // src/client/estimate.ts
427
721
  var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
428
722
  var CHAINLINK_LATEST_ROUND_ABI = [
429
723
  {
@@ -441,14 +735,25 @@ var CHAINLINK_LATEST_ROUND_ABI = [
441
735
  }
442
736
  ];
443
737
  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
- }
738
+ async function readEstimate(publicClient, input, context = {}) {
739
+ const { currency } = input;
449
740
  if (!isMarketRateSupported(currency)) {
450
741
  throw errors.oracleUnsupportedCurrency(currency);
451
742
  }
743
+ const relayQuote = input.source !== void 0 ? await quoteRelayToBaseUsdc(
744
+ {
745
+ user: input.source.user,
746
+ amount: input.amount,
747
+ source: { chainId: input.source.chainId, currency: input.source.currency },
748
+ ...input.source.recipient ? { recipient: input.source.recipient } : {},
749
+ ...input.source.tradeType ? { tradeType: input.source.tradeType } : {}
750
+ },
751
+ context.relay
752
+ ) : void 0;
753
+ const amount = relayQuote?.outputAmount ?? input.amount;
754
+ if (amount < MIN_CASHOUT_AMOUNT) {
755
+ throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
756
+ }
452
757
  const feedConfig = CHAINLINK_ORACLE_FEEDS[currency];
453
758
  const asOf = Math.floor(Date.now() / 1e3);
454
759
  let rate;
@@ -471,7 +776,7 @@ async function readEstimate(publicClient, input) {
471
776
  rate = feedConfig.invert ? 1 / price : price;
472
777
  }
473
778
  const stale = oracleUpdatedAt !== void 0 && asOf - oracleUpdatedAt > DEFAULT_MAX_STALENESS_SECONDS;
474
- return {
779
+ const estimate = {
475
780
  kind: "oracle-estimate",
476
781
  currency,
477
782
  amount,
@@ -479,8 +784,27 @@ async function readEstimate(publicClient, input) {
479
784
  receiveAmount: Number(amount) / 10 ** USDC_DECIMALS * rate,
480
785
  asOf,
481
786
  ...oracleUpdatedAt !== void 0 ? { oracleUpdatedAt } : {},
482
- ...stale ? { stale: true } : {}
787
+ ...stale ? { stale: true } : {},
788
+ ...relayQuote ? {
789
+ source: {
790
+ kind: "relay",
791
+ asset: relayQuote.source,
792
+ inputAmount: relayQuote.inputAmount,
793
+ relayQuote
794
+ }
795
+ } : {}
483
796
  };
797
+ if (context.indexerClient && context.environment) {
798
+ try {
799
+ estimate.eta = await readFillEta(context.indexerClient, {
800
+ environment: context.environment,
801
+ currency,
802
+ ...input.platform ? { platform: input.platform } : {}
803
+ });
804
+ } catch {
805
+ }
806
+ }
807
+ return estimate;
484
808
  }
485
809
 
486
810
  // src/client/createCashClient.ts
@@ -576,9 +900,8 @@ function createCashClient(options) {
576
900
  }
577
901
  return client;
578
902
  }
579
- function validateInput(input) {
580
- const { amount, receive } = input;
581
- if (amount < MIN_CASHOUT_AMOUNT) throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
903
+ function validatePayout(input) {
904
+ const { receive } = input;
582
905
  const catalog = getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
583
906
  if (!catalog[receive.platform]) throw errors.unsupportedPlatform(receive.platform);
584
907
  if (!isMarketRateSupported(receive.currency)) {
@@ -588,7 +911,6 @@ function createCashClient(options) {
588
911
  throw errors.payeeVerificationRequired(receive.platform);
589
912
  }
590
913
  return {
591
- amount,
592
914
  payouts: [
593
915
  {
594
916
  processorName: receive.platform,
@@ -599,6 +921,12 @@ function createCashClient(options) {
599
921
  ...input.intentAmountRange ? { intentAmountRange: input.intentAmountRange } : {}
600
922
  };
601
923
  }
924
+ function validateInput(input) {
925
+ if (input.amount < MIN_CASHOUT_AMOUNT) {
926
+ throw errors.amountBelowMinimum(input.amount, MIN_CASHOUT_AMOUNT);
927
+ }
928
+ return { amount: input.amount, ...validatePayout(input) };
929
+ }
602
930
  async function buildDepositParams(client, depositInput) {
603
931
  try {
604
932
  return await prepareCashDepositParams(client, depositInput);
@@ -675,6 +1003,14 @@ function createCashClient(options) {
675
1003
  if (!order.isInFlight) throw errors.orderNotActive(depositId);
676
1004
  return escrowContext(depositId);
677
1005
  }
1006
+ function capabilities(capabilityOptions) {
1007
+ const baseCapabilities = buildCapabilities(environment);
1008
+ if (!capabilityOptions?.includeRelaySources) return baseCapabilities;
1009
+ return readRelaySourceCapabilities(options.relay).then((relay) => ({
1010
+ ...baseCapabilities,
1011
+ source: { ...baseCapabilities.source, relay }
1012
+ }));
1013
+ }
678
1014
  async function settleAllowance(client, token, owner, escrow, amount) {
679
1015
  let allowance;
680
1016
  try {
@@ -703,18 +1039,109 @@ function createCashClient(options) {
703
1039
  throw errors.allowanceNotVisible(amount);
704
1040
  }
705
1041
  return {
706
- capabilities() {
707
- return buildCapabilities(environment);
1042
+ capabilities,
1043
+ async sourceCapabilities() {
1044
+ return readRelaySourceCapabilities(options.relay);
1045
+ },
1046
+ async quoteSource(input) {
1047
+ return quoteRelayToBaseUsdc(input, options.relay);
1048
+ },
1049
+ async executeSourceQuote(quote, opts) {
1050
+ return executeRelayQuote(quote, opts.signer, {
1051
+ ...options.relay ? { relay: options.relay } : {},
1052
+ ...opts.onProgress ? { onProgress: opts.onProgress } : {},
1053
+ ...opts.disableCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: opts.disableCapabilitiesCheck } : {}
1054
+ });
1055
+ },
1056
+ async relayStatus(requestId) {
1057
+ return readRelayStatus(requestId, options.relay);
708
1058
  },
709
1059
  async estimate(input) {
710
- return readEstimate(readClient.publicClient, input);
1060
+ return readEstimate(readClient.publicClient, input, {
1061
+ indexerClient: readClient,
1062
+ environment,
1063
+ ...options.relay ? { relay: options.relay } : {}
1064
+ });
711
1065
  },
712
1066
  async cashout(input, opts) {
713
- const depositInput = validateInput(input);
714
1067
  const client = signingClient("cashout", opts);
1068
+ const owner = opts.signer.account.address;
1069
+ const payoutInput = validatePayout(input);
1070
+ let sourceResult;
1071
+ let cashoutAmount = input.amount;
1072
+ if (input.source) {
1073
+ const sourceSigner = opts.sourceSigner ?? (input.source.chainId === BASE_CHAIN_ID ? opts.signer : void 0);
1074
+ if (!sourceSigner?.account) throw errors.signerRequired("source cashout");
1075
+ if (input.source.recipient !== void 0 && input.source.recipient.toLowerCase() !== owner.toLowerCase()) {
1076
+ throw errors.sourceRecipientMismatch(input.source.recipient, owner);
1077
+ }
1078
+ const relayQuote = await quoteRelayToBaseUsdc(
1079
+ {
1080
+ user: sourceSigner.account.address,
1081
+ amount: input.amount,
1082
+ source: { chainId: input.source.chainId, currency: input.source.currency },
1083
+ recipient: owner,
1084
+ ...input.source.tradeType ? { tradeType: input.source.tradeType } : {}
1085
+ },
1086
+ options.relay
1087
+ );
1088
+ if (relayQuote.outputAmount < MIN_CASHOUT_AMOUNT) {
1089
+ throw errors.amountBelowMinimum(relayQuote.outputAmount, MIN_CASHOUT_AMOUNT);
1090
+ }
1091
+ cashoutAmount = relayQuote.outputAmount;
1092
+ const depositInput2 = { amount: cashoutAmount, ...payoutInput };
1093
+ const params2 = await buildDepositParams(client, depositInput2);
1094
+ const escrow2 = client.escrowV2Address ?? client.escrowAddress;
1095
+ await settleAllowance(client, params2.token, owner, escrow2, depositInput2.amount);
1096
+ const executed = await executeRelayQuote(relayQuote.raw, sourceSigner, {
1097
+ ...options.relay ? { relay: options.relay } : {},
1098
+ ...opts.onSourceProgress ? { onProgress: opts.onSourceProgress } : {},
1099
+ ...opts.disableSourceCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: opts.disableSourceCapabilitiesCheck } : {}
1100
+ });
1101
+ sourceResult = {
1102
+ amount: cashoutAmount,
1103
+ ...executed.requestId ? { requestId: executed.requestId } : {},
1104
+ txHashes: executed.txHashes
1105
+ };
1106
+ const attributedParams2 = { ...params2, txOverrides: attribution };
1107
+ const send2 = async () => {
1108
+ try {
1109
+ return (await client.createDeposit(attributedParams2)).hash;
1110
+ } catch (err) {
1111
+ if (err instanceof Error && /exceeds allowance/i.test(err.message)) {
1112
+ await sleep(2e3);
1113
+ return (await client.createDeposit(attributedParams2)).hash;
1114
+ }
1115
+ throw err;
1116
+ }
1117
+ };
1118
+ let hash2;
1119
+ try {
1120
+ hash2 = await send2();
1121
+ } catch (err) {
1122
+ throw mapChainError("createDeposit", err);
1123
+ }
1124
+ const receipt2 = await client.publicClient.waitForTransactionReceipt({ hash: hash2 });
1125
+ if (receipt2.status === "reverted") throw errors.transactionFailed(hash2);
1126
+ const abi2 = client.escrowV2Abi ?? client.escrowAbi;
1127
+ const resolved2 = resolveCashDepositId({ logs: receipt2.logs, abi: abi2 });
1128
+ if (!resolved2) throw errors.depositResolutionFailed(hash2);
1129
+ const order2 = deriveCashOrder(resolved2.compositeId, [], {
1130
+ remainingAmount: depositInput2.amount,
1131
+ status: "ACTIVE"
1132
+ });
1133
+ return {
1134
+ depositId: resolved2.compositeId,
1135
+ txHash: hash2,
1136
+ escrowAddress: resolved2.escrowAddress,
1137
+ onchainDepositId: resolved2.onchainDepositId,
1138
+ order: order2,
1139
+ source: sourceResult
1140
+ };
1141
+ }
1142
+ const depositInput = validateInput(input);
715
1143
  const params = await buildDepositParams(client, depositInput);
716
1144
  const escrow = client.escrowV2Address ?? client.escrowAddress;
717
- const owner = opts.signer.account.address;
718
1145
  await settleAllowance(client, params.token, owner, escrow, depositInput.amount);
719
1146
  const attributedParams = { ...params, txOverrides: attribution };
720
1147
  const send = async () => {
@@ -748,10 +1175,12 @@ function createCashClient(options) {
748
1175
  txHash: hash,
749
1176
  escrowAddress: resolved.escrowAddress,
750
1177
  onchainDepositId: resolved.onchainDepositId,
751
- order
1178
+ order,
1179
+ ...sourceResult ? { source: sourceResult } : {}
752
1180
  };
753
1181
  },
754
1182
  async prepare(input) {
1183
+ if (input.source) throw errors.sourceRouteUnsupportedInPrepare();
755
1184
  const depositInput = validateInput(input);
756
1185
  const params = await buildDepositParams(readClient, depositInput);
757
1186
  const { prepared } = await readClient.prepareCreateDeposit({
@@ -1065,7 +1494,56 @@ var cashEstimateJsonSchema = z.object({
1065
1494
  receiveAmount: z.number(),
1066
1495
  asOf: z.number(),
1067
1496
  oracleUpdatedAt: z.number().optional(),
1068
- stale: z.boolean().optional()
1497
+ stale: z.boolean().optional(),
1498
+ source: z.object({
1499
+ kind: z.literal("relay"),
1500
+ asset: z.object({
1501
+ chainId: z.number(),
1502
+ address: z.string(),
1503
+ symbol: z.string(),
1504
+ decimals: z.number(),
1505
+ name: z.string().optional(),
1506
+ isNative: z.boolean().optional()
1507
+ }),
1508
+ inputAmount: bigintString,
1509
+ relayQuote: z.object({
1510
+ requestId: z.string().optional(),
1511
+ source: z.object({
1512
+ chainId: z.number(),
1513
+ address: z.string(),
1514
+ symbol: z.string(),
1515
+ decimals: z.number(),
1516
+ name: z.string().optional(),
1517
+ isNative: z.boolean().optional()
1518
+ }),
1519
+ destination: z.object({
1520
+ chainId: z.number(),
1521
+ address: z.string(),
1522
+ symbol: z.string(),
1523
+ decimals: z.number(),
1524
+ name: z.string().optional(),
1525
+ isNative: z.boolean().optional()
1526
+ }),
1527
+ inputAmount: bigintString,
1528
+ outputAmount: bigintString,
1529
+ rate: z.number().optional(),
1530
+ timeEstimateSeconds: z.number().optional(),
1531
+ fees: z.unknown().optional(),
1532
+ txs: z.array(
1533
+ z.object({
1534
+ to: z.string(),
1535
+ data: z.string(),
1536
+ value: bigintString,
1537
+ chainId: z.number()
1538
+ })
1539
+ ),
1540
+ raw: z.unknown()
1541
+ })
1542
+ }).optional(),
1543
+ eta: z.object({
1544
+ seconds: z.number().optional(),
1545
+ label: z.string()
1546
+ }).optional()
1069
1547
  });
1070
1548
  var preparedTransactionJsonSchema = z.object({
1071
1549
  to: z.string(),
@@ -1089,7 +1567,12 @@ var cashoutResultJsonSchema = z.object({
1089
1567
  txHash: z.string(),
1090
1568
  escrowAddress: z.string(),
1091
1569
  onchainDepositId: bigintString,
1092
- order: cashOrderJsonSchema
1570
+ order: cashOrderJsonSchema,
1571
+ source: z.object({
1572
+ amount: bigintString,
1573
+ requestId: z.string().optional(),
1574
+ txHashes: z.array(z.string())
1575
+ }).optional()
1093
1576
  });
1094
1577
  var prepareResultJsonSchema = z.object({
1095
1578
  txs: z.array(preparedTransactionJsonSchema),
@@ -1109,6 +1592,49 @@ var cashCapabilitiesJsonSchema = z.object({
1109
1592
  chainId: z.number(),
1110
1593
  token: z.object({ address: z.string(), symbol: z.literal("USDC"), decimals: z.number() }),
1111
1594
  environment: z.enum(["production", "preproduction", "staging"]),
1595
+ destination: z.object({
1596
+ chainId: z.number(),
1597
+ token: z.object({ address: z.string(), symbol: z.literal("USDC"), decimals: z.number() })
1598
+ }),
1599
+ source: z.object({
1600
+ default: z.object({
1601
+ chainId: z.number(),
1602
+ token: z.object({ address: z.string(), symbol: z.literal("USDC"), decimals: z.number() })
1603
+ }),
1604
+ relay: z.object({
1605
+ destination: z.object({
1606
+ chainId: z.number(),
1607
+ address: z.string(),
1608
+ symbol: z.string(),
1609
+ decimals: z.number(),
1610
+ name: z.string().optional(),
1611
+ isNative: z.boolean().optional()
1612
+ }),
1613
+ chains: z.array(
1614
+ z.object({
1615
+ id: z.number(),
1616
+ name: z.string(),
1617
+ displayName: z.string(),
1618
+ disabled: z.boolean(),
1619
+ depositEnabled: z.boolean(),
1620
+ blockProductionLagging: z.boolean(),
1621
+ vmType: z.string().optional(),
1622
+ tokens: z.array(
1623
+ z.object({
1624
+ chainId: z.number(),
1625
+ address: z.string(),
1626
+ symbol: z.string(),
1627
+ decimals: z.number(),
1628
+ name: z.string().optional(),
1629
+ isNative: z.boolean().optional()
1630
+ })
1631
+ )
1632
+ })
1633
+ ),
1634
+ source: z.literal("relay-sdk"),
1635
+ asOf: z.number()
1636
+ }).optional()
1637
+ }),
1112
1638
  platforms: z.array(
1113
1639
  z.object({
1114
1640
  platform: z.string(),
@@ -1200,14 +1726,38 @@ function orderFromJson(json) {
1200
1726
  return withExplain(data);
1201
1727
  }
1202
1728
  function estimateToJson(estimate) {
1203
- return { ...estimate, amount: estimate.amount.toString() };
1729
+ return omitUndefined({
1730
+ ...estimate,
1731
+ amount: estimate.amount.toString(),
1732
+ source: estimate.source ? {
1733
+ ...estimate.source,
1734
+ inputAmount: estimate.source.inputAmount.toString(),
1735
+ relayQuote: {
1736
+ ...estimate.source.relayQuote,
1737
+ inputAmount: estimate.source.relayQuote.inputAmount.toString(),
1738
+ outputAmount: estimate.source.relayQuote.outputAmount.toString(),
1739
+ txs: estimate.source.relayQuote.txs.map(preparedTxToJson),
1740
+ raw: sanitizeRelayQuoteRaw(estimate.source.relayQuote.raw)
1741
+ }
1742
+ } : void 0
1743
+ });
1204
1744
  }
1205
1745
  function estimateFromJson(json) {
1206
1746
  const parsed = cashEstimateJsonSchema.parse(json);
1207
1747
  return omitUndefined({
1208
1748
  ...parsed,
1209
1749
  currency: parsed.currency,
1210
- amount: BigInt(parsed.amount)
1750
+ amount: BigInt(parsed.amount),
1751
+ source: parsed.source ? {
1752
+ ...parsed.source,
1753
+ inputAmount: BigInt(parsed.source.inputAmount),
1754
+ relayQuote: {
1755
+ ...parsed.source.relayQuote,
1756
+ inputAmount: BigInt(parsed.source.relayQuote.inputAmount),
1757
+ outputAmount: BigInt(parsed.source.relayQuote.outputAmount),
1758
+ txs: parsed.source.relayQuote.txs.map(preparedTxFromJson)
1759
+ }
1760
+ } : void 0
1211
1761
  });
1212
1762
  }
1213
1763
  function preparedTxToJson(tx) {
@@ -1229,23 +1779,31 @@ function preparedStepFromJson(json) {
1229
1779
  return cashPreparedStepJsonSchema.parse(json);
1230
1780
  }
1231
1781
  function cashoutResultToJson(result) {
1232
- return {
1782
+ return omitUndefined({
1233
1783
  depositId: result.depositId,
1234
1784
  txHash: result.txHash,
1235
1785
  escrowAddress: result.escrowAddress,
1236
1786
  onchainDepositId: result.onchainDepositId.toString(),
1237
- order: orderToJson(result.order)
1238
- };
1787
+ order: orderToJson(result.order),
1788
+ source: result.source ? {
1789
+ ...result.source,
1790
+ amount: result.source.amount.toString()
1791
+ } : void 0
1792
+ });
1239
1793
  }
1240
1794
  function cashoutResultFromJson(json) {
1241
1795
  const parsed = cashoutResultJsonSchema.parse(json);
1242
- return {
1796
+ return omitUndefined({
1243
1797
  depositId: parsed.depositId,
1244
1798
  txHash: parsed.txHash,
1245
1799
  escrowAddress: parsed.escrowAddress,
1246
1800
  onchainDepositId: BigInt(parsed.onchainDepositId),
1247
- order: orderFromJson(parsed.order)
1248
- };
1801
+ order: orderFromJson(parsed.order),
1802
+ source: parsed.source ? {
1803
+ ...parsed.source,
1804
+ amount: BigInt(parsed.source.amount)
1805
+ } : void 0
1806
+ });
1249
1807
  }
1250
1808
  function prepareResultToJson(result) {
1251
1809
  return {
@@ -1304,6 +1862,10 @@ function capabilitiesFromJson(json) {
1304
1862
  const parsed = cashCapabilitiesJsonSchema.parse(json);
1305
1863
  return {
1306
1864
  ...parsed,
1865
+ source: {
1866
+ default: parsed.source.default,
1867
+ ...parsed.source.relay ? { relay: parsed.source.relay } : {}
1868
+ },
1307
1869
  platforms: parsed.platforms.map((p) => ({
1308
1870
  ...p,
1309
1871
  currencies: p.currencies