@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.cjs CHANGED
@@ -3,6 +3,8 @@
3
3
  var viem = require('viem');
4
4
  var chains = require('viem/chains');
5
5
  var sdk = require('@zkp2p/sdk');
6
+ var relaySdk = require('@relayprotocol/relay-sdk');
7
+ var chainUtils = require('@relayprotocol/relay-sdk/chain-utils');
6
8
  var zod = require('zod');
7
9
 
8
10
  // src/client/createCashClient.ts
@@ -430,10 +432,13 @@ function buildCapabilities(environment) {
430
432
  };
431
433
  }).filter((p) => p.currencies.length > 0).sort((a, b) => a.platform.localeCompare(b.platform));
432
434
  const currencies = [...new Set(platforms.flatMap((p) => p.currencies))].sort();
435
+ const baseUsdc = { address: BASE_USDC_ADDRESS, symbol: "USDC", decimals: USDC_DECIMALS };
433
436
  return {
434
437
  chainId: BASE_CHAIN_ID,
435
- token: { address: BASE_USDC_ADDRESS, symbol: "USDC", decimals: USDC_DECIMALS },
438
+ token: baseUsdc,
436
439
  environment,
440
+ destination: { chainId: BASE_CHAIN_ID, token: baseUsdc },
441
+ source: { default: { chainId: BASE_CHAIN_ID, token: baseUsdc } },
437
442
  platforms,
438
443
  currencies,
439
444
  amount: { min: MIN_CASHOUT_AMOUNT, recommendedMin: RECOMMENDED_MIN_CASHOUT_AMOUNT, max: null },
@@ -539,6 +544,18 @@ var errors = {
539
544
  },
540
545
  { cause }
541
546
  ),
