@zkp2p/cash 0.1.1 → 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.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,293 @@ function mapChainError(verb, err) {
598
615
  }
599
616
  return errors.chainCallFailed(verb, err);
600
617
  }
618
+ var ETA_WINDOW_DAYS = 7;
619
+ var ETA_WINDOW_SECONDS = ETA_WINDOW_DAYS * 24 * 60 * 60;
620
+ var ETA_SAMPLE_LIMIT = 250;
621
+ var FULFILLED = /* @__PURE__ */ new Set(["FULFILLED", "MANUALLY_RELEASED"]);
622
+ function toUnixSeconds2(value) {
623
+ if (value === null || value === void 0 || value === "") return void 0;
624
+ if (value instanceof Date) {
625
+ const seconds = Math.floor(value.getTime() / 1e3);
626
+ return Number.isFinite(seconds) && seconds > 0 ? seconds : void 0;
627
+ }
628
+ if (typeof value === "string" && /[TZ:-]/.test(value)) {
629
+ const parsed = Date.parse(value);
630
+ if (Number.isFinite(parsed)) return Math.floor(parsed / 1e3);
631
+ }
632
+ const n = Number(value);
633
+ return Number.isFinite(n) && n > 0 ? n : void 0;
634
+ }
635
+ function median(values) {
636
+ if (values.length === 0) return void 0;
637
+ const sorted = [...values].sort((a, b) => a - b);
638
+ const mid = Math.floor(sorted.length / 2);
639
+ return sorted.length % 2 === 1 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
640
+ }
641
+ function etaLabel(seconds) {
642
+ if (seconds === void 0) return "Recent fill time unavailable";
643
+ if (seconds < 60) return "Usually starts in under a minute";
644
+ const minutes = Math.max(1, Math.round(seconds / 60));
645
+ if (minutes < 60) return `Usually starts in about ${minutes} min`;
646
+ const hours = Math.max(1, Math.round(minutes / 60));
647
+ return `Usually starts in about ${hours} hr`;
648
+ }
649
+ function matchesPayout(deposit, environment, platform, currency) {
650
+ const payouts = derivePayouts(
651
+ deposit.paymentMethods ?? [],
652
+ deposit.currencies ?? [],
653
+ sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment)
654
+ );
655
+ if (payouts.length === 0) return true;
656
+ return payouts.some(
657
+ (payout) => (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 = await client.indexer.getDepositsWithRelations(
664
+ { chainId: BASE_CHAIN_ID },
665
+ { limit: ETA_SAMPLE_LIMIT, orderBy: "updatedAt", orderDirection: "desc" },
666
+ { includeIntents: true, intentStatuses: ["FULFILLED", "MANUALLY_RELEASED"] }
667
+ );
668
+ const firstFillLatencies = [];
669
+ for (const deposit of deposits) {
670
+ const createdAt = toUnixSeconds2(deposit.createdAt ?? deposit.timestamp);
671
+ if (createdAt === void 0 || createdAt < windowStart) continue;
672
+ if (!matchesPayout(deposit, input.environment, input.platform, input.currency)) continue;
673
+ const fulfilled = (deposit.intents ?? []).filter((intent) => intent.status != null && FULFILLED.has(intent.status)).map((intent) => ({
674
+ fulfilledAt: toUnixSeconds2(intent.fulfillTimestamp)
675
+ })).filter(
676
+ (intent) => intent.fulfilledAt !== void 0 && intent.fulfilledAt >= createdAt
677
+ ).sort((a, b) => a.fulfilledAt - b.fulfilledAt);
678
+ if (fulfilled.length === 0) continue;
679
+ firstFillLatencies.push(fulfilled[0].fulfilledAt - createdAt);
680
+ }
681
+ const seconds = median(firstFillLatencies);
682
+ return {
683
+ ...seconds !== void 0 ? { seconds } : {},
684
+ label: etaLabel(seconds)
685
+ };
686
+ }
687
+ var RELAY_API_URL = relaySdk.MAINNET_RELAY_API;
688
+ var NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
689
+ var BASE_USDC_ASSET = {
690
+ chainId: BASE_CHAIN_ID,
691
+ address: BASE_USDC_ADDRESS,
692
+ symbol: "USDC",
693
+ decimals: USDC_DECIMALS,
694
+ name: "USD Coin"
695
+ };
696
+ function relayClient(options = {}) {
697
+ if (options.client) return options.client;
698
+ return relaySdk.createClient({
699
+ baseApiUrl: options.apiUrl ?? RELAY_API_URL,
700
+ source: options.source ?? "peer-cash",
701
+ ...options.apiKey ? { apiKey: options.apiKey } : {},
702
+ ...options.chains ? { chains: options.chains } : {}
703
+ });
704
+ }
705
+ function asRecord(value) {
706
+ return value !== null && typeof value === "object" ? value : {};
707
+ }
708
+ function asString(value) {
709
+ return typeof value === "string" && value.length > 0 ? value : void 0;
710
+ }
711
+ function asNumber(value) {
712
+ const n = Number(value);
713
+ return Number.isFinite(n) ? n : void 0;
714
+ }
715
+ function normalizeToken(chainId, token) {
716
+ const row = asRecord(token);
717
+ const address = asString(row.address);
718
+ const symbol = asString(row.symbol);
719
+ const decimals = asNumber(row.decimals);
720
+ const name = asString(row.name);
721
+ if (!address || !symbol || decimals === void 0) return null;
722
+ const metadata = asRecord(row.metadata);
723
+ return {
724
+ chainId,
725
+ address,
726
+ symbol,
727
+ decimals,
728
+ ...name ? { name } : {},
729
+ ...metadata.isNative === true || address.toLowerCase() === NATIVE_TOKEN_ADDRESS ? { isNative: true } : {}
730
+ };
731
+ }
732
+ function normalizeTx(data, chainId) {
733
+ const row = asRecord(data);
734
+ const to = asString(row.to);
735
+ const calldata = asString(row.data) ?? "0x";
736
+ if (!to) return null;
737
+ return {
738
+ to,
739
+ data: calldata,
740
+ value: BigInt(String(row.value ?? "0")),
741
+ chainId: asNumber(row.chainId) ?? chainId
742
+ };
743
+ }
744
+ function normalizeChain(chain) {
745
+ const row = asRecord(chain);
746
+ const tokenRows = [
747
+ chain.currency,
748
+ ...chain.featuredTokens ?? [],
749
+ ...chain.erc20Currencies ?? [],
750
+ ...chain.solverCurrencies ?? []
751
+ ];
752
+ const tokens = /* @__PURE__ */ new Map();
753
+ for (const token of tokenRows) {
754
+ const normalizedToken = normalizeToken(chain.id, token);
755
+ if (normalizedToken) tokens.set(normalizedToken.address.toLowerCase(), normalizedToken);
756
+ }
757
+ return {
758
+ id: chain.id,
759
+ name: chain.name,
760
+ displayName: chain.displayName,
761
+ disabled: row.disabled === true,
762
+ depositEnabled: chain.depositEnabled ?? false,
763
+ blockProductionLagging: chain.blockProductionLagging ?? false,
764
+ ...chain.vmType ? { vmType: chain.vmType } : {},
765
+ tokens: [...tokens.values()].sort((a, b) => a.symbol.localeCompare(b.symbol))
766
+ };
767
+ }
768
+ function isSupportedEvmChain(chain) {
769
+ return chain.vmType === void 0 || chain.vmType === "evm";
770
+ }
771
+ function quoteRequestId(quote) {
772
+ return quote.steps.map((step) => step.requestId).find((id) => id !== void 0);
773
+ }
774
+ function quoteSourceChainId(quote) {
775
+ const details = asRecord(quote.details);
776
+ const currencyIn = asRecord(details.currencyIn);
777
+ const sourceCurrency = asRecord(currencyIn.currency);
778
+ return asNumber(sourceCurrency.chainId);
779
+ }
780
+ function sanitizeRelayQuoteRaw(quote) {
781
+ if (!quote.request) return quote;
782
+ const request = { ...quote.request };
783
+ delete request.headers;
784
+ return { ...quote, request };
785
+ }
786
+ async function resolveRelayChains(options, client, config = {}) {
787
+ const preferInjectedClientChains = config.preferInjectedClientChains ?? true;
788
+ 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());
789
+ client.chains = chains;
790
+ return chains;
791
+ }
792
+ function relayQuoteFromExecute(input, quote) {
793
+ const details = asRecord(quote.details);
794
+ const currencyIn = asRecord(details.currencyIn);
795
+ const currencyOut = asRecord(details.currencyOut);
796
+ const sourceCurrency = asRecord(currencyIn.currency);
797
+ const destinationCurrency = asRecord(currencyOut.currency);
798
+ const source = normalizeToken(input.source.chainId, sourceCurrency) ?? {
799
+ chainId: input.source.chainId,
800
+ address: input.source.currency,
801
+ symbol: "TOKEN",
802
+ decimals: 0
803
+ };
804
+ const destination = normalizeToken(BASE_CHAIN_ID, destinationCurrency) ?? BASE_USDC_ASSET;
805
+ const txs = quote.steps.flatMap(
806
+ (step) => step.items.map((item) => normalizeTx(item.data, input.source.chainId)).filter((tx) => tx !== null)
807
+ );
808
+ const outputAmount = BigInt(
809
+ String(currencyOut.minimumAmount ?? currencyOut.amount ?? input.amount.toString())
810
+ );
811
+ const requestId = quoteRequestId(quote);
812
+ const rate = asNumber(details.rate);
813
+ const timeEstimateSeconds = asNumber(details.timeEstimate);
814
+ return {
815
+ ...requestId ? { requestId } : {},
816
+ source,
817
+ destination,
818
+ inputAmount: BigInt(String(currencyIn.amount ?? input.amount.toString())),
819
+ outputAmount,
820
+ ...rate !== void 0 ? { rate } : {},
821
+ ...timeEstimateSeconds !== void 0 ? { timeEstimateSeconds } : {},
822
+ ...quote.fees !== void 0 ? { fees: quote.fees } : {},
823
+ txs,
824
+ raw: sanitizeRelayQuoteRaw(quote)
825
+ };
826
+ }
827
+ async function readRelaySourceCapabilities(options = {}) {
828
+ const client = relayClient(options);
829
+ const chains = await resolveRelayChains(options, client);
830
+ return {
831
+ destination: BASE_USDC_ASSET,
832
+ chains: chains.map(normalizeChain).filter((chain) => chain.tokens.length > 0 && isSupportedEvmChain(chain)).sort((a, b) => a.displayName.localeCompare(b.displayName)),
833
+ source: "relay-sdk",
834
+ asOf: Math.floor(Date.now() / 1e3)
835
+ };
836
+ }
837
+ async function quoteRelayToBaseUsdc(input, options = {}) {
838
+ const client = relayClient(options);
839
+ const quote = await client.actions.getQuote(
840
+ {
841
+ chainId: input.source.chainId,
842
+ currency: input.source.currency,
843
+ toChainId: BASE_CHAIN_ID,
844
+ toCurrency: BASE_USDC_ADDRESS,
845
+ user: input.user,
846
+ recipient: input.recipient ?? input.user,
847
+ amount: input.amount.toString(),
848
+ tradeType: input.tradeType ?? "EXACT_INPUT"
849
+ },
850
+ false
851
+ );
852
+ return relayQuoteFromExecute(input, quote);
853
+ }
854
+ async function executeRelayQuote(quote, wallet, options = {}) {
855
+ const client = relayClient(options.relay);
856
+ const sourceChainId = quoteSourceChainId(quote);
857
+ if (sourceChainId !== void 0 && !(client.chains ?? []).some((chain) => chain.id === sourceChainId)) {
858
+ await resolveRelayChains(options.relay ?? {}, client, { preferInjectedClientChains: false });
859
+ }
860
+ const { data } = await client.actions.execute({
861
+ quote,
862
+ wallet,
863
+ ...options.onProgress ? { onProgress: options.onProgress } : {},
864
+ ...options.disableCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: options.disableCapabilitiesCheck } : {}
865
+ });
866
+ const requestId = quoteRequestId(data);
867
+ return {
868
+ ...requestId ? { requestId } : {},
869
+ txHashes: data.steps.flatMap(
870
+ (step) => step.items.flatMap((item) => (item.txHashes ?? []).map((tx) => tx.txHash))
871
+ ),
872
+ quote: data
873
+ };
874
+ }
875
+ async function readRelayStatus(requestId, options = {}) {
876
+ const client = relayClient(options);
877
+ const response = await client.utils.request({
878
+ url: `${client.baseApiUrl}/intents/status/v3`,
879
+ method: "get",
880
+ params: { requestId }
881
+ });
882
+ const root = asRecord(response.data);
883
+ const status = asString(root.status);
884
+ if (status !== "refund" && status !== "waiting" && status !== "depositing" && status !== "failure" && status !== "pending" && status !== "submitted" && status !== "success") {
885
+ throw new Error(`Relay returned unknown status: ${String(root.status)}`);
886
+ }
887
+ const details = asString(root.details);
888
+ const updatedAt = asNumber(root.updatedAt);
889
+ const originChainId = asNumber(root.originChainId);
890
+ const destinationChainId = asNumber(root.destinationChainId);
891
+ const quoteCreatedAt = asNumber(root.quoteCreatedAt);
892
+ return {
893
+ requestId,
894
+ status,
895
+ ...details ? { details } : {},
896
+ inTxHashes: Array.isArray(root.inTxHashes) ? root.inTxHashes.map(String) : [],
897
+ txHashes: Array.isArray(root.txHashes) ? root.txHashes.map(String) : [],
898
+ ...updatedAt !== void 0 ? { updatedAt } : {},
899
+ ...originChainId !== void 0 ? { originChainId } : {},
900
+ ...destinationChainId !== void 0 ? { destinationChainId } : {},
901
+ ...quoteCreatedAt !== void 0 ? { quoteCreatedAt } : {},
902
+ raw: response.data
903
+ };
904
+ }
601
905
 