547
+ sourceRouteUnsupportedInPrepare: () => new CashError({
548
+ code: "SOURCE_ROUTE_UNSUPPORTED_IN_PREPARE",
549
+ message: `prepare() cannot execute a Relay source route before creating the Base USDC cash-out.`,
550
+ retryable: false,
551
+ remediation: `Use cashout(inputWithSource, { signer }) for the one-call bridge-then-cashout flow, or call quoteSource()/executeSourceQuote() first and then prepare() a Base USDC cash-out.`
552
+ }),
553
+ sourceRecipientMismatch: (recipient, owner) => new CashError({
554
+ code: "SOURCE_RECIPIENT_MISMATCH",
555
+ message: `Source recipient ${recipient} does not match the cash-out depositor ${owner}.`,
556
+ retryable: false,
557
+ remediation: `For one-call source cashout, deliver Relay output to the depositor address. For a different recipient, bridge first and then cash out from that recipient's signer.`
558
+ }),
542
559
  allowanceNotVisible: (amount) => new CashError({
543
560
  code: "ALLOWANCE_NOT_VISIBLE",
544
561
  message: `USDC approval for ${amount} base units did not become visible on the read path in time.`,
@@ -598,6 +615,302 @@ function mapChainError(verb, err) {
598
615
  }
599
616
  return errors.chainCallFailed(verb, err);
600
617
  }
618
+ var ETA_WINDOW_DAYS = 30;
619
+ var ETA_WINDOW_SECONDS = ETA_WINDOW_DAYS * 24 * 60 * 60;
620
+ var ETA_PAGE_LIMIT = 250;
621
+ var ETA_MAX_DEPOSIT_SCAN = 2e3;
622
+ var FULFILLED = /* @__PURE__ */ new Set(["FULFILLED", "MANUALLY_RELEASED"]);
623
+ function toUnixSeconds2(value) {
624
+ if (value === null || value === void 0 || value === "") return void 0;
625
+ if (value instanceof Date) {
626
+ const seconds = Math.floor(value.getTime() / 1e3);
627
+ return Number.isFinite(seconds) && seconds > 0 ? seconds : void 0;
628
+ }
629
+ if (typeof value === "string" && /[TZ:-]/.test(value)) {
630
+ const parsed = Date.parse(value);
631
+ if (Number.isFinite(parsed)) return Math.floor(parsed / 1e3);
632
+ }
633
+ const n = Number(value);
634
+ return Number.isFinite(n) && n > 0 ? n : void 0;
635
+ }
636
+ function median(values) {
637
+ if (values.length === 0) return void 0;
638
+ const sorted = [...values].sort((a, b) => a - b);
639
+ const mid = Math.floor(sorted.length / 2);
640
+ return sorted.length % 2 === 1 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
641
+ }
642
+ function etaLabel(seconds) {
643
+ if (seconds === void 0) return "Recent fill time unavailable";
644
+ if (seconds < 60) return "Usually starts in under a minute";
645
+ const minutes = Math.max(1, Math.round(seconds / 60));
646
+ if (minutes < 60) return `Usually starts in about ${minutes} min`;
647
+ const hours = Math.max(1, Math.round(minutes / 60));
648
+ return `Usually starts in about ${hours} hr`;
649
+ }
650
+ function matchesPayout(deposit, environment, platform, currency) {
651
+ const payouts = derivePayouts(
652
+ deposit.paymentMethods ?? [],
653
+ deposit.currencies ?? [],
654
+ sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment)
655
+ );
656
+ return payouts.some(
657
+ (payout) => payout.pricing.marketRate && payout.pricing.spreadBps === 0 && (platform === void 0 || payout.platform === platform) && (currency === void 0 || payout.currency === currency)
658
+ );
659
+ }
660
+ async function readFillEta(client, input) {
661
+ const now = Math.floor(Date.now() / 1e3);
662
+ const windowStart = now - ETA_WINDOW_SECONDS;
663
+ const deposits = [];
664
+ for (let offset = 0; offset < ETA_MAX_DEPOSIT_SCAN; offset += ETA_PAGE_LIMIT) {
665
+ const page = await client.indexer.getDepositsWithRelations(
666
+ { chainId: BASE_CHAIN_ID },
667
+ { limit: ETA_PAGE_LIMIT, offset, orderBy: "timestamp", orderDirection: "desc" },
668
+ { includeIntents: true, intentStatuses: ["FULFILLED", "MANUALLY_RELEASED"] }
669
+ );
670
+ deposits.push(...page);
671
+ if (page.length < ETA_PAGE_LIMIT) break;
672
+ const oldestCreatedAt = Math.min(
673
+ ...page.map((deposit) => toUnixSeconds2(deposit.createdAt ?? deposit.timestamp) ?? Infinity)
674
+ );
675
+ if (oldestCreatedAt < windowStart) break;
676
+ }
677
+ const firstFillLatencies = [];
678
+ for (const deposit of deposits) {
679
+ const createdAt = toUnixSeconds2(deposit.createdAt ?? deposit.timestamp);
680
+ if (createdAt === void 0 || createdAt < windowStart) continue;
681
+ if (!matchesPayout(deposit, input.environment, input.platform, input.currency)) continue;
682
+ const fulfilled = (deposit.intents ?? []).filter((intent) => intent.status != null && FULFILLED.has(intent.status)).map((intent) => ({
683
+ fulfilledAt: toUnixSeconds2(intent.fulfillTimestamp)
684
+ })).filter(
685
+ (intent) => intent.fulfilledAt !== void 0 && intent.fulfilledAt >= createdAt
686
+ ).sort((a, b) => a.fulfilledAt - b.fulfilledAt);
687
+ if (fulfilled.length === 0) continue;
688
+ firstFillLatencies.push(fulfilled[0].fulfilledAt - createdAt);
689
+ }
690
+ const seconds = median(firstFillLatencies);
691
+ return {
692
+ ...seconds !== void 0 ? { seconds } : {},
693
+ label: etaLabel(seconds)
694
+ };
695
+ }
696
+ var RELAY_API_URL = relaySdk.MAINNET_RELAY_API;
697
+ var NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
698
+ var BASE_USDC_ASSET = {
699
+ chainId: BASE_CHAIN_ID,
700
+ address: BASE_USDC_ADDRESS,
701
+ symbol: "USDC",
702
+ decimals: USDC_DECIMALS,
703
+ name: "USD Coin"
704
+ };
705
+ function relayClient(options = {}) {
706
+ if (options.client) return options.client;
707
+ return relaySdk.createClient({
708
+ baseApiUrl: options.apiUrl ?? RELAY_API_URL,
709
+ source: options.source ?? "peer-cash",
710
+ ...options.apiKey ? { apiKey: options.apiKey } : {},
711
+ ...options.chains ? { chains: options.chains } : {}
712
+ });
713
+ }
714
+ function asRecord(value) {
715
+ return value !== null && typeof value === "object" ? value : {};
716
+ }
717
+ function asString(value) {
718
+ return typeof value === "string" && value.length > 0 ? value : void 0;
719
+ }
720
+ function asNumber(value) {
721
+ const n = Number(value);
722
+ return Number.isFinite(n) ? n : void 0;
723
+ }
724
+ function normalizeToken(chainId, token) {
725
+ const row = asRecord(token);
726
+ const address = asString(row.address);
727
+ const symbol = asString(row.symbol);
728
+ const decimals = asNumber(row.decimals);
729
+ const name = asString(row.name);
730
+ if (!address || !symbol || decimals === void 0) return null;
731
+ const metadata = asRecord(row.metadata);
732
+ return {
733
+ chainId,
734
+ address,
735
+ symbol,
736
+ decimals,
737
+ ...name ? { name } : {},
738
+ ...metadata.isNative === true || address.toLowerCase() === NATIVE_TOKEN_ADDRESS ? { isNative: true } : {}
739
+ };
740
+ }
741
+ function normalizeTx(data, chainId) {
742
+ const row = asRecord(data);
743
+ const to = asString(row.to);
744
+ const calldata = asString(row.data) ?? "0x";
745
+ if (!to) return null;
746
+ return {
747
+ to,
748
+ data: calldata,
749
+ value: BigInt(String(row.value ?? "0")),
750
+ chainId: asNumber(row.chainId) ?? chainId
751
+ };
752
+ }
753
+ function normalizeChain(chain) {
754
+ const row = asRecord(chain);
755
+ const tokenRows = [
756
+ chain.currency,
757
+ ...chain.featuredTokens ?? [],
758
+ ...chain.erc20Currencies ?? [],
759
+ ...chain.solverCurrencies ?? []
760
+ ];
761
+ const tokens = /* @__PURE__ */ new Map();
762
+ for (const token of tokenRows) {
763
+ const normalizedToken = normalizeToken(chain.id, token);
764
+ if (normalizedToken) tokens.set(normalizedToken.address.toLowerCase(), normalizedToken);
765
+ }
766
+ return {
767
+ id: chain.id,
768
+ name: chain.name,
769
+ displayName: chain.displayName,
770
+ disabled: row.disabled === true,
771
+ depositEnabled: chain.depositEnabled ?? false,
772
+ blockProductionLagging: chain.blockProductionLagging ?? false,
773
+ ...chain.vmType ? { vmType: chain.vmType } : {},
774
+ tokens: [...tokens.values()].sort((a, b) => a.symbol.localeCompare(b.symbol))
775
+ };
776
+ }
777
+ function isSupportedEvmChain(chain) {
778
+ return chain.vmType === void 0 || chain.vmType === "evm";
779
+ }
780
+ function quoteRequestId(quote) {
781
+ return quote.steps.map((step) => step.requestId).find((id) => id !== void 0);
782
+ }
783
+ function quoteSourceChainId(quote) {
784
+ const details = asRecord(quote.details);
785
+ const currencyIn = asRecord(details.currencyIn);
786
+ const sourceCurrency = asRecord(currencyIn.currency);
787
+ return asNumber(sourceCurrency.chainId);
788
+ }
789
+ function sanitizeRelayQuoteRaw(quote) {
790
+ if (!quote.request) return quote;
791
+ const request = { ...quote.request };
792
+ delete request.headers;
793
+ return { ...quote, request };
794
+ }
795
+ async function resolveRelayChains(options, client, config = {}) {
796
+ const preferInjectedClientChains = config.preferInjectedClientChains ?? true;
797
+ const chains = options.chains ?? (preferInjectedClientChains && options.client?.chains?.length ? options.client.chains : void 0) ?? (options.client ? await chainUtils.fetchChainConfigs(client.baseApiUrl, client.source, client.apiKey) : await chainUtils.configureDynamicChains());
798
+ client.chains = chains;
799
+ return chains;
800
+ }
801
+ function relayQuoteFromExecute(input, quote) {
802
+ const details = asRecord(quote.details);
803
+ const currencyIn = asRecord(details.currencyIn);
804
+ const currencyOut = asRecord(details.currencyOut);
805
+ const sourceCurrency = asRecord(currencyIn.currency);
806
+ const destinationCurrency = asRecord(currencyOut.currency);
807
+ const source = normalizeToken(input.source.chainId, sourceCurrency) ?? {
808
+ chainId: input.source.chainId,
809
+ address: input.source.currency,
810
+ symbol: "TOKEN",
811
+ decimals: 0
812
+ };
813
+ const destination = normalizeToken(BASE_CHAIN_ID, destinationCurrency) ?? BASE_USDC_ASSET;
814
+ const txs = quote.steps.flatMap(
815
+ (step) => step.items.map((item) => normalizeTx(item.data, input.source.chainId)).filter((tx) => tx !== null)
816
+ );
817
+ const outputAmount = BigInt(
818
+ String(currencyOut.minimumAmount ?? currencyOut.amount ?? input.amount.toString())
819
+ );
820
+ const requestId = quoteRequestId(quote);
821
+ const rate = asNumber(details.rate);
822
+ const timeEstimateSeconds = asNumber(details.timeEstimate);
823
+ return {
824
+ ...requestId ? { requestId } : {},
825
+ source,
826
+ destination,
827
+ inputAmount: BigInt(String(currencyIn.amount ?? input.amount.toString())),
828
+ outputAmount,
829
+ ...rate !== void 0 ? { rate } : {},
830
+ ...timeEstimateSeconds !== void 0 ? { timeEstimateSeconds } : {},
831
+ ...quote.fees !== void 0 ? { fees: quote.fees } : {},
832
+ txs,
833
+ raw: sanitizeRelayQuoteRaw(quote)
834
+ };
835
+ }
836
+ async function readRelaySourceCapabilities(options = {}) {
837
+ const client = relayClient(options);
838
+ const chains = await resolveRelayChains(options, client);
839
+ return {
840
+ destination: BASE_USDC_ASSET,
841
+ chains: chains.map(normalizeChain).filter((chain) => chain.tokens.length > 0 && isSupportedEvmChain(chain)).sort((a, b) => a.displayName.localeCompare(b.displayName)),
842
+ source: "relay-sdk",
843
+ asOf: Math.floor(Date.now() / 1e3)
844
+ };
845
+ }
846
+ async function quoteRelayToBaseUsdc(input, options = {}) {
847
+ const client = relayClient(options);
848
+ const quote = await client.actions.getQuote(
849
+ {
850
+ chainId: input.source.chainId,
851
+ currency: input.source.currency,
852
+ toChainId: BASE_CHAIN_ID,
853
+ toCurrency: BASE_USDC_ADDRESS,
854
+ user: input.user,
855
+ recipient: input.recipient ?? input.user,
856
+ amount: input.amount.toString(),
857
+ tradeType: input.tradeType ?? "EXACT_INPUT"
858
+ },
859
+ false
860
+ );
861
+ return relayQuoteFromExecute(input, quote);
862
+ }
863
+ async function executeRelayQuote(quote, wallet, options = {}) {
864
+ const client = relayClient(options.relay);
865
+ const sourceChainId = quoteSourceChainId(quote);
866
+ if (sourceChainId !== void 0 && !(client.chains ?? []).some((chain) => chain.id === sourceChainId)) {
867
+ await resolveRelayChains(options.relay ?? {}, client, { preferInjectedClientChains: false });
868
+ }
869
+ const { data } = await client.actions.execute({
870
+ quote,
871
+ wallet,
872
+ ...options.onProgress ? { onProgress: options.onProgress } : {},
873
+ ...options.disableCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: options.disableCapabilitiesCheck } : {}
874
+ });
875
+ const requestId = quoteRequestId(data);
876
+ return {
877
+ ...requestId ? { requestId } : {},
878
+ txHashes: data.steps.flatMap(
879
+ (step) => step.items.flatMap((item) => (item.txHashes ?? []).map((tx) => tx.txHash))
880
+ ),
881
+ quote: data
882
+ };
883
+ }
884
+ async function readRelayStatus(requestId, options = {}) {
885
+ const client = relayClient(options);
886
+ const response = await client.utils.request({
887
+ url: `${client.baseApiUrl}/intents/status/v3`,
888
+ method: "get",
889
+ params: { requestId }
890
+ });
891
+ const root = asRecord(response.data);
892
+ const status = asString(root.status);
893
+ if (status !== "refund" && status !== "waiting" && status !== "depositing" && status !== "failure" && status !== "pending" && status !== "submitted" && status !== "success") {
894
+ throw new Error(`Relay returned unknown status: ${String(root.status)}`);
895
+ }
896
+ const details = asString(root.details);
897
+ const updatedAt = asNumber(root.updatedAt);
898
+ const originChainId = asNumber(root.originChainId);
899
+ const destinationChainId = asNumber(root.destinationChainId);
900
+ const quoteCreatedAt = asNumber(root.quoteCreatedAt);
901
+ return {
902
+ requestId,
903
+ status,
904
+ ...details ? { details } : {},
905
+ inTxHashes: Array.isArray(root.inTxHashes) ? root.inTxHashes.map(String) : [],
906
+ txHashes: Array.isArray(root.txHashes) ? root.txHashes.map(String) : [],
907
+ ...updatedAt !== void 0 ? { updatedAt } : {},
908
+ ...originChainId !== void 0 ? { originChainId } : {},
909
+ ...destinationChainId !== void 0 ? { destinationChainId } : {},
910
+ ...quoteCreatedAt !== void 0 ? { quoteCreatedAt } : {},
911
+ raw: response.data
912
+ };
913
+ }
601
914
 
602
915
  // src/client/estimate.ts
603
916
  var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
@@ -617,14 +930,25 @@ var CHAINLINK_LATEST_ROUND_ABI = [
617
930
  }
618
931
  ];
619
932
  var DEFAULT_MAX_STALENESS_SECONDS = 86400;
620
- async function readEstimate(publicClient, input) {
621
- const { amount, currency } = input;
622
- if (amount < MIN_CASHOUT_AMOUNT) {
623
- throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
624
- }
933
+ async function readEstimate(publicClient, input, context = {}) {
934
+ const { currency } = input;
625
935
  if (!isMarketRateSupported(currency)) {
626
936
  throw errors.oracleUnsupportedCurrency(currency);
627
937
  }
938
+ const relayQuote = input.source !== void 0 ? await quoteRelayToBaseUsdc(
939
+ {
940
+ user: input.source.user,
941
+ amount: input.amount,
942
+ source: { chainId: input.source.chainId, currency: input.source.currency },
943
+ ...input.source.recipient ? { recipient: input.source.recipient } : {},
944
+ ...input.source.tradeType ? { tradeType: input.source.tradeType } : {}
945
+ },
946
+ context.relay
947
+ ) : void 0;
948
+ const amount = relayQuote?.outputAmount ?? input.amount;
949
+ if (amount < MIN_CASHOUT_AMOUNT) {
950
+ throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
951
+ }
628
952
  const feedConfig = sdk.CHAINLINK_ORACLE_FEEDS[currency];
629
953
  const asOf = Math.floor(Date.now() / 1e3);
630
954
  let rate;
@@ -647,7 +971,7 @@ async function readEstimate(publicClient, input) {
647
971
  rate = feedConfig.invert ? 1 / price : price;
648
972
  }
649
973
  const stale = oracleUpdatedAt !== void 0 && asOf - oracleUpdatedAt > DEFAULT_MAX_STALENESS_SECONDS;
650
- return {
974
+ const estimate = {
651
975
  kind: "oracle-estimate",
652
976
  currency,
653
977
  amount,
@@ -655,8 +979,27 @@ async function readEstimate(publicClient, input) {
655
979
  receiveAmount: Number(amount) / 10 ** USDC_DECIMALS * rate,
656
980
  asOf,
657
981
  ...oracleUpdatedAt !== void 0 ? { oracleUpdatedAt } : {},
658
- ...stale ? { stale: true } : {}
982
+ ...stale ? { stale: true } : {},
983
+ ...relayQuote ? {
984
+ source: {
985
+ kind: "relay",
986
+ asset: relayQuote.source,
987
+ inputAmount: relayQuote.inputAmount,
988
+ relayQuote
989
+ }
990
+ } : {}
659
991
  };
992
+ if (context.indexerClient && context.environment) {
993
+ try {
994
+ estimate.eta = await readFillEta(context.indexerClient, {
995
+ environment: context.environment,
996
+ currency,
997
+ ...input.platform ? { platform: input.platform } : {}
998
+ });
999
+ } catch {
1000
+ }
1001
+ }
1002
+ return estimate;
660
1003
  }
661
1004
 
662
1005
  // src/client/createCashClient.ts
@@ -752,9 +1095,8 @@ function createCashClient(options) {
752
1095
  }
753
1096
  return client;
754
1097
  }
755
- function validateInput(input) {
756
- const { amount, receive } = input;
757
- if (amount < MIN_CASHOUT_AMOUNT) throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
1098
+ function validatePayout(input) {
1099
+ const { receive } = input;
758
1100
  const catalog = sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
759
1101
  if (!catalog[receive.platform]) throw errors.unsupportedPlatform(receive.platform);
760
1102
  if (!isMarketRateSupported(receive.currency)) {
@@ -764,7 +1106,6 @@ function createCashClient(options) {
764
1106
  throw errors.payeeVerificationRequired(receive.platform);
765
1107
  }
766
1108
  return {
767
- amount,
768
1109
  payouts: [
769
1110
  {
770
1111
  processorName: receive.platform,
@@ -775,6 +1116,12 @@ function createCashClient(options) {
775
1116
  ...input.intentAmountRange ? { intentAmountRange: input.intentAmountRange } : {}
776
1117
  };
777
1118
  }
1119
+ function validateInput(input) {
1120
+ if (input.amount < MIN_CASHOUT_AMOUNT) {
1121
+ throw errors.amountBelowMinimum(input.amount, MIN_CASHOUT_AMOUNT);
1122
+ }
1123
+ return { amount: input.amount, ...validatePayout(input) };
1124
+ }
778
1125
  async function buildDepositParams(client, depositInput) {
779
1126
  try {
780
1127
  return await prepareCashDepositParams(client, depositInput);
@@ -851,6 +1198,14 @@ function createCashClient(options) {
851
1198
  if (!order.isInFlight) throw errors.orderNotActive(depositId);
852
1199
  return escrowContext(depositId);
853
1200
  }
1201
+ function capabilities(capabilityOptions) {
1202
+ const baseCapabilities = buildCapabilities(environment);
1203
+ if (!capabilityOptions?.includeRelaySources) return baseCapabilities;
1204
+ return readRelaySourceCapabilities(options.relay).then((relay) => ({
1205
+ ...baseCapabilities,
1206
+ source: { ...baseCapabilities.source, relay }
1207
+ }));
1208
+ }
854
1209
  async function settleAllowance(client, token, owner, escrow, amount) {
855
1210
  let allowance;
856
1211
  try {
@@ -879,18 +1234,109 @@ function createCashClient(options) {
879
1234
  throw errors.allowanceNotVisible(amount);
880
1235
  }
881
1236
  return {
882
- capabilities() {
883
- return buildCapabilities(environment);
1237
+ capabilities,
1238
+ async sourceCapabilities() {
1239
+ return readRelaySourceCapabilities(options.relay);
1240
+ },
1241
+ async quoteSource(input) {
1242
+ return quoteRelayToBaseUsdc(input, options.relay);
1243
+ },
1244
+ async executeSourceQuote(quote, opts) {
1245
+ return executeRelayQuote(quote, opts.signer, {
1246
+ ...options.relay ? { relay: options.relay } : {},
1247
+ ...opts.onProgress ? { onProgress: opts.onProgress } : {},
1248
+ ...opts.disableCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: opts.disableCapabilitiesCheck } : {}
1249
+ });
1250
+ },
1251
+ async relayStatus(requestId) {
1252
+ return readRelayStatus(requestId, options.relay);
884
1253
  },
885
1254
  async estimate(input) {
886
- return readEstimate(readClient.publicClient, input);
1255
+ return readEstimate(readClient.publicClient, input, {
1256
+ indexerClient: readClient,
1257
+ environment,
1258
+ ...options.relay ? { relay: options.relay } : {}
1259
+ });
887
1260
  },
888
1261
  async cashout(input, opts) {
889
- const depositInput = validateInput(input);
890
1262
  const client = signingClient("cashout", opts);
1263
+ const owner = opts.signer.account.address;
1264
+ const payoutInput = validatePayout(input);
1265
+ let sourceResult;
1266
+ let cashoutAmount = input.amount;
1267
+ if (input.source) {
1268
+ const sourceSigner = opts.sourceSigner ?? (input.source.chainId === BASE_CHAIN_ID ? opts.signer : void 0);
1269
+ if (!sourceSigner?.account) throw errors.signerRequired("source cashout");
1270
+ if (input.source.recipient !== void 0 && input.source.recipient.toLowerCase() !== owner.toLowerCase()) {
1271
+ throw errors.sourceRecipientMismatch(input.source.recipient, owner);
1272
+ }
1273
+ const relayQuote = await quoteRelayToBaseUsdc(
1274
+ {
1275
+ user: sourceSigner.account.address,
1276
+ amount: input.amount,
1277
+ source: { chainId: input.source.chainId, currency: input.source.currency },
1278
+ recipient: owner,
1279
+ ...input.source.tradeType ? { tradeType: input.source.tradeType } : {}
1280
+ },
1281
+ options.relay
1282
+ );
1283
+ if (relayQuote.outputAmount < MIN_CASHOUT_AMOUNT) {
1284
+ throw errors.amountBelowMinimum(relayQuote.outputAmount, MIN_CASHOUT_AMOUNT);
1285
+ }
1286
+ cashoutAmount = relayQuote.outputAmount;
1287
+ const depositInput2 = { amount: cashoutAmount, ...payoutInput };
1288
+ const params2 = await buildDepositParams(client, depositInput2);
1289
+ const escrow2 = client.escrowV2Address ?? client.escrowAddress;
1290
+ await settleAllowance(client, params2.token, owner, escrow2, depositInput2.amount);
1291
+ const executed = await executeRelayQuote(relayQuote.raw, sourceSigner, {
1292
+ ...options.relay ? { relay: options.relay } : {},
1293
+ ...opts.onSourceProgress ? { onProgress: opts.onSourceProgress } : {},
1294
+ ...opts.disableSourceCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: opts.disableSourceCapabilitiesCheck } : {}
1295
+ });
1296
+ sourceResult = {
1297
+ amount: cashoutAmount,
1298
+ ...executed.requestId ? { requestId: executed.requestId } : {},
1299
+ txHashes: executed.txHashes
1300
+ };
1301
+ const attributedParams2 = { ...params2, txOverrides: attribution };
1302
+ const send2 = async () => {
1303
+ try {
1304
+ return (await client.createDeposit(attributedParams2)).hash;
1305
+ } catch (err) {
1306
+ if (err instanceof Error && /exceeds allowance/i.test(err.message)) {
1307
+ await sleep(2e3);
1308
+ return (await client.createDeposit(attributedParams2)).hash;
1309
+ }
1310
+ throw err;
1311
+ }
1312
+ };
1313
+ let hash2;
1314
+ try {
1315
+ hash2 = await send2();
1316
+ } catch (err) {
1317
+ throw mapChainError("createDeposit", err);
1318
+ }
1319
+ const receipt2 = await client.publicClient.waitForTransactionReceipt({ hash: hash2 });
1320
+ if (receipt2.status === "reverted") throw errors.transactionFailed(hash2);
1321
+ const abi2 = client.escrowV2Abi ?? client.escrowAbi;
1322
+ const resolved2 = resolveCashDepositId({ logs: receipt2.logs, abi: abi2 });
1323
+ if (!resolved2) throw errors.depositResolutionFailed(hash2);
1324
+ const order2 = deriveCashOrder(resolved2.compositeId, [], {
1325
+ remainingAmount: depositInput2.amount,
1326
+ status: "ACTIVE"
1327
+ });
1328
+ return {
1329
+ depositId: resolved2.compositeId,
1330
+ txHash: hash2,
1331
+ escrowAddress: resolved2.escrowAddress,
1332
+ onchainDepositId: resolved2.onchainDepositId,
1333
+ order: order2,
1334
+ source: sourceResult
1335
+ };
1336
+ }
1337
+ const depositInput = validateInput(input);
891
1338
  const params = await buildDepositParams(client, depositInput);
892
1339
  const escrow = client.escrowV2Address ?? client.escrowAddress;
893
- const owner = opts.signer.account.address;
894
1340
  await settleAllowance(client, params.token, owner, escrow, depositInput.amount);
895
1341
  const attributedParams = { ...params, txOverrides: attribution };
896
1342
  const send = async () => {
@@ -924,10 +1370,12 @@ function createCashClient(options) {
924
1370
  txHash: hash,
925
1371
  escrowAddress: resolved.escrowAddress,
926
1372
  onchainDepositId: resolved.onchainDepositId,
927
- order
1373
+ order,
1374
+ ...sourceResult ? { source: sourceResult } : {}
928
1375
  };
929
1376
  },
930
1377
  async prepare(input) {
1378
+ if (input.source) throw errors.sourceRouteUnsupportedInPrepare();
931
1379
  const depositInput = validateInput(input);
932
1380
  const params = await buildDepositParams(readClient, depositInput);
933
1381
  const { prepared } = await readClient.prepareCreateDeposit({
@@ -1241,7 +1689,56 @@ var cashEstimateJsonSchema = zod.z.object({
1241
1689
  receiveAmount: zod.z.number(),
1242
1690
  asOf: zod.z.number(),
1243
1691
  oracleUpdatedAt: zod.z.number().optional(),
1244
- stale: zod.z.boolean().optional()
1692
+ stale: zod.z.boolean().optional(),
1693
+ source: zod.z.object({
1694
+ kind: zod.z.literal("relay"),
1695
+ asset: zod.z.object({
1696
+ chainId: zod.z.number(),
1697
+ address: zod.z.string(),
1698
+ symbol: zod.z.string(),
1699
+ decimals: zod.z.number(),
1700
+ name: zod.z.string().optional(),
1701
+ isNative: zod.z.boolean().optional()
1702
+ }),
1703
+ inputAmount: bigintString,
1704
+ relayQuote: zod.z.object({
1705
+ requestId: zod.z.string().optional(),
1706
+ source: zod.z.object({
1707
+ chainId: zod.z.number(),
1708
+ address: zod.z.string(),
1709
+ symbol: zod.z.string(),
1710
+ decimals: zod.z.number(),
1711
+ name: zod.z.string().optional(),
1712
+ isNative: zod.z.boolean().optional()
1713
+ }),
1714
+ destination: zod.z.object({
1715
+ chainId: zod.z.number(),
1716
+ address: zod.z.string(),
1717
+ symbol: zod.z.string(),
1718
+ decimals: zod.z.number(),
1719
+ name: zod.z.string().optional(),
1720
+ isNative: zod.z.boolean().optional()
1721
+ }),
1722
+ inputAmount: bigintString,
1723
+ outputAmount: bigintString,
1724
+ rate: zod.z.number().optional(),
1725
+ timeEstimateSeconds: zod.z.number().optional(),
1726
+ fees: zod.z.unknown().optional(),
1727
+ txs: zod.z.array(
1728
+ zod.z.object({
1729
+ to: zod.z.string(),
1730
+ data: zod.z.string(),
1731
+ value: bigintString,
1732
+ chainId: zod.z.number()
1733
+ })
1734
+ ),
1735
+ raw: zod.z.unknown()
1736
+ })
1737
+ }).optional(),
1738
+ eta: zod.z.object({
1739
+ seconds: zod.z.number().optional(),
1740
+ label: zod.z.string()
1741
+ }).optional()
1245
1742
  });
1246
1743
  var preparedTransactionJsonSchema = zod.z.object({
1247
1744
  to: zod.z.string(),
@@ -1265,7 +1762,12 @@ var cashoutResultJsonSchema = zod.z.object({
1265
1762
  txHash: zod.z.string(),
1266
1763
  escrowAddress: zod.z.string(),
1267
1764
  onchainDepositId: bigintString,
1268
- order: cashOrderJsonSchema
1765
+ order: cashOrderJsonSchema,
1766
+ source: zod.z.object({
1767
+ amount: bigintString,
1768
+ requestId: zod.z.string().optional(),
1769
+ txHashes: zod.z.array(zod.z.string())
1770
+ }).optional()
1269
1771
  });
1270
1772
  var prepareResultJsonSchema = zod.z.object({
1271
1773
  txs: zod.z.array(preparedTransactionJsonSchema),
@@ -1285,6 +1787,49 @@ var cashCapabilitiesJsonSchema = zod.z.object({
1285
1787
  chainId: zod.z.number(),
1286
1788
  token: zod.z.object({ address: zod.z.string(), symbol: zod.z.literal("USDC"), decimals: zod.z.number() }),
1287
1789
  environment: zod.z.enum(["production", "preproduction", "staging"]),
1790
+ destination: zod.z.object({
1791
+ chainId: zod.z.number(),
1792
+ token: zod.z.object({ address: zod.z.string(), symbol: zod.z.literal("USDC"), decimals: zod.z.number() })
1793
+ }),
1794
+ source: zod.z.object({
1795
+ default: zod.z.object({
1796
+ chainId: zod.z.number(),
1797
+ token: zod.z.object({ address: zod.z.string(), symbol: zod.z.literal("USDC"), decimals: zod.z.number() })
1798
+ }),
1799
+ relay: zod.z.object({
1800
+ destination: zod.z.object({
1801
+ chainId: zod.z.number(),
1802
+ address: zod.z.string(),
1803
+ symbol: zod.z.string(),
1804
+ decimals: zod.z.number(),
1805
+ name: zod.z.string().optional(),
1806
+ isNative: zod.z.boolean().optional()
1807
+ }),
1808
+ chains: zod.z.array(
1809
+ zod.z.object({
1810
+ id: zod.z.number(),
1811
+ name: zod.z.string(),
1812
+ displayName: zod.z.string(),
1813
+ disabled: zod.z.boolean(),
1814
+ depositEnabled: zod.z.boolean(),
1815
+ blockProductionLagging: zod.z.boolean(),
1816
+ vmType: zod.z.string().optional(),
1817
+ tokens: zod.z.array(
1818
+ zod.z.object({
1819
+ chainId: zod.z.number(),
1820
+ address: zod.z.string(),
1821
+ symbol: zod.z.string(),
1822
+ decimals: zod.z.number(),
1823
+ name: zod.z.string().optional(),
1824
+ isNative: zod.z.boolean().optional()
1825
+ })
1826
+ )
1827
+ })
1828
+ ),
1829
+ source: zod.z.literal("relay-sdk"),
1830
+ asOf: zod.z.number()
1831
+ }).optional()
1832
+ }),
1288
1833
  platforms: zod.z.array(
1289
1834
  zod.z.object({
1290
1835
  platform: zod.z.string(),
@@ -1376,14 +1921,38 @@ function orderFromJson(json) {
1376
1921
  return withExplain(data);
1377
1922
  }
1378
1923
  function estimateToJson(estimate) {
1379
- return { ...estimate, amount: estimate.amount.toString() };
1924
+ return omitUndefined({
1925
+ ...estimate,
1926
+ amount: estimate.amount.toString(),
1927
+ source: estimate.source ? {
1928
+ ...estimate.source,
1929
+ inputAmount: estimate.source.inputAmount.toString(),
1930
+ relayQuote: {
1931
+ ...estimate.source.relayQuote,
1932
+ inputAmount: estimate.source.relayQuote.inputAmount.toString(),
1933
+ outputAmount: estimate.source.relayQuote.outputAmount.toString(),
1934
+ txs: estimate.source.relayQuote.txs.map(preparedTxToJson),
1935
+ raw: sanitizeRelayQuoteRaw(estimate.source.relayQuote.raw)
1936
+ }
1937
+ } : void 0
1938
+ });
1380
1939
  }
1381
1940
  function estimateFromJson(json) {
1382
1941
  const parsed = cashEstimateJsonSchema.parse(json);
1383
1942
  return omitUndefined({
1384
1943
  ...parsed,
1385
1944
  currency: parsed.currency,
1386
- amount: BigInt(parsed.amount)
1945
+ amount: BigInt(parsed.amount),
1946
+ source: parsed.source ? {
1947
+ ...parsed.source,
1948
+ inputAmount: BigInt(parsed.source.inputAmount),
1949
+ relayQuote: {
1950
+ ...parsed.source.relayQuote,
1951
+ inputAmount: BigInt(parsed.source.relayQuote.inputAmount),
1952
+ outputAmount: BigInt(parsed.source.relayQuote.outputAmount),
1953
+ txs: parsed.source.relayQuote.txs.map(preparedTxFromJson)
1954
+ }
1955
+ } : void 0
1387
1956
  });
1388
1957
  }
1389
1958
  function preparedTxToJson(tx) {
@@ -1405,23 +1974,31 @@ function preparedStepFromJson(json) {
1405
1974
  return cashPreparedStepJsonSchema.parse(json);
1406
1975
  }
1407
1976
  function cashoutResultToJson(result) {
1408
- return {
1977
+ return omitUndefined({
1409
1978
  depositId: result.depositId,
1410
1979
  txHash: result.txHash,
1411
1980
  escrowAddress: result.escrowAddress,
1412
1981
  onchainDepositId: result.onchainDepositId.toString(),
1413
- order: orderToJson(result.order)
1414
- };
1982
+ order: orderToJson(result.order),
1983
+ source: result.source ? {
1984
+ ...result.source,
1985
+ amount: result.source.amount.toString()
1986
+ } : void 0
1987
+ });
1415
1988
  }
1416
1989
  function cashoutResultFromJson(json) {
1417
1990
  const parsed = cashoutResultJsonSchema.parse(json);
1418
- return {
1991
+ return omitUndefined({
1419
1992
  depositId: parsed.depositId,
1420
1993
  txHash: parsed.txHash,
1421
1994
  escrowAddress: parsed.escrowAddress,
1422
1995
  onchainDepositId: BigInt(parsed.onchainDepositId),
1423
- order: orderFromJson(parsed.order)
1424
- };
1996
+ order: orderFromJson(parsed.order),
1997
+ source: parsed.source ? {
1998
+ ...parsed.source,
1999
+ amount: BigInt(parsed.source.amount)
2000
+ } : void 0
2001
+ });
1425
2002
  }
1426
2003
  function prepareResultToJson(result) {
1427
2004
  return {
@@ -1480,6 +2057,10 @@ function capabilitiesFromJson(json) {
1480
2057
  const parsed = cashCapabilitiesJsonSchema.parse(json);
1481
2058
  return {
1482
2059
  ...parsed,
2060
+ source: {
2061
+ default: parsed.source.default,
2062
+ ...parsed.source.relay ? { relay: parsed.source.relay } : {}
2063
+ },
1483
2064
  platforms: parsed.platforms.map((p) => ({
1484
2065
  ...p,
1485
2066
  currencies: p.currencies