602
906
  // src/client/estimate.ts
603
907
  var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
@@ -617,14 +921,25 @@ var CHAINLINK_LATEST_ROUND_ABI = [
617
921
  }
618
922
  ];
619
923
  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
- }
924
+ async function readEstimate(publicClient, input, context = {}) {
925
+ const { currency } = input;
625
926
  if (!isMarketRateSupported(currency)) {
626
927
  throw errors.oracleUnsupportedCurrency(currency);
627
928
  }
929
+ const relayQuote = input.source !== void 0 ? await quoteRelayToBaseUsdc(
930
+ {
931
+ user: input.source.user,
932
+ amount: input.amount,
933
+ source: { chainId: input.source.chainId, currency: input.source.currency },
934
+ ...input.source.recipient ? { recipient: input.source.recipient } : {},
935
+ ...input.source.tradeType ? { tradeType: input.source.tradeType } : {}
936
+ },
937
+ context.relay
938
+ ) : void 0;
939
+ const amount = relayQuote?.outputAmount ?? input.amount;
940
+ if (amount < MIN_CASHOUT_AMOUNT) {
941
+ throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
942
+ }
628
943
  const feedConfig = sdk.CHAINLINK_ORACLE_FEEDS[currency];
629
944
  const asOf = Math.floor(Date.now() / 1e3);
630
945
  let rate;
@@ -647,7 +962,7 @@ async function readEstimate(publicClient, input) {
647
962
  rate = feedConfig.invert ? 1 / price : price;
648
963
  }
649
964
  const stale = oracleUpdatedAt !== void 0 && asOf - oracleUpdatedAt > DEFAULT_MAX_STALENESS_SECONDS;
650
- return {
965
+ const estimate = {
651
966
  kind: "oracle-estimate",
652
967
  currency,
653
968
  amount,
@@ -655,8 +970,27 @@ async function readEstimate(publicClient, input) {
655
970
  receiveAmount: Number(amount) / 10 ** USDC_DECIMALS * rate,
656
971
  asOf,
657
972
  ...oracleUpdatedAt !== void 0 ? { oracleUpdatedAt } : {},
658
- ...stale ? { stale: true } : {}
973
+ ...stale ? { stale: true } : {},
974
+ ...relayQuote ? {
975
+ source: {
976
+ kind: "relay",
977
+ asset: relayQuote.source,
978
+ inputAmount: relayQuote.inputAmount,
979
+ relayQuote
980
+ }
981
+ } : {}
659
982
  };
983
+ if (context.indexerClient && context.environment) {
984
+ try {
985
+ estimate.eta = await readFillEta(context.indexerClient, {
986
+ environment: context.environment,
987
+ currency,
988
+ ...input.platform ? { platform: input.platform } : {}
989
+ });
990
+ } catch {
991
+ }
992
+ }
993
+ return estimate;
660
994
  }
661
995
 
662
996
  // src/client/createCashClient.ts
@@ -752,9 +1086,8 @@ function createCashClient(options) {
752
1086
  }
753
1087
  return client;
754
1088
  }
755
- function validateInput(input) {
756
- const { amount, receive } = input;
757
- if (amount < MIN_CASHOUT_AMOUNT) throw errors.amountBelowMinimum(amount, MIN_CASHOUT_AMOUNT);
1089
+ function validatePayout(input) {
1090
+ const { receive } = input;
758
1091
  const catalog = sdk.getPaymentMethodsCatalog(BASE_CHAIN_ID, environment);
759
1092
  if (!catalog[receive.platform]) throw errors.unsupportedPlatform(receive.platform);
760
1093
  if (!isMarketRateSupported(receive.currency)) {
@@ -764,7 +1097,6 @@ function createCashClient(options) {
764
1097
  throw errors.payeeVerificationRequired(receive.platform);
765
1098
  }
766
1099
  return {
767
- amount,
768
1100
  payouts: [
769
1101
  {
770
1102
  processorName: receive.platform,
@@ -775,6 +1107,12 @@ function createCashClient(options) {
775
1107
  ...input.intentAmountRange ? { intentAmountRange: input.intentAmountRange } : {}
776
1108
  };
777
1109
  }
1110
+ function validateInput(input) {
1111
+ if (input.amount < MIN_CASHOUT_AMOUNT) {
1112
+ throw errors.amountBelowMinimum(input.amount, MIN_CASHOUT_AMOUNT);
1113
+ }
1114
+ return { amount: input.amount, ...validatePayout(input) };
1115
+ }
778
1116
  async function buildDepositParams(client, depositInput) {
779
1117
  try {
780
1118
  return await prepareCashDepositParams(client, depositInput);
@@ -851,6 +1189,14 @@ function createCashClient(options) {
851
1189
  if (!order.isInFlight) throw errors.orderNotActive(depositId);
852
1190
  return escrowContext(depositId);
853
1191
  }
1192
+ function capabilities(capabilityOptions) {
1193
+ const baseCapabilities = buildCapabilities(environment);
1194
+ if (!capabilityOptions?.includeRelaySources) return baseCapabilities;
1195
+ return readRelaySourceCapabilities(options.relay).then((relay) => ({
1196
+ ...baseCapabilities,
1197
+ source: { ...baseCapabilities.source, relay }
1198
+ }));
1199
+ }
854
1200
  async function settleAllowance(client, token, owner, escrow, amount) {
855
1201
  let allowance;
856
1202
  try {
@@ -879,18 +1225,109 @@ function createCashClient(options) {
879
1225
  throw errors.allowanceNotVisible(amount);
880
1226
  }
881
1227
  return {
882
- capabilities() {
883
- return buildCapabilities(environment);
1228
+ capabilities,
1229
+ async sourceCapabilities() {
1230
+ return readRelaySourceCapabilities(options.relay);
1231
+ },
1232
+ async quoteSource(input) {
1233
+ return quoteRelayToBaseUsdc(input, options.relay);
1234
+ },
1235
+ async executeSourceQuote(quote, opts) {
1236
+ return executeRelayQuote(quote, opts.signer, {
1237
+ ...options.relay ? { relay: options.relay } : {},
1238
+ ...opts.onProgress ? { onProgress: opts.onProgress } : {},
1239
+ ...opts.disableCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: opts.disableCapabilitiesCheck } : {}
1240
+ });
1241
+ },
1242
+ async relayStatus(requestId) {
1243
+ return readRelayStatus(requestId, options.relay);
884
1244
  },
885
1245
  async estimate(input) {
886
- return readEstimate(readClient.publicClient, input);
1246
+ return readEstimate(readClient.publicClient, input, {
1247
+ indexerClient: readClient,
1248
+ environment,
1249
+ ...options.relay ? { relay: options.relay } : {}
1250
+ });
887
1251
  },
888
1252
  async cashout(input, opts) {
889
- const depositInput = validateInput(input);
890
1253
  const client = signingClient("cashout", opts);
1254
+ const owner = opts.signer.account.address;
1255
+ const payoutInput = validatePayout(input);
1256
+ let sourceResult;
1257
+ let cashoutAmount = input.amount;
1258
+ if (input.source) {
1259
+ const sourceSigner = opts.sourceSigner ?? (input.source.chainId === BASE_CHAIN_ID ? opts.signer : void 0);
1260
+ if (!sourceSigner?.account) throw errors.signerRequired("source cashout");
1261
+ if (input.source.recipient !== void 0 && input.source.recipient.toLowerCase() !== owner.toLowerCase()) {
1262
+ throw errors.sourceRecipientMismatch(input.source.recipient, owner);
1263
+ }
1264
+ const relayQuote = await quoteRelayToBaseUsdc(
1265
+ {
1266
+ user: sourceSigner.account.address,
1267
+ amount: input.amount,
1268
+ source: { chainId: input.source.chainId, currency: input.source.currency },
1269
+ recipient: owner,
1270
+ ...input.source.tradeType ? { tradeType: input.source.tradeType } : {}
1271
+ },
1272
+ options.relay
1273
+ );
1274
+ if (relayQuote.outputAmount < MIN_CASHOUT_AMOUNT) {
1275
+ throw errors.amountBelowMinimum(relayQuote.outputAmount, MIN_CASHOUT_AMOUNT);
1276
+ }
1277
+ cashoutAmount = relayQuote.outputAmount;
1278
+ const depositInput2 = { amount: cashoutAmount, ...payoutInput };
1279
+ const params2 = await buildDepositParams(client, depositInput2);
1280
+ const escrow2 = client.escrowV2Address ?? client.escrowAddress;
1281
+ await settleAllowance(client, params2.token, owner, escrow2, depositInput2.amount);
1282
+ const executed = await executeRelayQuote(relayQuote.raw, sourceSigner, {
1283
+ ...options.relay ? { relay: options.relay } : {},
1284
+ ...opts.onSourceProgress ? { onProgress: opts.onSourceProgress } : {},
1285
+ ...opts.disableSourceCapabilitiesCheck !== void 0 ? { disableCapabilitiesCheck: opts.disableSourceCapabilitiesCheck } : {}
1286
+ });
1287
+ sourceResult = {
1288
+ amount: cashoutAmount,
1289
+ ...executed.requestId ? { requestId: executed.requestId } : {},
1290
+ txHashes: executed.txHashes
1291
+ };
1292
+ const attributedParams2 = { ...params2, txOverrides: attribution };
1293
+ const send2 = async () => {
1294
+ try {
1295
+ return (await client.createDeposit(attributedParams2)).hash;
1296
+ } catch (err) {
1297
+ if (err instanceof Error && /exceeds allowance/i.test(err.message)) {
1298
+ await sleep(2e3);
1299
+ return (await client.createDeposit(attributedParams2)).hash;
1300
+ }
1301
+ throw err;
1302
+ }
1303
+ };
1304
+ let hash2;
1305
+ try {
1306
+ hash2 = await send2();
1307
+ } catch (err) {
1308
+ throw mapChainError("createDeposit", err);
1309
+ }
1310
+ const receipt2 = await client.publicClient.waitForTransactionReceipt({ hash: hash2 });
1311
+ if (receipt2.status === "reverted") throw errors.transactionFailed(hash2);
1312
+ const abi2 = client.escrowV2Abi ?? client.escrowAbi;
1313
+ const resolved2 = resolveCashDepositId({ logs: receipt2.logs, abi: abi2 });
1314
+ if (!resolved2) throw errors.depositResolutionFailed(hash2);
1315
+ const order2 = deriveCashOrder(resolved2.compositeId, [], {
1316
+ remainingAmount: depositInput2.amount,
1317
+ status: "ACTIVE"
1318
+ });
1319
+ return {
1320
+ depositId: resolved2.compositeId,
1321
+ txHash: hash2,
1322
+ escrowAddress: resolved2.escrowAddress,
1323
+ onchainDepositId: resolved2.onchainDepositId,
1324
+ order: order2,
1325
+ source: sourceResult
1326
+ };
1327
+ }
1328
+ const depositInput = validateInput(input);
891
1329
  const params = await buildDepositParams(client, depositInput);
892
1330
  const escrow = client.escrowV2Address ?? client.escrowAddress;
893
- const owner = opts.signer.account.address;
894
1331
  await settleAllowance(client, params.token, owner, escrow, depositInput.amount);
895
1332
  const attributedParams = { ...params, txOverrides: attribution };
896
1333
  const send = async () => {
@@ -924,10 +1361,12 @@ function createCashClient(options) {
924
1361
  txHash: hash,
925
1362
  escrowAddress: resolved.escrowAddress,
926
1363
  onchainDepositId: resolved.onchainDepositId,
927
- order
1364
+ order,
1365
+ ...sourceResult ? { source: sourceResult } : {}
928
1366
  };
929
1367
  },
930
1368
  async prepare(input) {
1369
+ if (input.source) throw errors.sourceRouteUnsupportedInPrepare();
931
1370
  const depositInput = validateInput(input);
932
1371
  const params = await buildDepositParams(readClient, depositInput);
933
1372
  const { prepared } = await readClient.prepareCreateDeposit({
@@ -1241,7 +1680,56 @@ var cashEstimateJsonSchema = zod.z.object({
1241
1680
  receiveAmount: zod.z.number(),
1242
1681
  asOf: zod.z.number(),
1243
1682
  oracleUpdatedAt: zod.z.number().optional(),
1244
- stale: zod.z.boolean().optional()
1683
+ stale: zod.z.boolean().optional(),
1684
+ source: zod.z.object({
1685
+ kind: zod.z.literal("relay"),
1686
+ asset: zod.z.object({
1687
+ chainId: zod.z.number(),
1688
+ address: zod.z.string(),
1689
+ symbol: zod.z.string(),
1690
+ decimals: zod.z.number(),
1691
+ name: zod.z.string().optional(),
1692
+ isNative: zod.z.boolean().optional()
1693
+ }),
1694
+ inputAmount: bigintString,
1695
+ relayQuote: zod.z.object({
1696
+ requestId: zod.z.string().optional(),
1697
+ source: zod.z.object({
1698
+ chainId: zod.z.number(),
1699
+ address: zod.z.string(),
1700
+ symbol: zod.z.string(),
1701
+ decimals: zod.z.number(),
1702
+ name: zod.z.string().optional(),
1703
+ isNative: zod.z.boolean().optional()
1704
+ }),
1705
+ destination: zod.z.object({
1706
+ chainId: zod.z.number(),
1707
+ address: zod.z.string(),
1708
+ symbol: zod.z.string(),
1709
+ decimals: zod.z.number(),
1710
+ name: zod.z.string().optional(),
1711
+ isNative: zod.z.boolean().optional()
1712
+ }),
1713
+ inputAmount: bigintString,
1714
+ outputAmount: bigintString,
1715
+ rate: zod.z.number().optional(),
1716
+ timeEstimateSeconds: zod.z.number().optional(),
1717
+ fees: zod.z.unknown().optional(),
1718
+ txs: zod.z.array(
1719
+ zod.z.object({
1720
+ to: zod.z.string(),
1721
+ data: zod.z.string(),
1722
+ value: bigintString,
1723
+ chainId: zod.z.number()
1724
+ })
1725
+ ),
1726
+ raw: zod.z.unknown()
1727
+ })
1728
+ }).optional(),
1729
+ eta: zod.z.object({
1730
+ seconds: zod.z.number().optional(),
1731
+ label: zod.z.string()
1732
+ }).optional()
1245
1733
  });
1246
1734
  var preparedTransactionJsonSchema = zod.z.object({
1247
1735
  to: zod.z.string(),
@@ -1265,7 +1753,12 @@ var cashoutResultJsonSchema = zod.z.object({
1265
1753
  txHash: zod.z.string(),
1266
1754
  escrowAddress: zod.z.string(),
1267
1755
  onchainDepositId: bigintString,
1268
- order: cashOrderJsonSchema
1756
+ order: cashOrderJsonSchema,
1757
+ source: zod.z.object({
1758
+ amount: bigintString,
1759
+ requestId: zod.z.string().optional(),
1760
+ txHashes: zod.z.array(zod.z.string())
1761
+ }).optional()
1269
1762
  });
1270
1763
  var prepareResultJsonSchema = zod.z.object({
1271
1764
  txs: zod.z.array(preparedTransactionJsonSchema),
@@ -1285,6 +1778,49 @@ var cashCapabilitiesJsonSchema = zod.z.object({
1285
1778
  chainId: zod.z.number(),
1286
1779
  token: zod.z.object({ address: zod.z.string(), symbol: zod.z.literal("USDC"), decimals: zod.z.number() }),
1287
1780
  environment: zod.z.enum(["production", "preproduction", "staging"]),
1781
+ destination: zod.z.object({
1782
+ chainId: zod.z.number(),
1783
+ token: zod.z.object({ address: zod.z.string(), symbol: zod.z.literal("USDC"), decimals: zod.z.number() })
1784
+ }),
1785
+ source: zod.z.object({
1786
+ default: zod.z.object({
1787
+ chainId: zod.z.number(),
1788
+ token: zod.z.object({ address: zod.z.string(), symbol: zod.z.literal("USDC"), decimals: zod.z.number() })
1789
+ }),
1790
+ relay: zod.z.object({
1791
+ destination: zod.z.object({
1792
+ chainId: zod.z.number(),
1793
+ address: zod.z.string(),
1794
+ symbol: zod.z.string(),
1795
+ decimals: zod.z.number(),
1796
+ name: zod.z.string().optional(),
1797
+ isNative: zod.z.boolean().optional()
1798
+ }),
1799
+ chains: zod.z.array(
1800
+ zod.z.object({
1801
+ id: zod.z.number(),
1802
+ name: zod.z.string(),
1803
+ displayName: zod.z.string(),
1804
+ disabled: zod.z.boolean(),
1805
+ depositEnabled: zod.z.boolean(),
1806
+ blockProductionLagging: zod.z.boolean(),
1807
+ vmType: zod.z.string().optional(),
1808
+ tokens: zod.z.array(
1809
+ zod.z.object({
1810
+ chainId: zod.z.number(),
1811
+ address: zod.z.string(),
1812
+ symbol: zod.z.string(),
1813
+ decimals: zod.z.number(),
1814
+ name: zod.z.string().optional(),
1815
+ isNative: zod.z.boolean().optional()
1816
+ })
1817
+ )
1818
+ })
1819
+ ),
1820
+ source: zod.z.literal("relay-sdk"),
1821
+ asOf: zod.z.number()
1822
+ }).optional()
1823
+ }),
1288
1824
  platforms: zod.z.array(
1289
1825
  zod.z.object({
1290
1826
  platform: zod.z.string(),
@@ -1376,14 +1912,38 @@ function orderFromJson(json) {
1376
1912
  return withExplain(data);
1377
1913
  }
1378
1914
  function estimateToJson(estimate) {
1379
- return { ...estimate, amount: estimate.amount.toString() };
1915
+ return omitUndefined({
1916
+ ...estimate,
1917
+ amount: estimate.amount.toString(),
1918
+ source: estimate.source ? {
1919
+ ...estimate.source,
1920
+ inputAmount: estimate.source.inputAmount.toString(),
1921
+ relayQuote: {
1922
+ ...estimate.source.relayQuote,
1923
+ inputAmount: estimate.source.relayQuote.inputAmount.toString(),
1924
+ outputAmount: estimate.source.relayQuote.outputAmount.toString(),
1925
+ txs: estimate.source.relayQuote.txs.map(preparedTxToJson),
1926
+ raw: sanitizeRelayQuoteRaw(estimate.source.relayQuote.raw)
1927
+ }
1928
+ } : void 0
1929
+ });
1380
1930
  }
1381
1931
  function estimateFromJson(json) {
1382
1932
  const parsed = cashEstimateJsonSchema.parse(json);
1383
1933
  return omitUndefined({
1384
1934
  ...parsed,
1385
1935
  currency: parsed.currency,
1386
- amount: BigInt(parsed.amount)
1936
+ amount: BigInt(parsed.amount),
1937
+ source: parsed.source ? {
1938
+ ...parsed.source,
1939
+ inputAmount: BigInt(parsed.source.inputAmount),
1940
+ relayQuote: {
1941
+ ...parsed.source.relayQuote,
1942
+ inputAmount: BigInt(parsed.source.relayQuote.inputAmount),
1943
+ outputAmount: BigInt(parsed.source.relayQuote.outputAmount),
1944
+ txs: parsed.source.relayQuote.txs.map(preparedTxFromJson)
1945
+ }
1946
+ } : void 0
1387
1947
  });
1388
1948
  }
1389
1949
  function preparedTxToJson(tx) {
@@ -1405,23 +1965,31 @@ function preparedStepFromJson(json) {
1405
1965
  return cashPreparedStepJsonSchema.parse(json);
1406
1966
  }
1407
1967
  function cashoutResultToJson(result) {
1408
- return {
1968
+ return omitUndefined({
1409
1969
  depositId: result.depositId,
1410
1970
  txHash: result.txHash,
1411
1971
  escrowAddress: result.escrowAddress,
1412
1972
  onchainDepositId: result.onchainDepositId.toString(),
1413
- order: orderToJson(result.order)
1414
- };
1973
+ order: orderToJson(result.order),
1974
+ source: result.source ? {
1975
+ ...result.source,
1976
+ amount: result.source.amount.toString()
1977
+ } : void 0
1978
+ });
1415
1979
  }
1416
1980
  function cashoutResultFromJson(json) {
1417
1981
  const parsed = cashoutResultJsonSchema.parse(json);
1418
- return {
1982
+ return omitUndefined({
1419
1983
  depositId: parsed.depositId,
1420
1984
  txHash: parsed.txHash,
1421
1985
  escrowAddress: parsed.escrowAddress,
1422
1986
  onchainDepositId: BigInt(parsed.onchainDepositId),
1423
- order: orderFromJson(parsed.order)
1424
- };
1987
+ order: orderFromJson(parsed.order),
1988
+ source: parsed.source ? {
1989
+ ...parsed.source,
1990
+ amount: BigInt(parsed.source.amount)
1991
+ } : void 0
1992
+ });
1425
1993
  }
1426
1994
  function prepareResultToJson(result) {
1427
1995
  return {
@@ -1480,6 +2048,10 @@ function capabilitiesFromJson(json) {
1480
2048
  const parsed = cashCapabilitiesJsonSchema.parse(json);
1481
2049
  return {
1482
2050
  ...parsed,
2051
+ source: {
2052
+ default: parsed.source.default,
2053
+ ...parsed.source.relay ? { relay: parsed.source.relay } : {}
2054
+ },
1483
2055
  platforms: parsed.platforms.map((p) => ({
1484
2056
  ...p,
1485
2057
  currencies: p.currencies