@sodax/sdk 2.0.0-rc.11 → 2.0.0-rc.13

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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { getAbiItem, isAddress, toHex, hexToBytes, encodeAbiParameters, parseAbiParameters, defineChain, encodeFunctionData, erc20Abi as erc20Abi$1, createPublicClient, http, bytesToHex, keccak256, fromHex, encodePacked, decodeAbiParameters, parseEventLogs, maxUint160, maxUint48 } from 'viem';
1
+ import { getAbiItem, parseAbi, isAddress, toHex, hexToBytes, encodeAbiParameters, parseAbiParameters, defineChain, encodeFunctionData, erc20Abi as erc20Abi$1, createPublicClient, http, bytesToHex, keccak256, stringToBytes, fromHex, encodePacked, decodeAbiParameters, parseEventLogs, maxUint160, maxUint48 } from 'viem';
2
2
  import { bcs } from '@mysten/sui/bcs';
3
3
  import { PublicKey, Connection, VersionedTransaction, SystemProgram, ComputeBudgetProgram, TransactionMessage, TransactionInstruction } from '@solana/web3.js';
4
4
  import { rpc, Address, xdr, Account, Horizon, Contract, TransactionBuilder, nativeToScVal, TimeoutInfinite, scValToBigInt, Operation, Asset, FeeBumpTransaction } from '@stellar/stellar-sdk';
@@ -169,7 +169,8 @@ var SODAX_FEATURES = [
169
169
  "migration",
170
170
  "dex",
171
171
  "partner",
172
- "recovery"
172
+ "recovery",
173
+ "leverageYield"
173
174
  ];
174
175
 
175
176
  // src/shared/utils/tiny-invariant.ts
@@ -531,6 +532,27 @@ var SodaTokens = {
531
532
  vault: "0x243b0c26c8b38793908d7C64e8510f21B19B4613"
532
533
  }
533
534
  };
535
+ var LsodaSymbols = ["lsodaWEETH", "lsodaWSTETH"];
536
+ var LsodaTokens = {
537
+ lsodaWEETH: {
538
+ symbol: "lsodaWEETH",
539
+ name: "Leveraged Soda weETH",
540
+ decimals: 18,
541
+ address: "0xD09de2f5070699A909c0FD32fb5A909d3886701D",
542
+ chainKey: ChainKeys.SONIC_MAINNET,
543
+ hubAsset: "0xD09de2f5070699A909c0FD32fb5A909d3886701D",
544
+ vault: "0xD09de2f5070699A909c0FD32fb5A909d3886701D"
545
+ },
546
+ lsodaWSTETH: {
547
+ symbol: "lsodaWSTETH",
548
+ name: "Leveraged Soda wstETH",
549
+ decimals: 18,
550
+ address: "0x136E5D1CEC5db1829E24941Eddd9C8640E02Ce7a",
551
+ chainKey: ChainKeys.SONIC_MAINNET,
552
+ hubAsset: "0x136E5D1CEC5db1829E24941Eddd9C8640E02Ce7a",
553
+ vault: "0x136E5D1CEC5db1829E24941Eddd9C8640E02Ce7a"
554
+ }
555
+ };
534
556
  var sonicSupportedTokens = {
535
557
  S: {
536
558
  symbol: "S",
@@ -595,7 +617,8 @@ var sonicSupportedTokens = {
595
617
  hubAsset: "0x7c7d53EEcda37a87ce0D5bf8E0b24512A48dC963",
596
618
  vault: SodaTokens.sodaSODA.address
597
619
  },
598
- ...SodaTokens
620
+ ...SodaTokens,
621
+ ...LsodaTokens
599
622
  };
600
623
  var redbellySupportedTokens = {
601
624
  RBNT: {
@@ -3024,9 +3047,9 @@ var spokeChainConfig = {
3024
3047
  supportedTokens: bitcoinSupportedTokens,
3025
3048
  radfi: {
3026
3049
  walletMode: "TRADING",
3027
- apiUrl: "https://api.radfi.co/api",
3050
+ apiUrl: "https://api.bound.exchange/api",
3028
3051
  apiKey: "",
3029
- umsUrl: "https://ums.radfi.co/api",
3052
+ umsUrl: "https://api.ums.bound.exchange/api",
3030
3053
  accessToken: "",
3031
3054
  refreshToken: ""
3032
3055
  },
@@ -3596,7 +3619,8 @@ var swapSupportedTokens = {
3596
3619
  spokeChainConfig[ChainKeys.SONIC_MAINNET].supportedTokens.USDT,
3597
3620
  spokeChainConfig[ChainKeys.SONIC_MAINNET].supportedTokens.wS,
3598
3621
  spokeChainConfig[ChainKeys.SONIC_MAINNET].supportedTokens.SODA,
3599
- ...Object.values(SodaTokens)
3622
+ ...Object.values(SodaTokens),
3623
+ ...Object.values(LsodaTokens)
3600
3624
  ],
3601
3625
  [ChainKeys.AVALANCHE_MAINNET]: [
3602
3626
  spokeChainConfig[ChainKeys.AVALANCHE_MAINNET].supportedTokens.AVAX,
@@ -3836,6 +3860,41 @@ var swapsConfig = {
3836
3860
  var getSupportedSolverTokens = (chainId) => swapSupportedTokens[chainId];
3837
3861
  var isSwapSupportedToken = (chainId, token) => swapSupportedTokens[chainId].some((t) => t.address.toLowerCase() === token.toLowerCase());
3838
3862
 
3863
+ // ../types/dist/leverageYield/leverageYield.js
3864
+ var leverageYieldVaults = [
3865
+ {
3866
+ name: LsodaTokens.lsodaWEETH.symbol,
3867
+ vault: LsodaTokens.lsodaWEETH.vault,
3868
+ asset: SodaTokens.sodaWEETH.address,
3869
+ // sodaWEETH on Sonic
3870
+ borrowToken: SodaTokens.sodaETH.address,
3871
+ // sodaETH on Sonic
3872
+ lsdSource: {
3873
+ // DefiLlama pool for EtherFi's weETH on Ethereum (project: 'ether.fi-stake').
3874
+ poolId: "46bd2bdf-6d92-4066-b482-e885ee172264",
3875
+ fallbackAprPct: 3,
3876
+ label: "EtherFi (weETH)"
3877
+ }
3878
+ },
3879
+ {
3880
+ name: LsodaTokens.lsodaWSTETH.symbol,
3881
+ vault: LsodaTokens.lsodaWSTETH.vault,
3882
+ asset: SodaTokens.sodaWSTETH.address,
3883
+ // sodaWSTETH on Sonic
3884
+ borrowToken: SodaTokens.sodaETH.address,
3885
+ // sodaETH on Sonic
3886
+ lsdSource: {
3887
+ // DefiLlama pool for Lido's stETH on Ethereum (wstETH inherits this rate via redeem).
3888
+ poolId: "747c1d2a-c668-4682-b9f9-296708a3dd90",
3889
+ fallbackAprPct: 2.4,
3890
+ label: "Lido (stETH)"
3891
+ }
3892
+ }
3893
+ ];
3894
+ var leverageYieldConfig = {
3895
+ vaults: leverageYieldVaults
3896
+ };
3897
+
3839
3898
  // ../types/dist/sodax-config/sodax-config.js
3840
3899
  var bridgeConfig = {
3841
3900
  partnerFee: void 0
@@ -3847,6 +3906,7 @@ var sodaxConfig = {
3847
3906
  moneyMarket: moneyMarketConfig,
3848
3907
  bridge: bridgeConfig,
3849
3908
  dex: dexConfig,
3909
+ leverageYield: leverageYieldConfig,
3850
3910
  hub: hubConfig,
3851
3911
  api: apiConfig,
3852
3912
  solver: solverConfig,
@@ -3868,6 +3928,9 @@ function detectBitcoinAddressType(address) {
3868
3928
  return "P2PKH";
3869
3929
  throw new Error(`Unknown Bitcoin address type: ${address}`);
3870
3930
  }
3931
+ function usesBip322MessageSigning(addressType) {
3932
+ return addressType === "P2WPKH" || addressType === "P2TR";
3933
+ }
3871
3934
  var BTC_WALLET_ADDRESS_TYPES = ["taproot", "segwit"];
3872
3935
  var BTC_ADDRESS_TYPES = ["P2PKH", "P2SH", "P2WPKH", "P2TR"];
3873
3936
 
@@ -3985,7 +4048,7 @@ function isValidWalletProviderForChainKey(chainKey, walletProvider) {
3985
4048
  }
3986
4049
 
3987
4050
  // ../types/dist/index.js
3988
- var CONFIG_VERSION = 209;
4051
+ var CONFIG_VERSION = 211;
3989
4052
  function isEvmSpokeChainConfig(value) {
3990
4053
  return typeof value === "object" && value !== null && value.chain.type === "EVM" && value.chain.key !== HUB_CHAIN_KEY;
3991
4054
  }
@@ -4412,7 +4475,7 @@ async function waitUntilIntentExecuted(payload) {
4412
4475
  }
4413
4476
  async function relayTxAndWaitPacket(params) {
4414
4477
  try {
4415
- const { srcTxHash, data, chainKey, relayerApiEndpoint, timeout = DEFAULT_RELAY_TX_TIMEOUT } = params;
4478
+ const { srcTxHash, data, chainKey, relayerApiEndpoint, timeout = DEFAULT_RELAY_TX_TIMEOUT, pollTxHash } = params;
4416
4479
  const intentRelayChainId = getIntentRelayChainId(chainKey).toString();
4417
4480
  const isSplitTxChain = isSolanaChainKeyType(chainKey) || isBitcoinChainKeyType(chainKey);
4418
4481
  invariant(!isSplitTxChain || data !== void 0, "Data is required for Solana and Bitcoin chain keys");
@@ -4431,7 +4494,9 @@ async function relayTxAndWaitPacket(params) {
4431
4494
  if (!submitResult.ok) return submitResult;
4432
4495
  return await waitUntilIntentExecuted({
4433
4496
  intentRelayChainId,
4434
- srcTxHash,
4497
+ // The relay may track the packet under a different id than the submit tx_hash (Bitcoin
4498
+ // on-demand: submit "withdraw", poll the derived `od:<hash>`). Defaults to the submit id.
4499
+ srcTxHash: pollTxHash ?? srcTxHash,
4435
4500
  timeout,
4436
4501
  apiUrl: relayerApiEndpoint
4437
4502
  });
@@ -13270,11 +13335,43 @@ function mergeSodaxConfig(base2, override) {
13270
13335
  return merged;
13271
13336
  }
13272
13337
 
13338
+ // src/shared/logger.ts
13339
+ var consoleLogger = {
13340
+ debug: (message, data) => data ? console.debug(message, data) : console.debug(message),
13341
+ info: (message, data) => data ? console.info(message, data) : console.info(message),
13342
+ warn: (message, data) => data ? console.warn(message, data) : console.warn(message),
13343
+ error: (message, error, data) => {
13344
+ if (error !== void 0 && data !== void 0) console.error(message, error, data);
13345
+ else if (error !== void 0) console.error(message, error);
13346
+ else console.error(message);
13347
+ }
13348
+ };
13349
+ var silentLogger = {
13350
+ debug: () => {
13351
+ },
13352
+ info: () => {
13353
+ },
13354
+ warn: () => {
13355
+ },
13356
+ error: () => {
13357
+ }
13358
+ };
13359
+ function resolveLogger(option) {
13360
+ if (option === void 0 || option === "console") return consoleLogger;
13361
+ if (option === "silent") return silentLogger;
13362
+ return option;
13363
+ }
13364
+
13273
13365
  // src/shared/config/ConfigService.ts
13274
13366
  var ConfigService = class {
13275
13367
  sodax;
13276
13368
  api;
13277
13369
  userConfig;
13370
+ /**
13371
+ * SDK log sink. Resolved once at construction and kept independent of {@link sodax} so that
13372
+ * {@link initialize}'s dynamic-config swap never clobbers it. Read by services via `config.logger`.
13373
+ */
13374
+ logger;
13278
13375
  initialized = false;
13279
13376
  // data structures for quick lookup
13280
13377
  supportedHubAssetsSet;
@@ -13286,10 +13383,11 @@ var ConfigService = class {
13286
13383
  stakedATokenAddressesSet;
13287
13384
  chainToSupportedTokenAddressMap;
13288
13385
  hubAssetToXTokenMap;
13289
- constructor({ api, config, userConfig }) {
13386
+ constructor({ api, config, userConfig, logger }) {
13290
13387
  this.api = api;
13291
13388
  this.sodax = config;
13292
13389
  this.userConfig = userConfig;
13390
+ this.logger = logger ?? resolveLogger(void 0);
13293
13391
  this.loadSodaxConfigDataStructures(config);
13294
13392
  }
13295
13393
  async initialize() {
@@ -13298,7 +13396,7 @@ var ConfigService = class {
13298
13396
  if (!result.ok) return result;
13299
13397
  const response = result.value;
13300
13398
  if (!response.version || response.version < CONFIG_VERSION) {
13301
- console.warn(
13399
+ this.logger.warn(
13302
13400
  `Dynamic config version is less than the current version, resorting to the default one. Current version: ${CONFIG_VERSION}, response version: ${response.version}`
13303
13401
  );
13304
13402
  } else {
@@ -14456,7 +14554,7 @@ var RadfiProvider = class {
14456
14554
  }
14457
14555
  }
14458
14556
  /**
14459
- * Authenticate with Radfi: BIP322-sign a login message, then call the Radfi API.
14557
+ * Authenticate with Bound Exchange: BIP322-sign a login message, then call the Bound Exchange API.
14460
14558
  * Returns accessToken, refreshToken, and tradingAddress.
14461
14559
  */
14462
14560
  async authenticateWithWallet(walletProvider, cachedPublicKey) {
@@ -14472,15 +14570,14 @@ var RadfiProvider = class {
14472
14570
  throw new Error("Failed to retrieve public key from wallet. Please unlock your wallet and try again.");
14473
14571
  }
14474
14572
  const message = `${Date.now()}`;
14475
- const addressType = detectBitcoinAddressType(address);
14476
- const signature = addressType === "P2WPKH" || addressType === "P2TR" ? await walletProvider.signBip322Message(message) : await walletProvider.signEcdsaMessage(message);
14573
+ const signature = usesBip322MessageSigning(detectBitcoinAddressType(address)) ? await walletProvider.signBip322Message(message) : await walletProvider.signEcdsaMessage(message);
14477
14574
  const result = await this.authenticate({ message, signature, address, publicKey });
14478
14575
  this.setRadfiAccessToken(result.accessToken, result.refreshToken);
14479
14576
  return { ...result, publicKey };
14480
14577
  }
14481
14578
  /**
14482
- * Ensure a valid Radfi access token is set on this provider.
14483
- * If a token exists, validates it via the Radfi API.
14579
+ * Ensure a valid Bound Exchange access token is set on this provider.
14580
+ * If a token exists, validates it via the Bound Exchange API.
14484
14581
  * If invalid, tries refreshing with the refresh token first.
14485
14582
  * If refresh also fails, falls back to full re-authentication (BIP322 sign).
14486
14583
  */
@@ -14513,7 +14610,7 @@ var RadfiProvider = class {
14513
14610
  });
14514
14611
  if (!res.ok) {
14515
14612
  const err = await res.json();
14516
- throw new RadfiApiError(res.status, err, "Radfi authentication failed");
14613
+ throw new RadfiApiError(res.status, err, "Bound Exchange authentication failed");
14517
14614
  }
14518
14615
  return res.json().then((r) => ({
14519
14616
  accessToken: r.data?.accessToken ?? "",
@@ -14605,12 +14702,20 @@ var RadfiProvider = class {
14605
14702
  }
14606
14703
  })
14607
14704
  });
14608
- if (!res.ok) {
14609
- const err = await res.json();
14610
- throw new RadfiApiError(res.status, err, "Radfi transaction request failed");
14705
+ const body = await res.json();
14706
+ if (!res.ok || !body?.data) {
14707
+ throw new RadfiApiError(res.status, body, "Bound Exchange transaction request failed");
14611
14708
  }
14612
- return res.json().then((r) => r.data);
14709
+ return body.data;
14613
14710
  }
14711
+ /**
14712
+ * Co-sign and broadcast a `sodax-withdraw` deposit via the Bound Exchange API.
14713
+ *
14714
+ * `relayData` ({ address, payload }) is the same `RelayExtraData` the SDK returns from
14715
+ * `createIntent()` / money-market supply etc. It is optional and non-breaking: when supplied,
14716
+ * the Bound Exchange backend persists it so it can auto-resubmit the intent relay if the relay
14717
+ * gets stuck (otherwise a stuck relay eventually refunds instead of completing the swap).
14718
+ */
14614
14719
  async requestRadfiSignature(params, accessToken) {
14615
14720
  const res = await this.request("/sodax/transaction/sign", {
14616
14721
  method: "POST",
@@ -14624,7 +14729,7 @@ var RadfiProvider = class {
14624
14729
  });
14625
14730
  if (!res.ok) {
14626
14731
  const err = await res.json();
14627
- throw new RadfiApiError(res.status, err, "Radfi signature request failed");
14732
+ throw new RadfiApiError(res.status, err, "Bound Exchange signature request failed");
14628
14733
  }
14629
14734
  return res.json().then((r) => r.data.txId);
14630
14735
  }
@@ -14648,7 +14753,7 @@ var RadfiProvider = class {
14648
14753
  return res.json();
14649
14754
  }
14650
14755
  /**
14651
- * Build a renew-utxo transaction via the Radfi API.
14756
+ * Build a renew-utxo transaction via the Bound Exchange API.
14652
14757
  * Returns a PSBT that needs to be signed by the user.
14653
14758
  */
14654
14759
  async buildRenewUtxoTransaction(params, accessToken) {
@@ -14672,8 +14777,8 @@ var RadfiProvider = class {
14672
14777
  return res.json().then((r) => r.data);
14673
14778
  }
14674
14779
  /**
14675
- * Sign and broadcast a renew-utxo transaction via the Radfi API.
14676
- * The user signs the PSBT first, then Radfi co-signs and broadcasts.
14780
+ * Sign and broadcast a renew-utxo transaction via the Bound Exchange API.
14781
+ * The user signs the PSBT first, then Bound Exchange co-signs and broadcasts.
14677
14782
  */
14678
14783
  async signAndBroadcastRenewUtxo(params, accessToken) {
14679
14784
  const res = await this.request("/transactions/sign", {
@@ -14714,7 +14819,7 @@ var RadfiProvider = class {
14714
14819
  return res.json().then((r) => r.data);
14715
14820
  }
14716
14821
  /**
14717
- * Sign and broadcast a withdraw transaction via Radfi.
14822
+ * Sign and broadcast a withdraw transaction via Bound Exchange.
14718
14823
  */
14719
14824
  async signAndBroadcastWithdraw(params, accessToken) {
14720
14825
  const res = await this.request("/transactions/sign", {
@@ -14768,7 +14873,20 @@ var RadfiProvider = class {
14768
14873
  };
14769
14874
 
14770
14875
  // src/shared/entities/btc/btc-utils.ts
14771
- function estimateBitcoinTxSize(inputCount, outputCount, addressType) {
14876
+ var BITCOIN_FEE_SAFETY_VBYTES = 20;
14877
+ function calcOpReturnOutputVbytes(payloadByteLength) {
14878
+ let scriptSize;
14879
+ if (payloadByteLength <= 75) {
14880
+ scriptSize = 3 + payloadByteLength;
14881
+ } else if (payloadByteLength <= 255) {
14882
+ scriptSize = 4 + payloadByteLength;
14883
+ } else {
14884
+ scriptSize = 5 + payloadByteLength;
14885
+ }
14886
+ const scriptLenVarint = scriptSize <= 252 ? 1 : 3;
14887
+ return 8 + scriptLenVarint + scriptSize;
14888
+ }
14889
+ function estimateBitcoinTxSize(inputCount, outputCount, addressType, opReturnOutputVbytes = 44) {
14772
14890
  let inputWeight;
14773
14891
  switch (addressType) {
14774
14892
  case "P2PKH":
@@ -14784,7 +14902,9 @@ function estimateBitcoinTxSize(inputCount, outputCount, addressType) {
14784
14902
  inputWeight = 68;
14785
14903
  break;
14786
14904
  }
14787
- return Math.ceil(10.5 + 44 + inputCount * inputWeight + outputCount * 31);
14905
+ return Math.ceil(
14906
+ 10.5 + opReturnOutputVbytes + BITCOIN_FEE_SAFETY_VBYTES + inputCount * inputWeight + outputCount * 31
14907
+ );
14788
14908
  }
14789
14909
  function encodeBtcPayloadToBytes(payload) {
14790
14910
  return JSON.stringify({
@@ -14801,6 +14921,10 @@ function normalizePsbtToBase64(signedPsbt) {
14801
14921
  const isHex = /^[0-9a-fA-F]+$/.test(signedPsbt);
14802
14922
  return isHex ? Buffer.from(signedPsbt, "hex").toString("base64") : signedPsbt;
14803
14923
  }
14924
+ function normalizeSignatureToBase64(signature) {
14925
+ const isHex = /^[0-9a-fA-F]+$/.test(signature);
14926
+ return isHex ? Buffer.from(signature, "hex").toString("base64") : signature;
14927
+ }
14804
14928
 
14805
14929
  // src/shared/services/spoke/BitcoinSpokeService.ts
14806
14930
  initEccLib(ecc);
@@ -14909,7 +15033,7 @@ var BitcoinSpokeService = class {
14909
15033
  return BigInt(totalBalance);
14910
15034
  }
14911
15035
  /**
14912
- * Fund the Radfi trading wallet by sending BTC from the user's personal wallet
15036
+ * Fund the Bound Exchange trading wallet by sending BTC from the user's personal wallet
14913
15037
  *
14914
15038
  * @param {bigint} amount - Amount in satoshis to send
14915
15039
  * @param {BitcoinSpokeProvider} spokeProvider - The Bitcoin spoke provider (must have signing capability)
@@ -14933,10 +15057,28 @@ var BitcoinSpokeService = class {
14933
15057
  async sendMessage(params) {
14934
15058
  return await this.encodeWithdrawalData(params);
14935
15059
  }
15060
+ /**
15061
+ * Build the relay submit/poll identity for an on-demand action (borrow/withdraw).
15062
+ *
15063
+ * Bitcoin borrow/withdraw are on-demand: there is no broadcast transaction — the spoke result is
15064
+ * the signed payload JSON produced by {@link encodeWithdrawalData}/{@link sendMessage}. The relay
15065
+ * accepts the submit under the literal `withdraw` tx_hash with the signed payload (as a JSON object)
15066
+ * in `data`, then tracks the resulting packet under a derived id: `od:` + keccak256 of the ASCII
15067
+ * `payload_hex` string (hash the hex characters, not the decoded bytes). Polling must use that
15068
+ * derived id (`pollTxHash`), not `withdraw`.
15069
+ *
15070
+ * @param tx - The JSON-stringified signed payload returned by `sendMessage` / `encodeWithdrawalData`.
15071
+ */
15072
+ getOnDemandRelayIdentity(tx) {
15073
+ const data = JSON.parse(tx);
15074
+ const payloadHex = data.payload_hex.startsWith("0x") ? data.payload_hex.slice(2) : data.payload_hex;
15075
+ const pollTxHash = `od:${keccak256(stringToBytes(payloadHex)).slice(2)}`;
15076
+ return { srcTxHash: "withdraw", data, pollTxHash };
15077
+ }
14936
15078
  /**
14937
15079
  * Build a priority Bitcoin transaction with proper fee calculation
14938
15080
  */
14939
- async buildBitcoinTransaction(utxos, outputs, changeAddress, chainId, walletProvider, feeRate) {
15081
+ async buildBitcoinTransaction(utxos, outputs, changeAddress, chainId, walletProvider, feeRate, opReturnOutputVbytes) {
14940
15082
  const psbt = new Psbt({ network: this.getBtcNetwork(chainId) });
14941
15083
  const effectiveFeeRate = feeRate ?? await this.getFeeRateEstimate();
14942
15084
  const walletAddress = await walletProvider.getWalletAddress();
@@ -14999,7 +15141,7 @@ var BitcoinSpokeService = class {
14999
15141
  });
15000
15142
  }
15001
15143
  inputSum += utxo.value;
15002
- const estimatedSize = estimateBitcoinTxSize(psbt.inputCount, outputs.length, addressType);
15144
+ const estimatedSize = estimateBitcoinTxSize(psbt.inputCount, outputs.length, addressType, opReturnOutputVbytes);
15003
15145
  const estimatedFee = Math.ceil(effectiveFeeRate * estimatedSize);
15004
15146
  if (inputSum >= outputSum + estimatedFee + DUST_THRESHOLD) {
15005
15147
  break;
@@ -15011,8 +15153,13 @@ var BitcoinSpokeService = class {
15011
15153
  value: output.value
15012
15154
  });
15013
15155
  }
15014
- const sizeWithChange = estimateBitcoinTxSize(psbt.inputCount, outputs.length + 1, addressType);
15015
- const sizeWithoutChange = estimateBitcoinTxSize(psbt.inputCount, outputs.length, addressType);
15156
+ const sizeWithChange = estimateBitcoinTxSize(
15157
+ psbt.inputCount,
15158
+ outputs.length + 1,
15159
+ addressType,
15160
+ opReturnOutputVbytes
15161
+ );
15162
+ const sizeWithoutChange = estimateBitcoinTxSize(psbt.inputCount, outputs.length, addressType, opReturnOutputVbytes);
15016
15163
  const feeWithChange = Math.ceil(effectiveFeeRate * sizeWithChange);
15017
15164
  const feeWithoutChange = Math.ceil(effectiveFeeRate * sizeWithoutChange);
15018
15165
  let change = inputSum - outputSum - feeWithChange;
@@ -15081,7 +15228,12 @@ var BitcoinSpokeService = class {
15081
15228
  return await this.radfi.requestRadfiSignature(
15082
15229
  {
15083
15230
  userAddress: from,
15084
- signedBase64Tx
15231
+ signedBase64Tx,
15232
+ // Forward the relay identity ({ hub wallet address, full payload }) so the Bound Exchange
15233
+ // backend can auto-resubmit the intent relay if it gets stuck. `to` is the hub wallet
15234
+ // (relayData.address) and `data` is the full payload (relayData.payload) — the same pair
15235
+ // feature services return as `relayData` from createIntent()/supply().
15236
+ relayData: { address: params.to, payload: data }
15085
15237
  },
15086
15238
  accessToken
15087
15239
  );
@@ -15091,7 +15243,11 @@ var BitcoinSpokeService = class {
15091
15243
  "Raw mode is not supported for normal Bitcoin deposits. Use TRADING wallet mode for raw transactions."
15092
15244
  );
15093
15245
  }
15094
- const utxos = await this.fetchUTXOs(from);
15246
+ const [allUtxos, mempoolSpent] = await Promise.all([
15247
+ this.fetchUTXOs(from),
15248
+ this.fetchMempoolSpentOutpoints(from)
15249
+ ]);
15250
+ const utxos = allUtxos.filter((u) => !mempoolSpent.has(`${u.txid}:${u.vout}`));
15095
15251
  if (!utxos?.length) {
15096
15252
  throw new Error("No UTXOs available for deposit");
15097
15253
  }
@@ -15106,7 +15262,7 @@ var BitcoinSpokeService = class {
15106
15262
  );
15107
15263
  return await this.signAndBroadcastTransaction(depositPsbt, params.walletProvider);
15108
15264
  } catch (error) {
15109
- console.error("Error during deposit:", error);
15265
+ this.config.logger.error("Error during deposit", error);
15110
15266
  throw error;
15111
15267
  }
15112
15268
  }
@@ -15114,22 +15270,37 @@ var BitcoinSpokeService = class {
15114
15270
  * Build deposit PSBT with embedded cross-chain data
15115
15271
  */
15116
15272
  async buildDepositPsbt(walletAddress, walletProvider, srcChainKey, token, amount, data, utxos) {
15117
- const assetManagerAddress = this.config.getChainConfig(srcChainKey).addresses.assetManager;
15118
- if (token.toLocaleLowerCase() === "btc") {
15273
+ const chainConfig = this.config.getChainConfig(srcChainKey);
15274
+ const assetManagerAddress = chainConfig.addresses.assetManager;
15275
+ const normalizedToken = token.toLocaleLowerCase();
15276
+ const nativeBtcTokens = new Set(
15277
+ ["btc", chainConfig.nativeToken, chainConfig.supportedTokens.BTC?.address].filter((value) => !!value).map((value) => value.toLocaleLowerCase())
15278
+ );
15279
+ const isNativeBtc = nativeBtcTokens.has(normalizedToken);
15280
+ if (isNativeBtc) {
15281
+ const OP_RETURN = opcodes.OP_RETURN;
15282
+ const OP_12 = opcodes.OP_12;
15283
+ if (OP_RETURN === void 0 || OP_12 === void 0) {
15284
+ throw new Error("bitcoinjs-lib opcodes OP_RETURN or OP_12 are undefined");
15285
+ }
15286
+ const OP_RADFI_SODAX_DATA = 49;
15287
+ const payload = Buffer.concat([Buffer.from([OP_RADFI_SODAX_DATA]), Buffer.from(data.slice(2), "hex")]);
15288
+ const opReturnOutputVbytes = calcOpReturnOutputVbytes(payload.length);
15119
15289
  const outputs = [
15120
15290
  {
15121
15291
  address: assetManagerAddress,
15122
15292
  value: Number(amount)
15123
15293
  }
15124
15294
  ];
15125
- const psbt = await this.buildBitcoinTransaction(utxos, outputs, walletAddress, srcChainKey, walletProvider);
15126
- const OP_RADFI_SODAX_DATA = 49;
15127
- const payload = Buffer.concat([Buffer.from([OP_RADFI_SODAX_DATA]), Buffer.from(data.slice(2), "hex")]);
15128
- const OP_RETURN = opcodes.OP_RETURN;
15129
- const OP_12 = opcodes.OP_12;
15130
- if (OP_RETURN === void 0 || OP_12 === void 0) {
15131
- throw new Error("bitcoinjs-lib opcodes OP_RETURN or OP_12 are undefined");
15132
- }
15295
+ const psbt = await this.buildBitcoinTransaction(
15296
+ utxos,
15297
+ outputs,
15298
+ walletAddress,
15299
+ srcChainKey,
15300
+ walletProvider,
15301
+ void 0,
15302
+ opReturnOutputVbytes
15303
+ );
15133
15304
  const compiledScript = script.compile([OP_RETURN, OP_12, payload]);
15134
15305
  psbt.addOutput({
15135
15306
  script: compiledScript,
@@ -15149,6 +15320,27 @@ var BitcoinSpokeService = class {
15149
15320
  }
15150
15321
  return await response.json();
15151
15322
  }
15323
+ /**
15324
+ * Returns the set of "txid:vout" outpoints currently being spent by
15325
+ * unconfirmed transactions in the mempool for the given address.
15326
+ * Used to prevent double-spend when building a new PSBT.
15327
+ */
15328
+ async fetchMempoolSpentOutpoints(address) {
15329
+ try {
15330
+ const response = await fetch(`${this.rpcUrl}/address/${address}/txs/mempool`);
15331
+ if (!response.ok) return /* @__PURE__ */ new Set();
15332
+ const mempoolTxs = await response.json();
15333
+ const spent = /* @__PURE__ */ new Set();
15334
+ for (const tx of mempoolTxs) {
15335
+ for (const input of tx.vin) {
15336
+ spent.add(`${input.txid}:${input.vout}`);
15337
+ }
15338
+ }
15339
+ return spent;
15340
+ } catch {
15341
+ return /* @__PURE__ */ new Set();
15342
+ }
15343
+ }
15152
15344
  /**
15153
15345
  * Fetch raw transaction hex
15154
15346
  */
@@ -15164,7 +15356,7 @@ var BitcoinSpokeService = class {
15164
15356
  let srcAddress = from;
15165
15357
  const addressType = detectBitcoinAddressType(from);
15166
15358
  if (walletMode === "TRADING") {
15167
- srcAddress = await this.radfi.getTradingWallet(srcAddress).then((res) => res.tradingAddress).catch(() => srcAddress);
15359
+ srcAddress = (await this.radfi.getTradingWallet(srcAddress)).tradingAddress;
15168
15360
  }
15169
15361
  const payload = {
15170
15362
  src_address: srcAddress,
@@ -15183,8 +15375,12 @@ var BitcoinSpokeService = class {
15183
15375
  if (params.raw === true) {
15184
15376
  return JSON.stringify(onDemandWithdraw);
15185
15377
  }
15186
- const signature = await params.walletProvider.signEcdsaMessage(orderedPayload);
15187
- onDemandWithdraw.signature = signature;
15378
+ if (!params.walletProvider.getPublicKey) {
15379
+ throw new Error("Wallet provider does not support getPublicKey");
15380
+ }
15381
+ const rawSignature = usesBip322MessageSigning(addressType) ? await params.walletProvider.signBip322Message(orderedPayload) : await params.walletProvider.signEcdsaMessage(orderedPayload);
15382
+ onDemandWithdraw.signature = normalizeSignatureToBase64(rawSignature);
15383
+ onDemandWithdraw.public_key = await params.walletProvider.getPublicKey();
15188
15384
  return JSON.stringify(onDemandWithdraw);
15189
15385
  }
15190
15386
  /**
@@ -15192,8 +15388,14 @@ var BitcoinSpokeService = class {
15192
15388
  */
15193
15389
  async signAndBroadcastTransaction(psbt, walletProvider) {
15194
15390
  const psbtBase64 = typeof psbt === "string" ? psbt : psbt.toBase64();
15195
- const signedPsbtHex = await walletProvider.signTransaction(psbtBase64);
15196
- const txHash = await this.broadcastTransaction(signedPsbtHex);
15391
+ const signedRaw = await walletProvider.signTransaction(psbtBase64, false);
15392
+ const signedPsbt = Psbt.fromBase64(normalizePsbtToBase64(signedRaw));
15393
+ try {
15394
+ signedPsbt.finalizeAllInputs();
15395
+ } catch {
15396
+ }
15397
+ const txHex = signedPsbt.extractTransaction().toHex();
15398
+ const txHash = await this.broadcastTransaction(txHex);
15197
15399
  return txHash;
15198
15400
  }
15199
15401
  /**
@@ -17432,7 +17634,7 @@ function base64Decode(str) {
17432
17634
  }
17433
17635
  var encoder = new TextEncoder();
17434
17636
  var decoder = new TextDecoder();
17435
- function stringToBytes(str) {
17637
+ function stringToBytes2(str) {
17436
17638
  return encoder.encode(str);
17437
17639
  }
17438
17640
  function bytesToString(bytes) {
@@ -20829,7 +21031,7 @@ var JsonRpcProvider = class {
20829
21031
  };
20830
21032
  }
20831
21033
  async viewContractState({ contractId, prefix: prefix2, blockQuery = { finality: DEFAULT_FINALITY } }) {
20832
- const prefixBase64 = base64Encode(stringToBytes(prefix2 || ""));
21034
+ const prefixBase64 = base64Encode(stringToBytes2(prefix2 || ""));
20833
21035
  let reference;
20834
21036
  if ("blockId" in blockQuery) {
20835
21037
  reference = { block_id: blockQuery.blockId };
@@ -20857,7 +21059,7 @@ var JsonRpcProvider = class {
20857
21059
  }
20858
21060
  }
20859
21061
  async callFunctionRaw({ contractId, method, args, blockQuery = { finality: DEFAULT_FINALITY } }) {
20860
- const argsBytes = args instanceof Uint8Array ? args : stringToBytes(JSON.stringify(args));
21062
+ const argsBytes = args instanceof Uint8Array ? args : stringToBytes2(JSON.stringify(args));
20861
21063
  const argsBase64 = base64Encode(argsBytes);
20862
21064
  if ("blockId" in blockQuery) {
20863
21065
  return this.query({
@@ -21123,6 +21325,7 @@ var JsonRpcProvider = class {
21123
21325
 
21124
21326
  // src/shared/services/spoke/NearSpokeService.ts
21125
21327
  var NEAR_DEFAULT_GAS = BigInt("300000000000000");
21328
+ var NEAR_STORAGE_DEPOSIT = BigInt("1250000000000000000000");
21126
21329
  var NearSpokeService = class {
21127
21330
  config;
21128
21331
  rpcProvider;
@@ -21172,7 +21375,7 @@ var NearSpokeService = class {
21172
21375
  token: Array.from(Buffer.from(fillData.token, "utf-8"))
21173
21376
  };
21174
21377
  }
21175
- async fillIntent(fromInfo, fillData, deposit = BigInt("0"), gas = BigInt("300000000000000")) {
21378
+ async fillIntent(fromInfo, fillData, deposit = BigInt("1"), gas = BigInt("300000000000000")) {
21176
21379
  const intentFiller = this.config.getChainConfig(fromInfo.srcChainKey).addresses.intentFiller;
21177
21380
  if (isNativeToken(fromInfo.srcChainKey, fillData.token)) {
21178
21381
  deposit = BigInt(fillData.amount);
@@ -21246,7 +21449,8 @@ var NearSpokeService = class {
21246
21449
  data: inputParams.data
21247
21450
  })
21248
21451
  },
21249
- deposit: BigInt("0"),
21452
+ // NEP-141 requires exactly 1 yoctoNEAR attached to ft_transfer_call.
21453
+ deposit: BigInt("1"),
21250
21454
  gas: NEAR_DEFAULT_GAS
21251
21455
  }
21252
21456
  };
@@ -21277,6 +21481,52 @@ var NearSpokeService = class {
21277
21481
  }
21278
21482
  return BigInt(bal);
21279
21483
  }
21484
+ /**
21485
+ * Whether `accountId` is storage-registered on a NEP-141 `token` contract. NEP-141 requires an
21486
+ * account to pay a one-time storage bond before it can receive (hold a balance of) the token, so
21487
+ * this gates any leg that delivers a token to a user on NEAR (swap output on NEAR, bridge into
21488
+ * NEAR, money-market borrow/withdraw to NEAR).
21489
+ *
21490
+ * Native NEAR is not a NEP-141 token and has no storage registration, so this returns `true` for
21491
+ * the native token. The view goes through {@link queryContract}, i.e. the configurable RPC from
21492
+ * chain config — a custom `rpcUrl` passed to the SDK is honoured.
21493
+ */
21494
+ async isStorageRegistered(token, accountId) {
21495
+ if (isNativeToken(ChainKeys.NEAR_MAINNET, token)) {
21496
+ return true;
21497
+ }
21498
+ const balance = await this.queryContract(token, "storage_balance_of", { account_id: accountId });
21499
+ return balance != null;
21500
+ }
21501
+ /**
21502
+ * Build (and, unless `raw`, submit) a NEP-141 `storage_deposit` registration for `accountId` on
21503
+ * the `token` contract. Call this when {@link isStorageRegistered} is `false` before a token is
21504
+ * delivered to the account on NEAR.
21505
+ *
21506
+ * Native NEAR has no storage registration — passing the native token throws.
21507
+ *
21508
+ * @param params.deposit Storage bond to attach; defaults to {@link NEAR_STORAGE_DEPOSIT}
21509
+ * (0.00125 NEAR). Override per token if its `storage_balance_bounds.min` differs.
21510
+ */
21511
+ async registerStorage(params) {
21512
+ if (isNativeToken(ChainKeys.NEAR_MAINNET, params.token)) {
21513
+ throw new Error("[NearSpokeService.registerStorage] Native NEAR has no NEP-141 storage registration.");
21514
+ }
21515
+ const tx = {
21516
+ signerId: params.accountId,
21517
+ params: {
21518
+ contractId: params.token,
21519
+ method: "storage_deposit",
21520
+ args: { account_id: params.accountId, registration_only: true },
21521
+ deposit: params.deposit ?? NEAR_STORAGE_DEPOSIT,
21522
+ gas: NEAR_DEFAULT_GAS
21523
+ }
21524
+ };
21525
+ if (params.raw === true) {
21526
+ return tx;
21527
+ }
21528
+ return params.walletProvider.signAndSubmitTxn(tx);
21529
+ }
21280
21530
  /**
21281
21531
  * Sends a message to the hub chain.
21282
21532
  * @param {SendMessageParams} params - Includes dstChainKey, the chain key of the hub chain.
@@ -22077,17 +22327,11 @@ var SuiSpokeService = class {
22077
22327
  ]
22078
22328
  });
22079
22329
  if (params.raw === true) {
22080
- tx.setSender(from);
22081
- const transactionRaw = await tx.build({
22082
- client: this.publicClient,
22083
- onlyTransactionKind: true
22084
- });
22085
- const transactionRawBase64String = Buffer.from(transactionRaw).toString("base64");
22086
22330
  return {
22087
22331
  from,
22088
22332
  to: `${assetManager.packageId}::${assetManager.moduleId}::transfer`,
22089
22333
  value: amount,
22090
- data: transactionRawBase64String
22334
+ data: tx.serialize()
22091
22335
  };
22092
22336
  }
22093
22337
  return params.walletProvider.signAndExecuteTxn(tx);
@@ -22107,17 +22351,11 @@ var SuiSpokeService = class {
22107
22351
  ]
22108
22352
  });
22109
22353
  if (params.raw === true) {
22110
- txb.setSender(from);
22111
- const transactionRaw = await txb.build({
22112
- client: this.publicClient,
22113
- onlyTransactionKind: true
22114
- });
22115
- const transactionRawBase64String = Buffer.from(transactionRaw).toString("base64");
22116
22354
  return {
22117
22355
  from,
22118
22356
  to: `${connection.packageId}::${connection.moduleId}::send_message_ua`,
22119
22357
  value: 0n,
22120
- data: transactionRawBase64String
22358
+ data: txb.serialize()
22121
22359
  };
22122
22360
  }
22123
22361
  return params.walletProvider.signAndExecuteTxn(txb);
@@ -22128,7 +22366,7 @@ var SuiSpokeService = class {
22128
22366
  * @returns {Promise<bigint>} The estimated computation cost.
22129
22367
  */
22130
22368
  async estimateGas({ tx }) {
22131
- const txb = Transaction$1.fromKind(tx.data);
22369
+ const txb = Transaction$1.from(tx.data);
22132
22370
  const result = await this.publicClient.devInspectTransactionBlock({
22133
22371
  sender: tx.from,
22134
22372
  transactionBlock: txb
@@ -22359,6 +22597,7 @@ var CustomStellarAccount = class {
22359
22597
  decrementSequenceNumber() {
22360
22598
  if (this.sequenceNumber > this.startingSequenceNumber) {
22361
22599
  this.sequenceNumber--;
22600
+ return;
22362
22601
  }
22363
22602
  throw new Error(
22364
22603
  `Sequence number cannot be decremented below the starting sequence number: ${this.startingSequenceNumber}`
@@ -22465,7 +22704,7 @@ var StellarSpokeService = class {
22465
22704
  );
22466
22705
  const assembledPriorityTx = rpc.assembleTransaction(rawPriorityTx, simulation).build();
22467
22706
  if (params.raw) {
22468
- const transactionXdr = rawPriorityTx.toXDR();
22707
+ const transactionXdr = assembledPriorityTx.toXDR();
22469
22708
  return {
22470
22709
  from,
22471
22710
  to: this.config.getChainConfig(srcChainKey).addresses.assetManager,
@@ -22484,13 +22723,13 @@ var StellarSpokeService = class {
22484
22723
  );
22485
22724
  return `${hash}`;
22486
22725
  } catch (error) {
22487
- console.error("Error during sendMessage:", error);
22726
+ this.config.logger.error("Error during sendMessage", error);
22488
22727
  throw error;
22489
22728
  }
22490
22729
  }
22491
22730
  handleSendTransactionError(response) {
22492
22731
  if (response.status === "ERROR") {
22493
- console.error(JSON.stringify(response, null, 2));
22732
+ this.config.logger.error(JSON.stringify(response, null, 2));
22494
22733
  throw new Error(JSON.stringify(response, null, 2));
22495
22734
  }
22496
22735
  return response;
@@ -22582,7 +22821,7 @@ var StellarSpokeService = class {
22582
22821
  );
22583
22822
  const assembledPriorityTx = rpc.assembleTransaction(rawPriorityTx, simulation).build();
22584
22823
  if (params.raw) {
22585
- const transactionXdr = rawPriorityTx.toXDR();
22824
+ const transactionXdr = assembledPriorityTx.toXDR();
22586
22825
  return {
22587
22826
  from,
22588
22827
  to: this.config.getChainConfig(srcChainKey).addresses.assetManager,
@@ -22601,7 +22840,7 @@ var StellarSpokeService = class {
22601
22840
  );
22602
22841
  return `${hash}`;
22603
22842
  } catch (error) {
22604
- console.error("Error during deposit:", error);
22843
+ this.config.logger.error("Error during deposit", error);
22605
22844
  throw error;
22606
22845
  }
22607
22846
  }
@@ -22629,7 +22868,7 @@ var StellarSpokeService = class {
22629
22868
  (balance2) => "limit" in balance2 && "balance" in balance2 && "asset_code" in balance2 && trustlineConfig.assetCode.toLowerCase() === balance2.asset_code?.toLowerCase() && "asset_issuer" in balance2 && trustlineConfig.assetIssuer.toLowerCase() === balance2.asset_issuer?.toLowerCase()
22630
22869
  );
22631
22870
  if (!tokenBalance) {
22632
- console.error(`No token balances found for token: ${token}`);
22871
+ this.config.logger.error(`No token balances found for token: ${token}`);
22633
22872
  return false;
22634
22873
  }
22635
22874
  const limit = parseToStroops(tokenBalance.limit);
@@ -22678,7 +22917,7 @@ var StellarSpokeService = class {
22678
22917
  const hash = await this.signAndSendTransaction(walletProvider, transaction);
22679
22918
  return `${hash}`;
22680
22919
  } catch (error) {
22681
- console.error("Error during requestTrustline:", error);
22920
+ this.config.logger.error("Error during requestTrustline", error);
22682
22921
  throw error;
22683
22922
  }
22684
22923
  }
@@ -23537,7 +23776,7 @@ var SpokeService = class _SpokeService {
23537
23776
  }
23538
23777
  default: {
23539
23778
  const exhaustiveCheck = chainType;
23540
- console.log(exhaustiveCheck);
23779
+ this.config.logger.debug("Unhandled exhaustive case", { value: exhaustiveCheck });
23541
23780
  throw new Error(`[getSpokeService] Invalid chain type. Valid chain types: ${ChainTypeArr.join(", ")}`);
23542
23781
  }
23543
23782
  }
@@ -23710,7 +23949,7 @@ var SpokeService = class _SpokeService {
23710
23949
  }
23711
23950
  default: {
23712
23951
  const exhaustiveCheck = chainType;
23713
- console.log(exhaustiveCheck);
23952
+ this.config.logger.debug("Unhandled exhaustive case", { value: exhaustiveCheck });
23714
23953
  return {
23715
23954
  ok: false,
23716
23955
  error: new Error(`[estimateGas] Invalid chain type. Valid chain types: ${ChainTypeArr.join(", ")}`)
@@ -23817,17 +24056,17 @@ var SpokeService = class _SpokeService {
23817
24056
  args: [params.srcChainId, params.srcAddress, params.payload]
23818
24057
  })
23819
24058
  });
23820
- console.warn("simulateRecvMessage did not revert as expected", { result });
24059
+ this.config.logger.warn("simulateRecvMessage did not revert as expected", { result });
23821
24060
  return {
23822
24061
  ok: false,
23823
24062
  error: new Error('Function should have reverted with "Simulation completed"')
23824
24063
  };
23825
24064
  } catch (error) {
23826
24065
  if (error instanceof Error && error.message?.includes("Simulation completed")) {
23827
- console.warn("simulateRecvMessage completed successfully with expected revert");
24066
+ this.config.logger.warn("simulateRecvMessage completed successfully with expected revert");
23828
24067
  return { ok: true, value: true };
23829
24068
  }
23830
- console.error("simulateRecvMessage failed with unexpected error:", error);
24069
+ this.config.logger.error("simulateRecvMessage failed with unexpected error", error);
23831
24070
  return { ok: false, error };
23832
24071
  }
23833
24072
  }
@@ -23912,7 +24151,7 @@ var SpokeService = class _SpokeService {
23912
24151
  }
23913
24152
  default: {
23914
24153
  const exhaustiveCheck = chainType;
23915
- console.log(exhaustiveCheck);
24154
+ this.config.logger.debug("Unhandled exhaustive case", { value: exhaustiveCheck });
23916
24155
  return {
23917
24156
  ok: false,
23918
24157
  error: new Error(`[deposit] Invalid chain type. Valid chain types: ${ChainTypeArr.join(", ")}`)
@@ -23989,7 +24228,7 @@ var SpokeService = class _SpokeService {
23989
24228
  }
23990
24229
  default: {
23991
24230
  const exhaustiveCheck = chainType;
23992
- console.log(exhaustiveCheck);
24231
+ this.config.logger.debug("Unhandled exhaustive case", { value: exhaustiveCheck });
23993
24232
  return {
23994
24233
  ok: false,
23995
24234
  error: new Error(`[getDeposit] Invalid chain type. Valid chain types: ${ChainTypeArr.join(", ")}`)
@@ -24098,7 +24337,7 @@ var SpokeService = class _SpokeService {
24098
24337
  }
24099
24338
  default: {
24100
24339
  const exhaustiveCheck = chainType;
24101
- console.log(exhaustiveCheck);
24340
+ this.config.logger.debug("Unhandled exhaustive case", { value: exhaustiveCheck });
24102
24341
  return {
24103
24342
  ok: false,
24104
24343
  error: new Error(`[sendMessage] Invalid chain type. Valid chain types: ${ChainTypeArr.join(", ")}`)
@@ -24177,10 +24416,10 @@ var SpokeService = class _SpokeService {
24177
24416
  if (isSolanaChainKeyType(chainKey)) {
24178
24417
  const result = await this.solana.waitForTransactionReceipt({ txHash, chainKey });
24179
24418
  if (!result.ok || result.value.status !== "success") {
24180
- console.warn(
24419
+ this.config.logger.warn(
24181
24420
  `Solana verifyTxHash failed: ${!result.ok ? result.error : "error" in result.value ? result.value.error : "unknown"}`
24182
24421
  );
24183
- console.warn("Returning true to assume transaction exists on chain in future ");
24422
+ this.config.logger.warn("Returning true to assume transaction exists on chain in future ");
24184
24423
  return { ok: true, value: true };
24185
24424
  }
24186
24425
  return { ok: true, value: true };
@@ -24267,7 +24506,7 @@ var SpokeService = class _SpokeService {
24267
24506
  }
24268
24507
  default: {
24269
24508
  const exhaustiveCheck = chainType;
24270
- console.log(exhaustiveCheck);
24509
+ this.config.logger.debug("Unhandled exhaustive case", { value: exhaustiveCheck });
24271
24510
  return { ok: false, error: new Error(`waitForTransactionReceipt not supported for ${params.chainKey}`) };
24272
24511
  }
24273
24512
  }
@@ -25318,6 +25557,7 @@ var EvmHubProvider = class {
25318
25557
  };
25319
25558
 
25320
25559
  // src/swap/SolverApiService.ts
25560
+ var bigintReplacer = (_key, value) => typeof value === "bigint" ? value.toString() : value;
25321
25561
  var SolverApiService = class {
25322
25562
  constructor() {
25323
25563
  }
@@ -25386,13 +25626,16 @@ var SolverApiService = class {
25386
25626
  }
25387
25627
  };
25388
25628
  } catch (e) {
25389
- console.error(`[SolverApiService.getQuote] failed. Details: ${JSON.stringify(e)}`);
25629
+ configService.logger.error(
25630
+ "[SolverApiService.getQuote] failed",
25631
+ e instanceof Error ? e : new Error(JSON.stringify(e, bigintReplacer))
25632
+ );
25390
25633
  return {
25391
25634
  ok: false,
25392
25635
  error: {
25393
25636
  detail: {
25394
25637
  code: SolverIntentErrorCode.UNKNOWN,
25395
- message: e ? JSON.stringify(e) : "Unknown error"
25638
+ message: e ? JSON.stringify(e, bigintReplacer) : "Unknown error"
25396
25639
  }
25397
25640
  }
25398
25641
  };
@@ -25409,7 +25652,7 @@ var SolverApiService = class {
25409
25652
  * @param config - Solver endpoint configuration.
25410
25653
  * @returns A `Result` containing `{ answer: 'OK', intent_hash: Hex }` on success.
25411
25654
  */
25412
- static async postExecution(request, config) {
25655
+ static async postExecution(request, config, logger = silentLogger) {
25413
25656
  try {
25414
25657
  const response = await retry(
25415
25658
  () => fetch(`${config.solverApiEndpoint}/execute`, {
@@ -25431,13 +25674,16 @@ var SolverApiService = class {
25431
25674
  value: await response.json()
25432
25675
  };
25433
25676
  } catch (e) {
25434
- console.error(`[SolverApiService.postExecution] failed. Details: ${JSON.stringify(e)}`);
25677
+ logger.error(
25678
+ "[SolverApiService.postExecution] failed",
25679
+ e instanceof Error ? e : new Error(JSON.stringify(e, bigintReplacer))
25680
+ );
25435
25681
  return {
25436
25682
  ok: false,
25437
25683
  error: {
25438
25684
  detail: {
25439
25685
  code: SolverIntentErrorCode.UNKNOWN,
25440
- message: e ? JSON.stringify(e) : "Unknown error"
25686
+ message: e ? JSON.stringify(e, bigintReplacer) : "Unknown error"
25441
25687
  }
25442
25688
  }
25443
25689
  };
@@ -25452,7 +25698,7 @@ var SolverApiService = class {
25452
25698
  * `fill_tx_hash` is set only when `status === SolverIntentStatusCode.SOLVED (3)`.
25453
25699
  * @throws Invariant error if `intent_tx_hash` is empty (thrown before the async request).
25454
25700
  */
25455
- static async getStatus(request, config) {
25701
+ static async getStatus(request, config, logger = silentLogger) {
25456
25702
  invariant(request.intent_tx_hash.length > 0, "Empty intent_tx_hash");
25457
25703
  try {
25458
25704
  const response = await fetch(`${config.solverApiEndpoint}/status`, {
@@ -25473,13 +25719,16 @@ var SolverApiService = class {
25473
25719
  value: await response.json()
25474
25720
  };
25475
25721
  } catch (e) {
25476
- console.error(`[SolverApiService.getStatus] failed. Details: ${JSON.stringify(e)}`);
25722
+ logger.error(
25723
+ "[SolverApiService.getStatus] failed",
25724
+ e instanceof Error ? e : new Error(JSON.stringify(e, bigintReplacer))
25725
+ );
25477
25726
  return {
25478
25727
  ok: false,
25479
25728
  error: {
25480
25729
  detail: {
25481
25730
  code: SolverIntentErrorCode.UNKNOWN,
25482
- message: e ? JSON.stringify(e) : "Unknown error"
25731
+ message: e ? JSON.stringify(e, bigintReplacer) : "Unknown error"
25483
25732
  }
25484
25733
  }
25485
25734
  };
@@ -25599,7 +25848,7 @@ var SwapService = class {
25599
25848
  * `fill_tx_hash` is populated only when `status === SolverIntentStatusCode.SOLVED (3)`.
25600
25849
  */
25601
25850
  async getStatus(request) {
25602
- return SolverApiService.getStatus(request, this.solver);
25851
+ return SolverApiService.getStatus(request, this.solver, this.config.logger);
25603
25852
  }
25604
25853
  /**
25605
25854
  * Notifies the solver API that an intent has been registered on the hub chain, triggering
@@ -25622,7 +25871,7 @@ var SwapService = class {
25622
25871
  */
25623
25872
  async postExecution(request) {
25624
25873
  try {
25625
- const result = await SolverApiService.postExecution(request, this.solver);
25874
+ const result = await SolverApiService.postExecution(request, this.solver, this.config.logger);
25626
25875
  if (result.ok) return result;
25627
25876
  const detail = result.error?.detail ?? {
25628
25877
  code: -999,
@@ -25895,7 +26144,7 @@ var SwapService = class {
25895
26144
  * - `raw: false` — broadcasts the transaction; `walletProvider` is required and must match `K`.
25896
26145
  *
25897
26146
  * Validates tokens and chain keys against the active `ConfigService` before constructing the
25898
- * intent. Bitcoin source chains require an additional RadFi access token step.
26147
+ * intent. Bitcoin source chains require an additional Bound Exchange access token step.
25899
26148
  *
25900
26149
  * @param _params - Intent parameters, source chain key, wallet provider (when `raw: false`),
25901
26150
  * and optional `skipSimulation` flag.
@@ -28376,11 +28625,13 @@ var MigrationService = class {
28376
28625
 
28377
28626
  // src/backendApi/BackendApiService.ts
28378
28627
  var BackendApiService = class {
28379
- constructor(config) {
28628
+ constructor(config, logger = consoleLogger) {
28380
28629
  this.config = config;
28381
28630
  this.headers = { ...config.headers };
28631
+ this.logger = logger;
28382
28632
  }
28383
28633
  headers;
28634
+ logger;
28384
28635
  /**
28385
28636
  * Execute a single HTTP request and return the parsed JSON body.
28386
28637
  *
@@ -28419,10 +28670,10 @@ var BackendApiService = class {
28419
28670
  if (error.name === "AbortError") {
28420
28671
  throw new Error("REQUEST_TIMEOUT", { cause: new Error(`Request timeout after ${timeout}ms`) });
28421
28672
  }
28422
- console.error("[BackendApiService] Request error:", error.message);
28673
+ this.logger.error("[BackendApiService] Request error", error);
28423
28674
  throw error;
28424
28675
  }
28425
- console.error("[BackendApiService] Unknown error:", error);
28676
+ this.logger.error("[BackendApiService] Unknown error", error);
28426
28677
  throw new Error("UNKNOWN_REQUEST_ERROR", { cause: error });
28427
28678
  }
28428
28679
  }
@@ -29092,7 +29343,7 @@ var BridgeService = class {
29092
29343
  * transaction simulation or batching). When `raw` is `false`, signs and submits the deposit
29093
29344
  * transaction via the provided wallet provider.
29094
29345
  *
29095
- * Bitcoin is only supported with `raw: false` because it requires the RadFi trading wallet
29346
+ * Bitcoin is only supported with `raw: false` because it requires the Bound Exchange trading wallet
29096
29347
  * derivation flow.
29097
29348
  *
29098
29349
  * @param _params - Bridge parameters including source/destination chain keys, token addresses,
@@ -29125,9 +29376,12 @@ var BridgeService = class {
29125
29376
  { ...baseCtx, field: "walletProvider" }
29126
29377
  );
29127
29378
  walletAddress = await this.spoke.bitcoin.getEffectiveWalletAddress(personalAddress);
29128
- await this.spoke.bitcoin.radfi.ensureRadfiAccessToken(_params.walletProvider);
29379
+ if (this.spoke.bitcoin.walletMode === "TRADING") {
29380
+ await this.spoke.bitcoin.radfi.ensureRadfiAccessToken(_params.walletProvider);
29381
+ }
29129
29382
  }
29130
- const hubWallet = await this.hubProvider.getUserHubWalletAddress(params.srcAddress, params.srcChainKey);
29383
+ const hubWallet = await this.hubProvider.getUserHubWalletAddress(walletAddress, params.srcChainKey);
29384
+ const effectiveSkipSimulation = skipSimulation || isBitcoinChainKeyType(params.srcChainKey) && this.spoke.bitcoin.walletMode === "USER";
29131
29385
  const data = this.buildBridgeData(params, srcToken, dstToken, this.config.bridge.partnerFee);
29132
29386
  const coreParams = {
29133
29387
  srcChainKey: params.srcChainKey,
@@ -29136,7 +29390,7 @@ var BridgeService = class {
29136
29390
  token: params.srcToken,
29137
29391
  amount: params.amount,
29138
29392
  data,
29139
- skipSimulation
29393
+ skipSimulation: effectiveSkipSimulation
29140
29394
  };
29141
29395
  const txResult = await this.spoke.deposit(
29142
29396
  _params.raw ? {
@@ -29149,7 +29403,7 @@ var BridgeService = class {
29149
29403
  }
29150
29404
  );
29151
29405
  if (!txResult.ok) {
29152
- console.error(txResult.error);
29406
+ this.config.logger.error("createBridgeIntent failed", txResult.error);
29153
29407
  if (isBridgeCreateIntentError(txResult.error)) return { ok: false, error: txResult.error };
29154
29408
  return {
29155
29409
  ok: false,
@@ -29164,7 +29418,7 @@ var BridgeService = class {
29164
29418
  }
29165
29419
  };
29166
29420
  } catch (error) {
29167
- console.error(error);
29421
+ this.config.logger.error("createBridgeIntent failed", error);
29168
29422
  if (isBridgeCreateIntentError(error)) return { ok: false, error };
29169
29423
  return {
29170
29424
  ok: false,
@@ -29334,7 +29588,7 @@ var BridgeService = class {
29334
29588
  value: availableDepositNormalised.isLessThan(assetManagerBalanceNormalised) ? { amount: availableDeposit, decimals: fromTokenInfo.decimals, type: "DEPOSIT_LIMIT" } : { amount: assetManagerBalance, decimals: toTokenInfo.decimals, type: "WITHDRAWAL_LIMIT" }
29335
29589
  };
29336
29590
  } catch (error) {
29337
- console.error(error);
29591
+ this.config.logger.error("getBridgeableAmount failed", error);
29338
29592
  if (isBridgeLookupError(error)) return { ok: false, error };
29339
29593
  return {
29340
29594
  ok: false,
@@ -29381,7 +29635,7 @@ var BridgeService = class {
29381
29635
  });
29382
29636
  return srcToken.vault.toLowerCase() === dstToken.vault.toLowerCase();
29383
29637
  } catch (error) {
29384
- console.error(error);
29638
+ this.config.logger.error("isBridgeable check failed", error);
29385
29639
  return false;
29386
29640
  }
29387
29641
  }
@@ -31771,7 +32025,7 @@ var ClService = class _ClService {
31771
32025
  };
31772
32026
  const txResult = await this.spoke.sendMessage(sendMessageParams);
31773
32027
  if (!txResult.ok) {
31774
- console.error("executeSupplyLiquidity error:", txResult.error);
32028
+ this.config.logger.error("executeSupplyLiquidity error", txResult.error);
31775
32029
  return {
31776
32030
  ok: false,
31777
32031
  error: txResult.error
@@ -31785,7 +32039,7 @@ var ClService = class _ClService {
31785
32039
  }
31786
32040
  };
31787
32041
  } catch (error) {
31788
- console.error("executeSupplyLiquidity error:", error);
32042
+ this.config.logger.error("executeSupplyLiquidity error", error);
31789
32043
  return {
31790
32044
  ok: false,
31791
32045
  error
@@ -32058,7 +32312,7 @@ var ClService = class _ClService {
32058
32312
  }
32059
32313
  return { ok: true, value: { srcChainTxHash: txResult.value.tx, dstChainTxHash: hubTxHash } };
32060
32314
  } catch (error) {
32061
- console.error("supplyLiquidity error:", error);
32315
+ this.config.logger.error("supplyLiquidity error", error);
32062
32316
  return {
32063
32317
  ok: false,
32064
32318
  error
@@ -32202,7 +32456,7 @@ var ClService = class _ClService {
32202
32456
  }
32203
32457
  };
32204
32458
  } catch (error) {
32205
- console.error("getPoolRewardConfig error:", error);
32459
+ this.config.logger.error("getPoolRewardConfig error", error);
32206
32460
  return {
32207
32461
  ok: false,
32208
32462
  error: lookupFailed("dex", "getPoolRewardConfig", error)
@@ -32263,7 +32517,7 @@ var ClService = class _ClService {
32263
32517
  };
32264
32518
  const txResult = await this.spoke.sendMessage(sendMessageParams);
32265
32519
  if (!txResult.ok) {
32266
- console.error("executeClaimRewards error:", txResult.error);
32520
+ this.config.logger.error("executeClaimRewards error", txResult.error);
32267
32521
  return {
32268
32522
  ok: false,
32269
32523
  error: txResult.error
@@ -32277,7 +32531,7 @@ var ClService = class _ClService {
32277
32531
  }
32278
32532
  };
32279
32533
  } catch (error) {
32280
- console.error("executeClaimRewards error:", error);
32534
+ this.config.logger.error("executeClaimRewards error", error);
32281
32535
  return {
32282
32536
  ok: false,
32283
32537
  error
@@ -32319,7 +32573,7 @@ var ClService = class _ClService {
32319
32573
  }
32320
32574
  return { ok: true, value: { srcChainTxHash: txResult.value.tx, dstChainTxHash: hubTxHash } };
32321
32575
  } catch (error) {
32322
- console.error("claimRewards error:", error);
32576
+ this.config.logger.error("claimRewards error", error);
32323
32577
  return {
32324
32578
  ok: false,
32325
32579
  error
@@ -32367,7 +32621,7 @@ var ClService = class _ClService {
32367
32621
  address: tokenAddress
32368
32622
  };
32369
32623
  } catch (error) {
32370
- console.error(`Failed to fetch token info for ${tokenAddress}:`, error);
32624
+ this.config.logger.error(`Failed to fetch token info for ${tokenAddress}`, error);
32371
32625
  return {
32372
32626
  symbol: "UNKNOWN",
32373
32627
  name: "Unknown Token",
@@ -32392,12 +32646,12 @@ var ClService = class _ClService {
32392
32646
  const oneShare = BigInt(10 ** 18);
32393
32647
  const result = await Erc4626Service.convertToAssets(statATokenAddress, oneShare, this.hubProvider.publicClient);
32394
32648
  if (!result.ok) {
32395
- console.error("[getStatATokenConversionRate] Failed to get conversion rate:", result.error);
32649
+ this.config.logger.error("[getStatATokenConversionRate] Failed to get conversion rate", result.error);
32396
32650
  return oneShare;
32397
32651
  }
32398
32652
  return result.value;
32399
32653
  } catch (error) {
32400
- console.error("[getStatATokenConversionRate] Error:", error);
32654
+ this.config.logger.error("[getStatATokenConversionRate] Error", error);
32401
32655
  return BigInt(10 ** 18);
32402
32656
  }
32403
32657
  }
@@ -32434,7 +32688,7 @@ var ClService = class _ClService {
32434
32688
  underlyingToken
32435
32689
  };
32436
32690
  } catch (error) {
32437
- console.error(`[getTokenEnrichmentData] Failed to enrich token ${token.address}:`, error);
32691
+ this.config.logger.error(`[getTokenEnrichmentData] Failed to enrich token ${token.address}`, error);
32438
32692
  return {
32439
32693
  token,
32440
32694
  isStatAToken: true
@@ -32504,7 +32758,7 @@ var ClService = class _ClService {
32504
32758
  rewardConfig = rewardConfigResult.value;
32505
32759
  }
32506
32760
  } catch (error) {
32507
- console.warn("Failed to fetch reward config for pool:", error);
32761
+ this.config.logger.warn("Failed to fetch reward config for pool", { error });
32508
32762
  }
32509
32763
  }
32510
32764
  return {
@@ -32540,7 +32794,7 @@ var ClService = class _ClService {
32540
32794
  }
32541
32795
  };
32542
32796
  } catch (error) {
32543
- console.error("Failed to fetch pool data:", error);
32797
+ this.config.logger.error("Failed to fetch pool data", error);
32544
32798
  return {
32545
32799
  ok: false,
32546
32800
  error: lookupFailed("dex", "getPoolData", error)
@@ -35324,14 +35578,25 @@ var MoneyMarketService = class _MoneyMarketService {
35324
35578
  );
35325
35579
  const dstChainKey = params.dstChainKey ?? srcChainKey;
35326
35580
  const dstAddress = params.dstAddress ?? params.srcAddress;
35327
- const [fromHubWallet, toHubWallet] = await Promise.all([
35328
- this.hubProvider.getUserHubWalletAddress(params.srcAddress, srcChainKey),
35329
- this.hubProvider.getUserHubWalletAddress(dstAddress, dstChainKey)
35330
- ]);
35581
+ const isBitcoinSrc = isBitcoinChainKeyType(srcChainKey);
35582
+ if (isBitcoinSrc && !_params.raw) {
35583
+ mmInvariant(
35584
+ walletProvider !== void 0 && isBitcoinWalletProviderType(walletProvider),
35585
+ `Invalid wallet provider for chain key: ${srcChainKey}. Expected bitcoin wallet provider.`,
35586
+ { ...baseCtx, field: "walletProvider" }
35587
+ );
35588
+ await this.spoke.bitcoin.radfi.ensureRadfiAccessToken(walletProvider);
35589
+ }
35590
+ const srcEffectiveAddress = isBitcoinSrc ? await this.spoke.bitcoin.getEffectiveWalletAddress(params.srcAddress) : params.srcAddress;
35591
+ const fromHubWallet = await this.hubProvider.getUserHubWalletAddress(srcEffectiveAddress, srcChainKey);
35592
+ const toHubWallet = dstChainKey === srcChainKey && dstAddress === params.srcAddress ? fromHubWallet : await this.hubProvider.getUserHubWalletAddress(
35593
+ isBitcoinChainKeyType(dstChainKey) ? await this.spoke.bitcoin.getEffectiveWalletAddress(dstAddress) : dstAddress,
35594
+ dstChainKey
35595
+ );
35331
35596
  const data = this.buildSupplyData(srcChainKey, params.token, params.amount, toHubWallet);
35332
35597
  const coreParams = {
35333
35598
  srcChainKey,
35334
- srcAddress: params.srcAddress,
35599
+ srcAddress: srcEffectiveAddress,
35335
35600
  to: fromHubWallet,
35336
35601
  token: params.token,
35337
35602
  amount: params.amount,
@@ -35371,6 +35636,17 @@ var MoneyMarketService = class _MoneyMarketService {
35371
35636
  }
35372
35637
  }
35373
35638
  // ==== borrow ==========================================================================
35639
+ /**
35640
+ * Build the relay submit/poll identity for a money-market borrow/withdraw.
35641
+ *
35642
+ * Bitcoin borrow/withdraw are on-demand: the spoke result is a signed payload JSON that the relay
35643
+ * submits under the literal "withdraw" tx_hash and tracks under a derived `od:<hash>` poll id
35644
+ * (see {@link BitcoinSpokeService.getOnDemandRelayIdentity}). Every other chain relays and polls by
35645
+ * its real spoke tx hash, so `pollTxHash` is undefined and `srcChainTxHash` stays the spoke tx.
35646
+ */
35647
+ buildRelayIdentity(srcChainKey, tx, relayData) {
35648
+ return isBitcoinChainKeyType(srcChainKey) ? this.spoke.bitcoin.getOnDemandRelayIdentity(tx) : { srcTxHash: tx, data: relayData, pollTxHash: void 0 };
35649
+ }
35374
35650
  /**
35375
35651
  * Borrow tokens from the money market lending pool and wait for the cross-chain relay to
35376
35652
  * deliver the funds to the destination address.
@@ -35406,9 +35682,9 @@ var MoneyMarketService = class _MoneyMarketService {
35406
35682
  value: { srcChainTxHash: txResult.value.tx, dstChainTxHash: txResult.value.tx }
35407
35683
  };
35408
35684
  }
35685
+ const relayIdentity = this.buildRelayIdentity(srcChainKey, txResult.value.tx, txResult.value.relayData);
35409
35686
  const packet = await relayTxAndWaitPacket({
35410
- srcTxHash: txResult.value.tx,
35411
- data: txResult.value.relayData,
35687
+ ...relayIdentity,
35412
35688
  chainKey: srcChainKey,
35413
35689
  relayerApiEndpoint: this.relayerApiEndpoint,
35414
35690
  timeout
@@ -35423,7 +35699,13 @@ var MoneyMarketService = class _MoneyMarketService {
35423
35699
  dstChainKey: baseCtx.dstChainKey
35424
35700
  })
35425
35701
  };
35426
- return { ok: true, value: { srcChainTxHash: txResult.value.tx, dstChainTxHash: packet.value.dst_tx_hash } };
35702
+ return {
35703
+ ok: true,
35704
+ value: {
35705
+ srcChainTxHash: relayIdentity.pollTxHash ?? txResult.value.tx,
35706
+ dstChainTxHash: packet.value.dst_tx_hash
35707
+ }
35708
+ };
35427
35709
  } catch (error) {
35428
35710
  if (isMoneyMarketOrchestrationError(error)) return { ok: false, error };
35429
35711
  return {
@@ -35465,7 +35747,8 @@ var MoneyMarketService = class _MoneyMarketService {
35465
35747
  field: "token"
35466
35748
  });
35467
35749
  const encodedDstAddress = encodeAddress(dstChainKey, dstAddress);
35468
- const fromHubWallet = await this.hubProvider.getUserHubWalletAddress(params.srcAddress, srcChainKey);
35750
+ const srcEffectiveAddress = isBitcoinChainKeyType(srcChainKey) ? await this.spoke.bitcoin.getEffectiveWalletAddress(params.srcAddress) : params.srcAddress;
35751
+ const fromHubWallet = await this.hubProvider.getUserHubWalletAddress(srcEffectiveAddress, srcChainKey);
35469
35752
  const payload = this.buildBorrowData(
35470
35753
  fromHubWallet,
35471
35754
  encodedDstAddress,
@@ -35544,144 +35827,151 @@ var MoneyMarketService = class _MoneyMarketService {
35544
35827
  }
35545
35828
  const needsRelay = srcChainKey !== hubChainId || params.dstChainKey != null && params.dstAddress != null && params.dstChainKey !== hubChainId && params.dstAddress !== walletRouter;
35546
35829
  if (!needsRelay) {
35547
- return {
35548
- ok: true,
35549
- value: { srcChainTxHash: txResult.value.tx, dstChainTxHash: txResult.value.tx }
35550
- };
35551
- }
35552
- const packet = await relayTxAndWaitPacket({
35553
- srcTxHash: txResult.value.tx,
35554
- data: txResult.value.relayData,
35555
- chainKey: srcChainKey,
35556
- relayerApiEndpoint: this.relayerApiEndpoint,
35557
- timeout
35558
- });
35559
- if (!packet.ok)
35560
- return {
35561
- ok: false,
35562
- error: mapRelayFailure(packet.error, {
35563
- feature: "moneyMarket",
35564
- action: baseCtx.action,
35565
- srcChainKey: baseCtx.srcChainKey,
35566
- dstChainKey: baseCtx.dstChainKey
35567
- })
35568
- };
35569
- return { ok: true, value: { srcChainTxHash: txResult.value.tx, dstChainTxHash: packet.value.dst_tx_hash } };
35570
- } catch (error) {
35571
- if (isMoneyMarketOrchestrationError(error)) return { ok: false, error };
35572
- return {
35573
- ok: false,
35574
- error: executionFailed("moneyMarket", error, { ...baseCtx, phase: "intentCreation" })
35575
- };
35576
- }
35577
- }
35578
- /**
35579
- * Build and optionally broadcast the spoke-side withdraw message without waiting for the
35580
- * cross-chain relay to deliver funds.
35581
- *
35582
- * Use this when you need manual relay control or want to sign and broadcast the transaction
35583
- * yourself. Pass `{ raw: true }` to receive unsigned calldata instead of executing.
35584
- *
35585
- * @param _params - Withdraw action params plus `raw` flag and optional `skipSimulation`.
35586
- * @returns The spoke transaction result (hash or raw calldata) plus `relayData` required to
35587
- * trigger the hub-side withdrawal via {@link relayTxAndWaitPacket}.
35588
- */
35589
- async createWithdrawIntent(_params) {
35590
- const { params, walletProvider } = _params;
35591
- const srcChainKey = params.srcChainKey;
35592
- const skipSimulation = _params.skipSimulation ?? false;
35593
- const baseCtx = { srcChainKey, dstChainKey: params.dstChainKey, action: "withdraw" };
35594
- try {
35595
- mmInvariant(params.action === "withdraw", "Invalid action", { ...baseCtx, field: "action" });
35596
- mmInvariant(params.token.length > 0, "Token is required", { ...baseCtx, field: "token" });
35597
- mmInvariant(params.amount > 0n, "Amount must be greater than 0", { ...baseCtx, field: "amount" });
35598
- mmInvariant(
35599
- isUndefinedOrValidWalletProviderForChainKey(srcChainKey, walletProvider),
35600
- `Invalid wallet provider for chain key: ${srcChainKey}, walletProvider.chainType: ${walletProvider?.chainType}`,
35601
- { ...baseCtx, field: "walletProvider" }
35602
- );
35603
- const dstChainKey = params.dstChainKey ?? srcChainKey;
35604
- const dstAddress = params.dstAddress ?? params.srcAddress;
35605
- mmInvariant(
35606
- this.config.isMoneyMarketSupportedToken(dstChainKey, params.token),
35607
- `Unsupported spoke chain (${dstChainKey}) token: ${params.token}`,
35608
- { ...baseCtx, field: "token" }
35609
- );
35610
- const encodedDstAddress = encodeAddress(dstChainKey, dstAddress);
35611
- const fromHubWallet = await this.hubProvider.getUserHubWalletAddress(params.srcAddress, srcChainKey);
35612
- const payload = this.buildWithdrawData(
35613
- fromHubWallet,
35614
- encodedDstAddress,
35615
- params.token,
35616
- params.amount,
35617
- dstChainKey
35618
- );
35619
- const coreParams = {
35620
- srcChainKey,
35621
- srcAddress: params.srcAddress,
35622
- dstChainKey: HUB_CHAIN_KEY,
35623
- dstAddress: fromHubWallet,
35624
- payload,
35625
- skipSimulation
35626
- };
35627
- const sendMessageParams = _params.raw ? {
35628
- ...coreParams,
35629
- raw: true
35630
- } : {
35631
- ...coreParams,
35632
- raw: false,
35633
- walletProvider: _params.walletProvider
35634
- };
35635
- const txResult = await this.spoke.sendMessage(sendMessageParams);
35636
- if (!txResult.ok) {
35637
- if (isMoneyMarketCreateIntentError(txResult.error)) return { ok: false, error: txResult.error };
35638
- return {
35639
- ok: false,
35640
- error: intentCreationFailed("moneyMarket", txResult.error, baseCtx)
35641
- };
35642
- }
35643
- return {
35644
- ok: true,
35645
- value: {
35646
- tx: txResult.value,
35647
- relayData: { address: fromHubWallet, payload }
35648
- }
35649
- };
35650
- } catch (error) {
35651
- if (isMoneyMarketCreateIntentError(error)) return { ok: false, error };
35652
- return {
35653
- ok: false,
35654
- error: intentCreationFailed("moneyMarket", error, baseCtx)
35655
- };
35656
- }
35657
- }
35658
- // ==== repay ===========================================================================
35659
- /**
35660
- * Repay a borrowed position in the money market lending pool and wait for the cross-chain
35661
- * relay to settle on the hub.
35662
- *
35663
- * Hub-chain callers skip the relay step. The repayment is credited to `dstAddress` on
35664
- * `dstChainKey` (defaulting to the source address and chain when omitted).
35665
- *
35666
- * @param _params - Repay action params: `srcChainKey`, `srcAddress`, `token`, `amount`,
35667
- * `walletProvider`, and optional `dstChainKey`/`dstAddress`.
35668
- * @returns A pair of transaction hashes — `srcChainTxHash` (spoke) and `dstChainTxHash` (hub).
35669
- */
35670
- async repay(_params) {
35671
- const { params, timeout = DEFAULT_RELAY_TX_TIMEOUT } = _params;
35672
- const srcChainKey = params.srcChainKey;
35673
- const baseCtx = { srcChainKey, dstChainKey: params.dstChainKey, action: "repay" };
35674
- try {
35675
- const txResult = await this.createRepayIntent(_params);
35676
- if (!txResult.ok) return { ok: false, error: txResult.error };
35677
- const verify = await this.spoke.verifyTxHash({ txHash: txResult.value.tx, chainKey: srcChainKey });
35678
- if (!verify.ok) {
35679
- return {
35680
- ok: false,
35681
- error: verifyFailed("moneyMarket", verify.error, baseCtx)
35682
- };
35683
- }
35684
- if (isHubChainKeyType(srcChainKey)) {
35830
+ return {
35831
+ ok: true,
35832
+ value: { srcChainTxHash: txResult.value.tx, dstChainTxHash: txResult.value.tx }
35833
+ };
35834
+ }
35835
+ const relayIdentity = this.buildRelayIdentity(srcChainKey, txResult.value.tx, txResult.value.relayData);
35836
+ const packet = await relayTxAndWaitPacket({
35837
+ ...relayIdentity,
35838
+ chainKey: srcChainKey,
35839
+ relayerApiEndpoint: this.relayerApiEndpoint,
35840
+ timeout
35841
+ });
35842
+ if (!packet.ok)
35843
+ return {
35844
+ ok: false,
35845
+ error: mapRelayFailure(packet.error, {
35846
+ feature: "moneyMarket",
35847
+ action: baseCtx.action,
35848
+ srcChainKey: baseCtx.srcChainKey,
35849
+ dstChainKey: baseCtx.dstChainKey
35850
+ })
35851
+ };
35852
+ return {
35853
+ ok: true,
35854
+ value: {
35855
+ srcChainTxHash: relayIdentity.pollTxHash ?? txResult.value.tx,
35856
+ dstChainTxHash: packet.value.dst_tx_hash
35857
+ }
35858
+ };
35859
+ } catch (error) {
35860
+ if (isMoneyMarketOrchestrationError(error)) return { ok: false, error };
35861
+ return {
35862
+ ok: false,
35863
+ error: executionFailed("moneyMarket", error, { ...baseCtx, phase: "intentCreation" })
35864
+ };
35865
+ }
35866
+ }
35867
+ /**
35868
+ * Build and optionally broadcast the spoke-side withdraw message without waiting for the
35869
+ * cross-chain relay to deliver funds.
35870
+ *
35871
+ * Use this when you need manual relay control or want to sign and broadcast the transaction
35872
+ * yourself. Pass `{ raw: true }` to receive unsigned calldata instead of executing.
35873
+ *
35874
+ * @param _params - Withdraw action params plus `raw` flag and optional `skipSimulation`.
35875
+ * @returns The spoke transaction result (hash or raw calldata) plus `relayData` required to
35876
+ * trigger the hub-side withdrawal via {@link relayTxAndWaitPacket}.
35877
+ */
35878
+ async createWithdrawIntent(_params) {
35879
+ const { params, walletProvider } = _params;
35880
+ const srcChainKey = params.srcChainKey;
35881
+ const skipSimulation = _params.skipSimulation ?? false;
35882
+ const baseCtx = { srcChainKey, dstChainKey: params.dstChainKey, action: "withdraw" };
35883
+ try {
35884
+ mmInvariant(params.action === "withdraw", "Invalid action", { ...baseCtx, field: "action" });
35885
+ mmInvariant(params.token.length > 0, "Token is required", { ...baseCtx, field: "token" });
35886
+ mmInvariant(params.amount > 0n, "Amount must be greater than 0", { ...baseCtx, field: "amount" });
35887
+ mmInvariant(
35888
+ isUndefinedOrValidWalletProviderForChainKey(srcChainKey, walletProvider),
35889
+ `Invalid wallet provider for chain key: ${srcChainKey}, walletProvider.chainType: ${walletProvider?.chainType}`,
35890
+ { ...baseCtx, field: "walletProvider" }
35891
+ );
35892
+ const dstChainKey = params.dstChainKey ?? srcChainKey;
35893
+ const dstAddress = params.dstAddress ?? params.srcAddress;
35894
+ mmInvariant(
35895
+ this.config.isMoneyMarketSupportedToken(dstChainKey, params.token),
35896
+ `Unsupported spoke chain (${dstChainKey}) token: ${params.token}`,
35897
+ { ...baseCtx, field: "token" }
35898
+ );
35899
+ const encodedDstAddress = encodeAddress(dstChainKey, dstAddress);
35900
+ const srcEffectiveAddress = isBitcoinChainKeyType(srcChainKey) ? await this.spoke.bitcoin.getEffectiveWalletAddress(params.srcAddress) : params.srcAddress;
35901
+ const fromHubWallet = await this.hubProvider.getUserHubWalletAddress(srcEffectiveAddress, srcChainKey);
35902
+ const payload = this.buildWithdrawData(
35903
+ fromHubWallet,
35904
+ encodedDstAddress,
35905
+ params.token,
35906
+ params.amount,
35907
+ dstChainKey
35908
+ );
35909
+ const coreParams = {
35910
+ srcChainKey,
35911
+ srcAddress: params.srcAddress,
35912
+ dstChainKey: HUB_CHAIN_KEY,
35913
+ dstAddress: fromHubWallet,
35914
+ payload,
35915
+ skipSimulation
35916
+ };
35917
+ const sendMessageParams = _params.raw ? {
35918
+ ...coreParams,
35919
+ raw: true
35920
+ } : {
35921
+ ...coreParams,
35922
+ raw: false,
35923
+ walletProvider: _params.walletProvider
35924
+ };
35925
+ const txResult = await this.spoke.sendMessage(sendMessageParams);
35926
+ if (!txResult.ok) {
35927
+ if (isMoneyMarketCreateIntentError(txResult.error)) return { ok: false, error: txResult.error };
35928
+ return {
35929
+ ok: false,
35930
+ error: intentCreationFailed("moneyMarket", txResult.error, baseCtx)
35931
+ };
35932
+ }
35933
+ return {
35934
+ ok: true,
35935
+ value: {
35936
+ tx: txResult.value,
35937
+ relayData: { address: fromHubWallet, payload }
35938
+ }
35939
+ };
35940
+ } catch (error) {
35941
+ if (isMoneyMarketCreateIntentError(error)) return { ok: false, error };
35942
+ return {
35943
+ ok: false,
35944
+ error: intentCreationFailed("moneyMarket", error, baseCtx)
35945
+ };
35946
+ }
35947
+ }
35948
+ // ==== repay ===========================================================================
35949
+ /**
35950
+ * Repay a borrowed position in the money market lending pool and wait for the cross-chain
35951
+ * relay to settle on the hub.
35952
+ *
35953
+ * Hub-chain callers skip the relay step. The repayment is credited to `dstAddress` on
35954
+ * `dstChainKey` (defaulting to the source address and chain when omitted).
35955
+ *
35956
+ * @param _params - Repay action params: `srcChainKey`, `srcAddress`, `token`, `amount`,
35957
+ * `walletProvider`, and optional `dstChainKey`/`dstAddress`.
35958
+ * @returns A pair of transaction hashes — `srcChainTxHash` (spoke) and `dstChainTxHash` (hub).
35959
+ */
35960
+ async repay(_params) {
35961
+ const { params, timeout = DEFAULT_RELAY_TX_TIMEOUT } = _params;
35962
+ const srcChainKey = params.srcChainKey;
35963
+ const baseCtx = { srcChainKey, dstChainKey: params.dstChainKey, action: "repay" };
35964
+ try {
35965
+ const txResult = await this.createRepayIntent(_params);
35966
+ if (!txResult.ok) return { ok: false, error: txResult.error };
35967
+ const verify = await this.spoke.verifyTxHash({ txHash: txResult.value.tx, chainKey: srcChainKey });
35968
+ if (!verify.ok) {
35969
+ return {
35970
+ ok: false,
35971
+ error: verifyFailed("moneyMarket", verify.error, baseCtx)
35972
+ };
35973
+ }
35974
+ if (isHubChainKeyType(srcChainKey)) {
35685
35975
  return {
35686
35976
  ok: true,
35687
35977
  value: { srcChainTxHash: txResult.value.tx, dstChainTxHash: txResult.value.tx }
@@ -35745,14 +36035,25 @@ var MoneyMarketService = class _MoneyMarketService {
35745
36035
  );
35746
36036
  const dstChainKey = params.dstChainKey ?? srcChainKey;
35747
36037
  const dstAddress = params.dstAddress ?? params.srcAddress;
35748
- const [fromHubWallet, toHubWallet] = await Promise.all([
35749
- this.hubProvider.getUserHubWalletAddress(params.srcAddress, srcChainKey),
35750
- this.hubProvider.getUserHubWalletAddress(dstAddress, dstChainKey)
35751
- ]);
36038
+ const isBitcoinSrc = isBitcoinChainKeyType(srcChainKey);
36039
+ if (isBitcoinSrc && !_params.raw) {
36040
+ mmInvariant(
36041
+ walletProvider !== void 0 && isBitcoinWalletProviderType(walletProvider),
36042
+ `Invalid wallet provider for chain key: ${srcChainKey}. Expected bitcoin wallet provider.`,
36043
+ { ...baseCtx, field: "walletProvider" }
36044
+ );
36045
+ await this.spoke.bitcoin.radfi.ensureRadfiAccessToken(walletProvider);
36046
+ }
36047
+ const srcEffectiveAddress = isBitcoinSrc ? await this.spoke.bitcoin.getEffectiveWalletAddress(params.srcAddress) : params.srcAddress;
36048
+ const fromHubWallet = await this.hubProvider.getUserHubWalletAddress(srcEffectiveAddress, srcChainKey);
36049
+ const toHubWallet = dstChainKey === srcChainKey && dstAddress === params.srcAddress ? fromHubWallet : await this.hubProvider.getUserHubWalletAddress(
36050
+ isBitcoinChainKeyType(dstChainKey) ? await this.spoke.bitcoin.getEffectiveWalletAddress(dstAddress) : dstAddress,
36051
+ dstChainKey
36052
+ );
35752
36053
  const data = this.buildRepayData(srcChainKey, params.token, params.amount, toHubWallet);
35753
36054
  const coreParams = {
35754
36055
  srcChainKey,
35755
- srcAddress: params.srcAddress,
36056
+ srcAddress: srcEffectiveAddress,
35756
36057
  to: fromHubWallet,
35757
36058
  token: params.token,
35758
36059
  amount: params.amount,
@@ -36313,9 +36614,9 @@ var PartnerFeeClaimService = class {
36313
36614
  if (typeof balanceResult === "bigint") {
36314
36615
  balance = balanceResult;
36315
36616
  } else {
36316
- console.warn(
36317
- `[PartnerFeeClaimService] Unexpected balance result format for ${entry.hubAsset.symbol} (${entry.assetAddress}):`,
36318
- balanceResult
36617
+ this.config.logger.warn(
36618
+ `[PartnerFeeClaimService] Unexpected balance result format for ${entry.hubAsset.symbol} (${entry.assetAddress})`,
36619
+ { balanceResult }
36319
36620
  );
36320
36621
  balance = 0n;
36321
36622
  }
@@ -36651,7 +36952,8 @@ var PartnerFeeClaimService = class {
36651
36952
  }
36652
36953
  const solverExecutionResponse = await SolverApiService.postExecution(
36653
36954
  { intent_tx_hash: intentTxHash },
36654
- this.config.solver
36955
+ this.config.solver,
36956
+ this.config.logger
36655
36957
  );
36656
36958
  if (!solverExecutionResponse.ok) {
36657
36959
  return solverExecutionResponse;
@@ -36732,7 +37034,12 @@ var RecoveryService = class {
36732
37034
  srcChainKey: chainKey,
36733
37035
  field: "chainKey"
36734
37036
  });
36735
- const entries = Object.values(chainConfig.supportedTokens).filter((token) => isAddress(token.hubAsset));
37037
+ const leverageVaultAddresses = new Set(
37038
+ this.config.sodaxConfig.leverageYield.vaults.map((vault) => vault.vault.toLowerCase())
37039
+ );
37040
+ const entries = Object.values(chainConfig.supportedTokens).filter(
37041
+ (token) => isAddress(token.hubAsset) && !leverageVaultAddresses.has(token.hubAsset.toLowerCase())
37042
+ );
36736
37043
  if (entries.length === 0) {
36737
37044
  return { ok: true, value: [] };
36738
37045
  }
@@ -36834,6 +37141,871 @@ var RecoveryService = class {
36834
37141
  }
36835
37142
  };
36836
37143
 
37144
+ // src/leverageYield/errors.ts
37145
+ var leverageYieldInvariant = createInvariant("leverageYield");
37146
+ var LEVERAGE_YIELD_CODES = /* @__PURE__ */ new Set([
37147
+ "VALIDATION_FAILED",
37148
+ "INTENT_CREATION_FAILED",
37149
+ "APPROVE_FAILED",
37150
+ "ALLOWANCE_CHECK_FAILED",
37151
+ "LOOKUP_FAILED",
37152
+ "TX_VERIFICATION_FAILED",
37153
+ "TX_SUBMIT_FAILED",
37154
+ "RELAY_TIMEOUT",
37155
+ "RELAY_FAILED",
37156
+ "EXECUTION_FAILED",
37157
+ "EXTERNAL_API_ERROR",
37158
+ "UNKNOWN"
37159
+ ]);
37160
+ var LEVERAGE_YIELD_POST_EXECUTION_CODES = /* @__PURE__ */ new Set([
37161
+ "EXECUTION_FAILED",
37162
+ "EXTERNAL_API_ERROR",
37163
+ "UNKNOWN"
37164
+ ]);
37165
+ var LEVERAGE_YIELD_SWAP_CODES = /* @__PURE__ */ new Set([
37166
+ "VALIDATION_FAILED",
37167
+ "INTENT_CREATION_FAILED",
37168
+ "TX_VERIFICATION_FAILED",
37169
+ "TX_SUBMIT_FAILED",
37170
+ "RELAY_TIMEOUT",
37171
+ "RELAY_FAILED",
37172
+ "EXECUTION_FAILED",
37173
+ "EXTERNAL_API_ERROR",
37174
+ "UNKNOWN"
37175
+ ]);
37176
+ var isLeverageYieldCreateIntentError = isCodeMember(CREATE_INTENT_CODES);
37177
+ var isLeverageYieldApproveError = isCodeMember(APPROVE_CODES);
37178
+ var isLeverageYieldAllowanceCheckError = isCodeMember(ALLOWANCE_CHECK_CODES);
37179
+ var isLeverageYieldLookupError = isCodeMember(LOOKUP_CODES);
37180
+ var isLeverageYieldPostExecutionError = isCodeMember(
37181
+ LEVERAGE_YIELD_POST_EXECUTION_CODES
37182
+ );
37183
+ var isLeverageYieldSwapError = isCodeMember(LEVERAGE_YIELD_SWAP_CODES);
37184
+ var isLeverageYieldError = isCodeMember(LEVERAGE_YIELD_CODES);
37185
+
37186
+ // src/leverageYield/LeverageYieldService.ts
37187
+ var leverageYieldVaultAbi = parseAbi([
37188
+ "function asset() view returns (address)",
37189
+ "function getPositionDetails() view returns (uint256 collateral, uint256 debt, uint256 ltv, uint256 healthFactor, uint256 idleAsset)",
37190
+ // Used by getApr() — read-only metadata that doesn't change per pass.
37191
+ "function pool() view returns (address)",
37192
+ "function borrowToken() view returns (address)",
37193
+ "function targetLTV() view returns (uint256)"
37194
+ ]);
37195
+ var INTENT_DEADLINE_BUFFER_SECONDS = 5 * 60;
37196
+ var ANY_SOLVER_ADDRESS = "0x0000000000000000000000000000000000000000";
37197
+ var MAX_WITHDRAW_DUST_BUFFER = 1000n;
37198
+ function pctToRay(aprPct) {
37199
+ return BigInt(Math.round(aprPct * 1e9)) * 10n ** 16n;
37200
+ }
37201
+ var DEFILLAMA_FETCH_TIMEOUT_MS = 1e4;
37202
+ var DEFILLAMA_FETCH_MAX_ATTEMPTS = 2;
37203
+ async function fetchWithTimeout(url, timeoutMs) {
37204
+ const controller = new AbortController();
37205
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
37206
+ try {
37207
+ return await fetch(url, { signal: controller.signal });
37208
+ } finally {
37209
+ clearTimeout(timeoutId);
37210
+ }
37211
+ }
37212
+ async function fetchDefillamaApr(poolId) {
37213
+ return retry(async () => {
37214
+ const response = await fetchWithTimeout(`https://yields.llama.fi/chart/${poolId}`, DEFILLAMA_FETCH_TIMEOUT_MS);
37215
+ if (!response.ok) throw new Error(`DefiLlama APR fetch failed: HTTP ${response.status}`);
37216
+ const json = await response.json();
37217
+ const series = json?.data;
37218
+ if (!Array.isArray(series) || series.length === 0) {
37219
+ throw new Error("DefiLlama APR response: empty time series");
37220
+ }
37221
+ const latest = series[series.length - 1]?.apy;
37222
+ if (typeof latest !== "number") throw new Error("DefiLlama APR: latest entry missing numeric apy");
37223
+ return latest;
37224
+ }, DEFILLAMA_FETCH_MAX_ATTEMPTS);
37225
+ }
37226
+ var LeverageYieldService = class {
37227
+ hubProvider;
37228
+ config;
37229
+ spoke;
37230
+ constructor({ hubProvider, config, spoke }) {
37231
+ this.hubProvider = hubProvider;
37232
+ this.config = config;
37233
+ this.spoke = spoke;
37234
+ }
37235
+ // ─── Registry ──────────────────────────────────────────────────────────
37236
+ /** Returns the static registry of known leverage-yield vaults. */
37237
+ listVaults() {
37238
+ return this.config.sodaxConfig.leverageYield.vaults;
37239
+ }
37240
+ /** Looks up a vault by its `name` field. Returns `undefined` when not registered. */
37241
+ getVault(name) {
37242
+ return this.listVaults().find((v) => v.name === name);
37243
+ }
37244
+ /**
37245
+ * Looks up a registered vault by its on-chain proxy address (case-insensitive).
37246
+ * Returns `undefined` when the address isn't in the registry.
37247
+ */
37248
+ getVaultByAddress(address) {
37249
+ const normalized = address.toLowerCase();
37250
+ return this.listVaults().find((v) => v.vault.toLowerCase() === normalized);
37251
+ }
37252
+ /**
37253
+ * Resolves the intent `deadline`: returns the caller-supplied value verbatim, otherwise
37254
+ * derives a default from the hub-chain (Sonic) block timestamp plus
37255
+ * {@link INTENT_DEADLINE_BUFFER_SECONDS}. The deadline is enforced on-chain against the hub
37256
+ * block time, so the default is anchored to that block — never the client clock, which can
37257
+ * drift and produce an already-expired or over-extended deadline.
37258
+ *
37259
+ * Returns a {@link Result}: a `getBlock` RPC outage is a read failure, surfaced as
37260
+ * `LOOKUP_FAILED` (`method: 'resolveDeadline'`) rather than masquerading as an intent-build
37261
+ * failure. The `deposit` / `withdraw` callers forward it verbatim.
37262
+ */
37263
+ async resolveDeadline(deadline, srcChainKey) {
37264
+ if (deadline !== void 0) return { ok: true, value: deadline };
37265
+ try {
37266
+ const block = await this.hubProvider.publicClient.getBlock({
37267
+ includeTransactions: false,
37268
+ blockTag: "latest"
37269
+ });
37270
+ return { ok: true, value: block.timestamp + BigInt(INTENT_DEADLINE_BUFFER_SECONDS) };
37271
+ } catch (error) {
37272
+ return { ok: false, error: lookupFailed("leverageYield", "resolveDeadline", error, { srcChainKey }) };
37273
+ }
37274
+ }
37275
+ /**
37276
+ * Builds the {@link LeverageYieldSwapPayload} for a leverage-yield deposit (any token → lsoda*).
37277
+ * The lsoda* output is delivered to the user's hub wallet on Sonic so a later
37278
+ * {@link LeverageYieldService.withdraw} can swap it back. Spread the result into
37279
+ * {@link LeverageYieldService.vaultSwap}: `vaultSwap({ ...payload, walletProvider })`.
37280
+ * An optional `partnerFee` is forwarded on the payload as the per-intent fee override.
37281
+ *
37282
+ * For `minOutputAmount`, quote via `sodax.swaps.getQuote` with the vault address as the
37283
+ * destination token (`token_dst`) — vault shares are solver-tradeable, so the generic swap
37284
+ * quote applies; then subtract your slippage tolerance.
37285
+ */
37286
+ async deposit(params) {
37287
+ const baseCtx = { srcChainKey: params.srcChainKey, action: "deposit" };
37288
+ try {
37289
+ leverageYieldInvariant(params.inputAmount > 0n, "inputAmount must be greater than 0", {
37290
+ ...baseCtx,
37291
+ field: "inputAmount"
37292
+ });
37293
+ leverageYieldInvariant(params.vault.length > 0, "Vault address is required", { ...baseCtx, field: "vault" });
37294
+ leverageYieldInvariant(params.inputToken.length > 0, "inputToken is required", {
37295
+ ...baseCtx,
37296
+ field: "inputToken"
37297
+ });
37298
+ const hubWallet = await this.hubProvider.getUserHubWalletAddress(params.srcAddress, params.srcChainKey);
37299
+ const deadlineResult = await this.resolveDeadline(params.deadline, params.srcChainKey);
37300
+ if (!deadlineResult.ok) return deadlineResult;
37301
+ const deadline = deadlineResult.value;
37302
+ return {
37303
+ ok: true,
37304
+ value: {
37305
+ params: {
37306
+ inputToken: params.inputToken,
37307
+ outputToken: params.vault,
37308
+ inputAmount: params.inputAmount,
37309
+ minOutputAmount: params.minOutputAmount,
37310
+ deadline,
37311
+ allowPartialFill: false,
37312
+ srcChainKey: params.srcChainKey,
37313
+ dstChainKey: this.hubProvider.chainConfig.chain.key,
37314
+ srcAddress: params.srcAddress,
37315
+ dstAddress: hubWallet,
37316
+ solver: params.solver ?? ANY_SOLVER_ADDRESS,
37317
+ data: "0x"
37318
+ },
37319
+ // Per-intent fee override — only included when the caller supplies one, so the
37320
+ // payload stays free of undefined-valued keys.
37321
+ ...params.partnerFee !== void 0 && { partnerFee: params.partnerFee }
37322
+ }
37323
+ };
37324
+ } catch (error) {
37325
+ if (isLeverageYieldCreateIntentError(error)) return { ok: false, error };
37326
+ return { ok: false, error: intentCreationFailed("leverageYield", error, baseCtx) };
37327
+ }
37328
+ }
37329
+ /**
37330
+ * Builds the {@link LeverageYieldSwapPayload} for a leverage-yield withdraw (lsoda* → any
37331
+ * token). The payload carries `hubWalletSwap: true` — {@link LeverageYieldService.vaultSwap}
37332
+ * then spends the lsoda* held in the user's hub wallet by authorising it via a
37333
+ * `Connection.sendMessage` the user signs on `srcChainKey`. Wrapped in a {@link Result} for
37334
+ * a call shape uniform with {@link LeverageYieldService.deposit}; async because the default
37335
+ * `deadline` is read from the hub block timestamp.
37336
+ *
37337
+ * For `minOutputAmount`, quote via `sodax.swaps.getQuote` with the vault address as the
37338
+ * source token (`token_src`) — vault shares are solver-tradeable, so the generic swap quote
37339
+ * applies; then subtract your slippage tolerance.
37340
+ */
37341
+ async withdraw(params) {
37342
+ const baseCtx = { srcChainKey: params.srcChainKey, action: "withdraw" };
37343
+ try {
37344
+ leverageYieldInvariant(params.inputAmount > 0n, "inputAmount must be greater than 0", {
37345
+ ...baseCtx,
37346
+ field: "inputAmount"
37347
+ });
37348
+ leverageYieldInvariant(params.vault.length > 0, "Vault address is required", { ...baseCtx, field: "vault" });
37349
+ leverageYieldInvariant(params.outputToken.length > 0, "outputToken is required", {
37350
+ ...baseCtx,
37351
+ field: "outputToken"
37352
+ });
37353
+ const deadlineResult = await this.resolveDeadline(params.deadline, params.srcChainKey);
37354
+ if (!deadlineResult.ok) return deadlineResult;
37355
+ const deadline = deadlineResult.value;
37356
+ return {
37357
+ ok: true,
37358
+ value: {
37359
+ params: {
37360
+ inputToken: params.vault,
37361
+ outputToken: params.outputToken,
37362
+ inputAmount: params.inputAmount,
37363
+ minOutputAmount: params.minOutputAmount,
37364
+ deadline,
37365
+ allowPartialFill: false,
37366
+ srcChainKey: params.srcChainKey,
37367
+ dstChainKey: params.dstChainKey,
37368
+ srcAddress: params.srcAddress,
37369
+ dstAddress: params.recipient ?? params.srcAddress,
37370
+ solver: params.solver ?? ANY_SOLVER_ADDRESS,
37371
+ data: "0x"
37372
+ },
37373
+ hubWalletSwap: true
37374
+ }
37375
+ };
37376
+ } catch (error) {
37377
+ if (isLeverageYieldCreateIntentError(error)) return { ok: false, error };
37378
+ return { ok: false, error: intentCreationFailed("leverageYield", error, baseCtx) };
37379
+ }
37380
+ }
37381
+ /**
37382
+ * Creates a vault swap intent on the user's source spoke chain without submitting it to
37383
+ * the solver. Leverage-yield copy of the swap domain's `createIntent`, specialised for
37384
+ * vault flows — duplicated deliberately so the vault-specific execution modifiers
37385
+ * (`hubWalletSwap`, per-intent `partnerFee`) stay off the generic swap surface.
37386
+ *
37387
+ * Use {@link LeverageYieldService.vaultSwap} for the full end-to-end flow
37388
+ * (create → relay → notify solver); use this directly when you need the raw transaction
37389
+ * or drive the relay yourself (e.g. the backend submit-tx path). To complete a manual flow,
37390
+ * relay the returned `relayData` (the shared `relayTxAndWaitPacket` helper) and then call
37391
+ * {@link LeverageYieldService.notifySolver} with the hub-side intent tx hash.
37392
+ *
37393
+ * @param _params - Intent parameters, source chain key, wallet provider (when `raw: false`),
37394
+ * and optional `skipSimulation` / `hubWalletSwap` / `partnerFee`.
37395
+ * @returns A `Result<CreateVaultIntentResult<K, Raw>, LeverageYieldCreateIntentError>`.
37396
+ * On success contains:
37397
+ * - `tx` — chain-specific tx hash (executed) or raw tx data (raw mode).
37398
+ * - `intent` — the fully constructed `Intent` object augmented with `feeAmount`.
37399
+ * - `relayData` — `{ address, payload }` needed to submit the intent to the relayer.
37400
+ *
37401
+ * On failure `result.error` is a SodaxError with `VALIDATION_FAILED` (invariant
37402
+ * precondition), `INTENT_CREATION_FAILED` (spoke-side creation/deposit failed) or
37403
+ * `UNKNOWN` (defensive fallback).
37404
+ */
37405
+ async createVaultIntent(_params) {
37406
+ const { params, skipSimulation, hubWalletSwap, partnerFee = this.config.swaps.partnerFee } = _params;
37407
+ const baseCtx = { srcChainKey: params.srcChainKey, dstChainKey: params.dstChainKey };
37408
+ try {
37409
+ leverageYieldInvariant(
37410
+ isUndefinedOrValidWalletProviderForChainKey(params.srcChainKey, _params.walletProvider),
37411
+ `Invalid wallet provider for chain key: ${params.srcChainKey}`,
37412
+ baseCtx
37413
+ );
37414
+ const hubChainKey = this.hubProvider.chainConfig.chain.key;
37415
+ const inputTokenChainKey = hubWalletSwap ? hubChainKey : params.srcChainKey;
37416
+ leverageYieldInvariant(
37417
+ this.config.isValidOriginalAssetAddress(inputTokenChainKey, params.inputToken),
37418
+ `Unsupported spoke chain token (srcChainKey: ${inputTokenChainKey}, inputToken: ${params.inputToken})`,
37419
+ { ...baseCtx, field: "inputToken" }
37420
+ );
37421
+ leverageYieldInvariant(
37422
+ this.config.isValidOriginalAssetAddress(params.dstChainKey, params.outputToken),
37423
+ `Unsupported spoke chain token (params.dstChain: ${params.dstChainKey}, params.outputToken: ${params.outputToken})`,
37424
+ { ...baseCtx, field: "outputToken" }
37425
+ );
37426
+ leverageYieldInvariant(
37427
+ this.config.isValidSpokeChainKey(params.srcChainKey),
37428
+ `Invalid spoke chain (srcChainKey): ${params.srcChainKey}`,
37429
+ { ...baseCtx, field: "srcChainKey" }
37430
+ );
37431
+ leverageYieldInvariant(
37432
+ this.config.isValidSpokeChainKey(params.dstChainKey),
37433
+ `Invalid spoke chain (params.dstChain): ${params.dstChainKey}`,
37434
+ { ...baseCtx, field: "dstChainKey" }
37435
+ );
37436
+ if (isBitcoinChainKey(params.dstChainKey) && params.outputToken === "BTC") {
37437
+ leverageYieldInvariant(
37438
+ params.minOutputAmount >= 546n,
37439
+ `Invalid minOutputAmount (params.minOutputAmount): ${params.minOutputAmount}`,
37440
+ { ...baseCtx, field: "minOutputAmount" }
37441
+ );
37442
+ }
37443
+ const personalAddress = params.srcAddress;
37444
+ let walletAddress = personalAddress;
37445
+ if (isBitcoinChainKeyType(params.srcChainKey) && _params.raw === false) {
37446
+ leverageYieldInvariant(
37447
+ isBitcoinWalletProviderType(_params.walletProvider),
37448
+ `Invalid wallet provider for chain key: ${params.srcChainKey}`,
37449
+ baseCtx
37450
+ );
37451
+ walletAddress = await this.spoke.bitcoin.getEffectiveWalletAddress(personalAddress);
37452
+ await this.spoke.bitcoin.radfi.ensureRadfiAccessToken(_params.walletProvider);
37453
+ }
37454
+ const creatorHubWalletAddress = await this.hubProvider.getUserHubWalletAddress(walletAddress, params.srcChainKey);
37455
+ if (hubWalletSwap) {
37456
+ const [data2, intent2, feeAmount2] = EvmSolverService.constructCreateIntentData(
37457
+ { ...params, srcChainKey: hubChainKey, srcAddress: creatorHubWalletAddress },
37458
+ creatorHubWalletAddress,
37459
+ this.config,
37460
+ partnerFee
37461
+ );
37462
+ const coreSendMessageParams = {
37463
+ srcChainKey: params.srcChainKey,
37464
+ // Personal address — NOT the resolved trading `walletAddress`. SpokeService.sendMessage
37465
+ // re-resolves the effective (Bitcoin trading) address itself; passing the already-resolved
37466
+ // trading address here would double-resolve it (getTradingWallet(tradingAddress) →
37467
+ // "Trading wallet not found"). The trading address is used only for the hub-wallet
37468
+ // derivation above (creatorHubWalletAddress). Mirrors MoneyMarketService borrow/withdraw.
37469
+ srcAddress: personalAddress,
37470
+ dstChainKey: hubChainKey,
37471
+ dstAddress: creatorHubWalletAddress,
37472
+ payload: data2,
37473
+ skipSimulation
37474
+ };
37475
+ const txResult2 = await this.spoke.sendMessage(
37476
+ _params.raw ? { ...coreSendMessageParams, raw: true } : {
37477
+ ...coreSendMessageParams,
37478
+ raw: false,
37479
+ walletProvider: _params.walletProvider
37480
+ }
37481
+ );
37482
+ if (!txResult2.ok) {
37483
+ if (isLeverageYieldCreateIntentError(txResult2.error)) {
37484
+ return { ok: false, error: txResult2.error };
37485
+ }
37486
+ return { ok: false, error: intentCreationFailed("leverageYield", txResult2.error, baseCtx) };
37487
+ }
37488
+ return {
37489
+ ok: true,
37490
+ value: {
37491
+ tx: txResult2.value,
37492
+ intent: { ...intent2, feeAmount: feeAmount2 },
37493
+ relayData: { address: creatorHubWalletAddress, payload: data2 }
37494
+ }
37495
+ };
37496
+ }
37497
+ if (isHubChainKeyType(params.srcChainKey) && isSonicChainKeyType(params.srcChainKey)) {
37498
+ const coreSonicParams = {
37499
+ createIntentParams: params,
37500
+ creatorHubWalletAddress,
37501
+ solverConfig: this.config.solver,
37502
+ fee: partnerFee,
37503
+ hubProvider: this.hubProvider
37504
+ };
37505
+ const [txResult2, intent2, feeAmount2, data2] = await SonicSpokeService.createSwapIntent(
37506
+ _params.raw ? { ...coreSonicParams, raw: true } : {
37507
+ ...coreSonicParams,
37508
+ raw: false,
37509
+ walletProvider: _params.walletProvider
37510
+ }
37511
+ );
37512
+ return {
37513
+ ok: true,
37514
+ value: {
37515
+ tx: txResult2,
37516
+ intent: { ...intent2, feeAmount: feeAmount2 },
37517
+ relayData: { address: intent2.creator, payload: data2 }
37518
+ }
37519
+ };
37520
+ }
37521
+ const [data, intent, feeAmount] = EvmSolverService.constructCreateIntentData(
37522
+ {
37523
+ ...params,
37524
+ srcAddress: walletAddress
37525
+ },
37526
+ creatorHubWalletAddress,
37527
+ this.config,
37528
+ partnerFee
37529
+ );
37530
+ const coreDepositParams = {
37531
+ srcChainKey: params.srcChainKey,
37532
+ srcAddress: walletAddress,
37533
+ to: creatorHubWalletAddress,
37534
+ token: params.inputToken,
37535
+ amount: params.inputAmount,
37536
+ data,
37537
+ skipSimulation
37538
+ };
37539
+ const txResult = await this.spoke.deposit(
37540
+ _params.raw ? {
37541
+ ...coreDepositParams,
37542
+ raw: true
37543
+ } : {
37544
+ ...coreDepositParams,
37545
+ raw: false,
37546
+ walletProvider: _params.walletProvider
37547
+ }
37548
+ );
37549
+ if (!txResult.ok) {
37550
+ if (isLeverageYieldCreateIntentError(txResult.error)) {
37551
+ return { ok: false, error: txResult.error };
37552
+ }
37553
+ return {
37554
+ ok: false,
37555
+ error: intentCreationFailed("leverageYield", txResult.error, baseCtx)
37556
+ };
37557
+ }
37558
+ return {
37559
+ ok: true,
37560
+ value: {
37561
+ tx: txResult.value,
37562
+ intent: { ...intent, feeAmount },
37563
+ relayData: { address: intent.creator, payload: data }
37564
+ }
37565
+ };
37566
+ } catch (error) {
37567
+ if (isLeverageYieldCreateIntentError(error)) return { ok: false, error };
37568
+ return {
37569
+ ok: false,
37570
+ error: intentCreationFailed("leverageYield", error, baseCtx)
37571
+ };
37572
+ }
37573
+ }
37574
+ /**
37575
+ * Executes a full end-to-end leverage-yield vault swap (deposit or withdraw).
37576
+ * Leverage-yield copy of the swap domain's `swap()` orchestrator:
37577
+ * 1. Calls {@link LeverageYieldService.createVaultIntent} to submit the intent
37578
+ * transaction on the source spoke chain.
37579
+ * 2. Verifies the spoke transaction landed on-chain.
37580
+ * 3. For non-hub source chains: submits the spoke tx to the relayer and waits for the
37581
+ * relay packet to land on the hub (Sonic). Skipped when `srcChainKey` is the hub.
37582
+ * 4. Notifies the solver, triggering it to fill the intent.
37583
+ *
37584
+ * Spread a {@link LeverageYieldSwapPayload} from `deposit` / `withdraw` into this method
37585
+ * alongside the wallet provider: `vaultSwap({ ...payload, walletProvider })`.
37586
+ *
37587
+ * @returns A `Result<VaultSwapResponse, LeverageYieldSwapError>`. On success:
37588
+ * - `solverExecutionResponse` — solver acknowledgement (`{ answer: 'OK', intent_hash }`).
37589
+ * - `intent` — the on-chain intent object that was created.
37590
+ * - `intentDeliveryInfo` — source/destination chain keys, tx hashes, and user addresses.
37591
+ *
37592
+ * On failure `result.error` carries one of the create-intent codes plus
37593
+ * `TX_VERIFICATION_FAILED`, `TX_SUBMIT_FAILED`, `RELAY_TIMEOUT`, `RELAY_FAILED`,
37594
+ * `EXECUTION_FAILED`, `EXTERNAL_API_ERROR` or `UNKNOWN`.
37595
+ */
37596
+ async vaultSwap(_params) {
37597
+ const { params } = _params;
37598
+ const srcChainKey = params.srcChainKey;
37599
+ const baseCtx = { srcChainKey, dstChainKey: params.dstChainKey, action: "vaultSwap" };
37600
+ try {
37601
+ const timeout = _params.timeout;
37602
+ const createIntentResult = await this.createVaultIntent(_params);
37603
+ if (!createIntentResult.ok) {
37604
+ return { ok: false, error: createIntentResult.error };
37605
+ }
37606
+ const { tx: spokeTxHash, intent, relayData } = createIntentResult.value;
37607
+ const verifyTxHashResult = await this.spoke.verifyTxHash({
37608
+ txHash: spokeTxHash,
37609
+ chainKey: srcChainKey
37610
+ });
37611
+ if (!verifyTxHashResult.ok) {
37612
+ return {
37613
+ ok: false,
37614
+ error: verifyFailed("leverageYield", verifyTxHashResult.error, baseCtx)
37615
+ };
37616
+ }
37617
+ let dstIntentTxHash;
37618
+ if (isHubChainKeyType(srcChainKey)) {
37619
+ dstIntentTxHash = spokeTxHash;
37620
+ } else {
37621
+ const packet = await relayTxAndWaitPacket({
37622
+ srcTxHash: spokeTxHash,
37623
+ data: relayData,
37624
+ chainKey: srcChainKey,
37625
+ relayerApiEndpoint: this.config.relay.relayerApiEndpoint,
37626
+ timeout
37627
+ });
37628
+ if (!packet.ok) {
37629
+ return {
37630
+ ok: false,
37631
+ error: mapRelayFailure(packet.error, { feature: "leverageYield", ...baseCtx })
37632
+ };
37633
+ }
37634
+ dstIntentTxHash = packet.value.dst_tx_hash;
37635
+ }
37636
+ const postExecResult = await this.notifySolver({
37637
+ intent_tx_hash: dstIntentTxHash
37638
+ });
37639
+ if (!postExecResult.ok) {
37640
+ return { ok: false, error: postExecResult.error };
37641
+ }
37642
+ return {
37643
+ ok: true,
37644
+ value: {
37645
+ solverExecutionResponse: postExecResult.value,
37646
+ intent,
37647
+ intentDeliveryInfo: {
37648
+ srcChainKey,
37649
+ srcTxHash: spokeTxHash,
37650
+ srcAddress: params.srcAddress,
37651
+ dstChainKey: params.dstChainKey,
37652
+ dstTxHash: dstIntentTxHash,
37653
+ dstAddress: params.dstAddress
37654
+ }
37655
+ }
37656
+ };
37657
+ } catch (error) {
37658
+ if (isLeverageYieldSwapError(error)) return { ok: false, error };
37659
+ return {
37660
+ ok: false,
37661
+ error: unknownFailed("leverageYield", error, baseCtx)
37662
+ };
37663
+ }
37664
+ }
37665
+ /**
37666
+ * Notifies the solver that the vault intent landed on the hub, triggering it to fill.
37667
+ * Leverage-yield copy of the swap domain's `postExecution` — emits only
37668
+ * `EXECUTION_FAILED` / `EXTERNAL_API_ERROR` / `UNKNOWN` (relay/verify codes appear only
37669
+ * on {@link LeverageYieldService.vaultSwap}, which owns the verify + relay steps).
37670
+ *
37671
+ * Called automatically by {@link LeverageYieldService.vaultSwap} after the relay packet
37672
+ * lands on the hub. Public so callers who created the intent via
37673
+ * {@link LeverageYieldService.createVaultIntent} and relayed it themselves can finish the
37674
+ * flow manually.
37675
+ *
37676
+ * @param request - `{ intent_tx_hash }` — the hub-chain (Sonic) tx hash where the intent
37677
+ * was registered (the relay packet's `dst_tx_hash`, or the spoke tx hash for hub-sourced
37678
+ * intents).
37679
+ */
37680
+ async notifySolver(request) {
37681
+ try {
37682
+ const result = await SolverApiService.postExecution(request, this.config.solver, this.config.logger);
37683
+ if (result.ok) return result;
37684
+ const detail = result.error?.detail ?? {
37685
+ code: -999,
37686
+ // SolverIntentErrorCode.UNKNOWN
37687
+ message: "Solver returned malformed error response"
37688
+ };
37689
+ return {
37690
+ ok: false,
37691
+ error: new SodaxError("EXTERNAL_API_ERROR", detail.message, {
37692
+ feature: "leverageYield",
37693
+ context: {
37694
+ phase: "postExecution",
37695
+ api: "solver",
37696
+ solverCode: detail.code,
37697
+ solverDetail: detail
37698
+ }
37699
+ })
37700
+ };
37701
+ } catch (error) {
37702
+ if (isLeverageYieldPostExecutionError(error)) return { ok: false, error };
37703
+ return { ok: false, error: executionFailed("leverageYield", error, { phase: "postExecution" }) };
37704
+ }
37705
+ }
37706
+ /**
37707
+ * Approves the vault's underlying asset to the leverage vault on Sonic. For callers
37708
+ * interacting with the vault directly on the hub — the swap-style {@link
37709
+ * LeverageYieldService.deposit} flow handles its own approvals.
37710
+ */
37711
+ async approve(params) {
37712
+ const baseCtx = { action: "approve" };
37713
+ try {
37714
+ leverageYieldInvariant(params.amount > 0n, "Amount must be greater than 0", { ...baseCtx, field: "amount" });
37715
+ leverageYieldInvariant(params.vault.length > 0, "Vault address is required", { ...baseCtx, field: "vault" });
37716
+ const assetResult = await this.getAsset(params.vault);
37717
+ if (!assetResult.ok) {
37718
+ return { ok: false, error: approveFailed("leverageYield", assetResult.error, baseCtx) };
37719
+ }
37720
+ const from = await params.walletProvider.getWalletAddress();
37721
+ const baseApprove = {
37722
+ token: assetResult.value,
37723
+ amount: params.amount,
37724
+ from,
37725
+ spender: params.vault
37726
+ };
37727
+ if (params.raw) {
37728
+ const tx2 = await Erc20Service.approve({ ...baseApprove, raw: true });
37729
+ return { ok: true, value: tx2 };
37730
+ }
37731
+ const tx = await Erc20Service.approve({
37732
+ ...baseApprove,
37733
+ raw: false,
37734
+ walletProvider: params.walletProvider
37735
+ });
37736
+ return { ok: true, value: tx };
37737
+ } catch (error) {
37738
+ if (isLeverageYieldApproveError(error)) return { ok: false, error };
37739
+ return { ok: false, error: approveFailed("leverageYield", error, baseCtx) };
37740
+ }
37741
+ }
37742
+ /**
37743
+ * Reads on-chain allowance of the vault's underlying asset for `owner → vault`. Returns
37744
+ * `true` when the allowance covers `amount`. Use before a direct
37745
+ * {@link LeverageYieldService.deposit}.
37746
+ */
37747
+ async isAllowanceValid(params) {
37748
+ const baseCtx = { action: "allowanceCheck" };
37749
+ try {
37750
+ leverageYieldInvariant(params.amount > 0n, "Amount must be greater than 0", { ...baseCtx, field: "amount" });
37751
+ const assetResult = await this.getAsset(params.vault);
37752
+ if (!assetResult.ok) {
37753
+ return { ok: false, error: allowanceCheckFailed("leverageYield", assetResult.error, baseCtx) };
37754
+ }
37755
+ const allowance = await this.hubProvider.publicClient.readContract({
37756
+ address: assetResult.value,
37757
+ abi: erc20Abi$1,
37758
+ functionName: "allowance",
37759
+ args: [params.owner, params.vault]
37760
+ });
37761
+ return { ok: true, value: allowance >= params.amount };
37762
+ } catch (error) {
37763
+ if (isLeverageYieldAllowanceCheckError(error)) return { ok: false, error };
37764
+ return { ok: false, error: allowanceCheckFailed("leverageYield", error, baseCtx) };
37765
+ }
37766
+ }
37767
+ // ─── Reads ──────────────────────────────────────────────────────────────
37768
+ /** ERC-4626 `asset()` of the vault — the sodaWEETH-style underlying. */
37769
+ async getAsset(vault) {
37770
+ try {
37771
+ const value = await this.hubProvider.publicClient.readContract({
37772
+ address: vault,
37773
+ abi: leverageYieldVaultAbi,
37774
+ functionName: "asset"
37775
+ });
37776
+ return { ok: true, value };
37777
+ } catch (error) {
37778
+ if (isLeverageYieldLookupError(error)) return { ok: false, error };
37779
+ return { ok: false, error: lookupFailed("leverageYield", "getAsset", error) };
37780
+ }
37781
+ }
37782
+ /** Reads the vault's leveraged position snapshot via the non-standard `getPositionDetails`. */
37783
+ async getPosition(vault) {
37784
+ try {
37785
+ const [collateral, debt, ltv, healthFactor, idleAsset] = await this.hubProvider.publicClient.readContract({
37786
+ address: vault,
37787
+ abi: leverageYieldVaultAbi,
37788
+ functionName: "getPositionDetails"
37789
+ });
37790
+ return { ok: true, value: { collateral, debt, ltv, healthFactor, idleAsset } };
37791
+ } catch (error) {
37792
+ if (isLeverageYieldLookupError(error)) return { ok: false, error };
37793
+ return { ok: false, error: lookupFailed("leverageYield", "getPosition", error) };
37794
+ }
37795
+ }
37796
+ /**
37797
+ * Computes the steady-state APR of a leverage-yield vault from the AAVE supply/borrow
37798
+ * rates of its asset and borrowToken, scaled by the vault's target leverage. Matches the
37799
+ * convention used by leveraged-LSD vaults (Origami, Gearbox, etc.) — assumes constant
37800
+ * LTV at `targetLTV` and constant AAVE rates.
37801
+ *
37802
+ * Returns raw fields in RAY (1e27, AAVE's native rate scale) plus the leverage multiplier
37803
+ * in WAD (1e18). See {@link LeverageYieldApr} for the formula and caveats.
37804
+ */
37805
+ async getApr(vault) {
37806
+ try {
37807
+ const [pool, asset, borrowToken, targetLtvBps] = await Promise.all([
37808
+ this.hubProvider.publicClient.readContract({
37809
+ address: vault,
37810
+ abi: leverageYieldVaultAbi,
37811
+ functionName: "pool"
37812
+ }),
37813
+ this.hubProvider.publicClient.readContract({
37814
+ address: vault,
37815
+ abi: leverageYieldVaultAbi,
37816
+ functionName: "asset"
37817
+ }),
37818
+ this.hubProvider.publicClient.readContract({
37819
+ address: vault,
37820
+ abi: leverageYieldVaultAbi,
37821
+ functionName: "borrowToken"
37822
+ }),
37823
+ this.hubProvider.publicClient.readContract({
37824
+ address: vault,
37825
+ abi: leverageYieldVaultAbi,
37826
+ functionName: "targetLTV"
37827
+ })
37828
+ ]);
37829
+ const [assetReserve, borrowReserve] = await Promise.all([
37830
+ this.hubProvider.publicClient.readContract({
37831
+ address: pool,
37832
+ abi: poolAbi,
37833
+ functionName: "getReserveData",
37834
+ args: [asset]
37835
+ }),
37836
+ this.hubProvider.publicClient.readContract({
37837
+ address: pool,
37838
+ abi: poolAbi,
37839
+ functionName: "getReserveData",
37840
+ args: [borrowToken]
37841
+ })
37842
+ ]);
37843
+ const supplyAprRay = assetReserve.currentLiquidityRate;
37844
+ const borrowAprRay = borrowReserve.currentVariableBorrowRate;
37845
+ const BPS = 10000n;
37846
+ const WAD2 = 1000000000000000000n;
37847
+ leverageYieldInvariant(targetLtvBps < BPS, `targetLTV (${targetLtvBps}) must be < 100% (10_000 bps)`, {
37848
+ method: "getApr",
37849
+ field: "targetLtvBps"
37850
+ });
37851
+ const leverageMultiplierWad = targetLtvBps * WAD2 / (BPS - targetLtvBps);
37852
+ const spreadRay = supplyAprRay - borrowAprRay;
37853
+ const netAprRay = supplyAprRay + spreadRay * leverageMultiplierWad / WAD2;
37854
+ return {
37855
+ ok: true,
37856
+ value: { supplyAprRay, borrowAprRay, targetLtvBps, leverageMultiplierWad, netAprRay }
37857
+ };
37858
+ } catch (error) {
37859
+ if (isLeverageYieldLookupError(error)) return { ok: false, error };
37860
+ return { ok: false, error: lookupFailed("leverageYield", "getApr", error) };
37861
+ }
37862
+ }
37863
+ /**
37864
+ * Off-chain LSD staking-APR for the vault's underlying asset. Looks the vault up in the
37865
+ * registry, hits DefiLlama's per-pool chart endpoint for the configured `poolId`, and on
37866
+ * any fetch failure returns the registry's hardcoded `fallbackAprPct` with `stale: true`.
37867
+ *
37868
+ * Always resolves to `{ ok: true, ... }` for a known vault — the fallback path replaces
37869
+ * the error, since a missing LSD APR shouldn't break the parent call. Returns
37870
+ * `{ aprRay: 0n, stale: true, label: 'no LSD source' }` for vaults without an
37871
+ * `lsdSource` configured (non-LSD strategies); callers can treat that as "skip LSD".
37872
+ */
37873
+ async getLsdApr(vault) {
37874
+ try {
37875
+ const cfg = this.getVaultByAddress(vault);
37876
+ const source = cfg?.lsdSource;
37877
+ if (!source) {
37878
+ return { ok: true, value: { aprRay: 0n, label: "no LSD source", stale: true } };
37879
+ }
37880
+ try {
37881
+ const aprPct = await fetchDefillamaApr(source.poolId);
37882
+ return { ok: true, value: { aprRay: pctToRay(aprPct), label: source.label, stale: false } };
37883
+ } catch (fetchError) {
37884
+ this.config.logger.warn("[leverageYield] DefiLlama APR fetch failed; using fallbackAprPct", {
37885
+ vault,
37886
+ poolId: source.poolId,
37887
+ fallbackAprPct: source.fallbackAprPct,
37888
+ error: fetchError instanceof Error ? fetchError.message : String(fetchError)
37889
+ });
37890
+ return {
37891
+ ok: true,
37892
+ value: { aprRay: pctToRay(source.fallbackAprPct), label: `${source.label} (fallback)`, stale: true }
37893
+ };
37894
+ }
37895
+ } catch (error) {
37896
+ if (isLeverageYieldLookupError(error)) return { ok: false, error };
37897
+ return { ok: false, error: lookupFailed("leverageYield", "getLsdApr", error) };
37898
+ }
37899
+ }
37900
+ /**
37901
+ * Combined view of {@link getApr} + {@link getLsdApr}: re-applies the vault's leverage
37902
+ * formula with the LSD's native staking yield folded into the supply side, exposing the
37903
+ * **effective** net APR that LSD-backed strategies actually earn. The AAVE-only
37904
+ * `netAprRay` is preserved on the return value for callers who want to display both.
37905
+ *
37906
+ * effectiveSupply = supplyAprRay + lsdApr.aprRay
37907
+ * effectiveNet = effectiveSupply + leverage × (effectiveSupply − borrowAprRay)
37908
+ *
37909
+ * Fetches AAVE rates and the LSD APR in parallel for one round-trip's worth of latency.
37910
+ */
37911
+ async getEffectiveApr(vault) {
37912
+ try {
37913
+ const [aprResult, lsdResult] = await Promise.all([this.getApr(vault), this.getLsdApr(vault)]);
37914
+ if (!aprResult.ok) return aprResult;
37915
+ if (!lsdResult.ok) return lsdResult;
37916
+ const apr = aprResult.value;
37917
+ const lsd = lsdResult.value;
37918
+ const effectiveSupplyAprRay = apr.supplyAprRay + lsd.aprRay;
37919
+ const spreadRay = effectiveSupplyAprRay - apr.borrowAprRay;
37920
+ const WAD2 = 1000000000000000000n;
37921
+ const effectiveNetAprRay = effectiveSupplyAprRay + spreadRay * apr.leverageMultiplierWad / WAD2;
37922
+ return {
37923
+ ok: true,
37924
+ value: { ...apr, lsdApr: lsd, effectiveSupplyAprRay, effectiveNetAprRay }
37925
+ };
37926
+ } catch (error) {
37927
+ if (isLeverageYieldLookupError(error)) return { ok: false, error };
37928
+ return { ok: false, error: lookupFailed("leverageYield", "getEffectiveApr", error) };
37929
+ }
37930
+ }
37931
+ /** Synchronously withdrawable assets for `owner` (clamped against leverage HF floor). */
37932
+ async getMaxWithdraw(vault, owner) {
37933
+ const inner = await Erc4626Service.getMaxWithdraw(vault, owner, this.hubProvider.publicClient);
37934
+ if (!inner.ok) return { ok: false, error: lookupFailed("leverageYield", "getMaxWithdraw", inner.error) };
37935
+ return { ok: true, value: inner.value };
37936
+ }
37937
+ /** Total underlying assets currently held by the vault (vault-asset units, 18 decimals) — TVL. */
37938
+ async getTotalAssets(vault) {
37939
+ const inner = await Erc4626Service.getTotalAssets(vault, this.hubProvider.publicClient);
37940
+ if (!inner.ok) return { ok: false, error: lookupFailed("leverageYield", "getTotalAssets", inner.error) };
37941
+ return { ok: true, value: inner.value };
37942
+ }
37943
+ /** Shares minted for a given asset deposit. */
37944
+ async previewDeposit(vault, assets) {
37945
+ const inner = await Erc4626Service.previewDeposit(vault, assets, this.hubProvider.publicClient);
37946
+ if (!inner.ok) return { ok: false, error: lookupFailed("leverageYield", "previewDeposit", inner.error) };
37947
+ return { ok: true, value: inner.value };
37948
+ }
37949
+ /** Shares burned for a given asset withdrawal. */
37950
+ async previewWithdraw(vault, assets) {
37951
+ const inner = await Erc4626Service.previewWithdraw(vault, assets, this.hubProvider.publicClient);
37952
+ if (!inner.ok) return { ok: false, error: lookupFailed("leverageYield", "previewWithdraw", inner.error) };
37953
+ return { ok: true, value: inner.value };
37954
+ }
37955
+ /** Assets received for a given share redemption. */
37956
+ async previewRedeem(vault, shares) {
37957
+ const inner = await Erc4626Service.previewRedeem(vault, shares, this.hubProvider.publicClient);
37958
+ if (!inner.ok) return { ok: false, error: lookupFailed("leverageYield", "previewRedeem", inner.error) };
37959
+ return { ok: true, value: inner.value };
37960
+ }
37961
+ /** Vault shares held by `owner`. */
37962
+ async getShareBalance(vault, owner) {
37963
+ try {
37964
+ const value = await this.hubProvider.publicClient.readContract({
37965
+ address: vault,
37966
+ abi: erc20Abi$1,
37967
+ functionName: "balanceOf",
37968
+ args: [owner]
37969
+ });
37970
+ return { ok: true, value };
37971
+ } catch (error) {
37972
+ if (isLeverageYieldLookupError(error)) return { ok: false, error };
37973
+ return { ok: false, error: lookupFailed("leverageYield", "getShareBalance", error) };
37974
+ }
37975
+ }
37976
+ /**
37977
+ * Convenience: resolves the user's hub wallet from `(srcChainKey, srcAddress)` and
37978
+ * returns its on-chain `maxWithdraw`, less {@link MAX_WITHDRAW_DUST_BUFFER}. The trim
37979
+ * keeps the value safe for an asset-denominated ERC-4626 `withdraw` — the raw
37980
+ * `maxWithdraw` can trip the round-up that asks for one more share than the user holds.
37981
+ */
37982
+ async getMaxWithdrawForUser(vault, srcChainKey, srcAddress) {
37983
+ try {
37984
+ const hubWallet = await this.hubProvider.getUserHubWalletAddress(srcAddress, srcChainKey);
37985
+ const maxWithdrawResult = await this.getMaxWithdraw(vault, hubWallet);
37986
+ if (!maxWithdrawResult.ok) return maxWithdrawResult;
37987
+ const buffered = maxWithdrawResult.value > MAX_WITHDRAW_DUST_BUFFER ? maxWithdrawResult.value - MAX_WITHDRAW_DUST_BUFFER : 0n;
37988
+ return { ok: true, value: buffered };
37989
+ } catch (error) {
37990
+ if (isLeverageYieldLookupError(error)) return { ok: false, error };
37991
+ return { ok: false, error: lookupFailed("leverageYield", "getMaxWithdrawForUser", error, { srcChainKey }) };
37992
+ }
37993
+ }
37994
+ /**
37995
+ * Convenience: resolves the user's hub wallet from `(srcChainKey, srcAddress)` and
37996
+ * returns its on-chain share balance.
37997
+ */
37998
+ async getShareBalanceForUser(vault, srcChainKey, srcAddress) {
37999
+ try {
38000
+ const hubWallet = await this.hubProvider.getUserHubWalletAddress(srcAddress, srcChainKey);
38001
+ return await this.getShareBalance(vault, hubWallet);
38002
+ } catch (error) {
38003
+ if (isLeverageYieldLookupError(error)) return { ok: false, error };
38004
+ return { ok: false, error: lookupFailed("leverageYield", "getShareBalanceForUser", error, { srcChainKey }) };
38005
+ }
38006
+ }
38007
+ };
38008
+
36837
38009
  // src/shared/entities/Sodax.ts
36838
38010
  var Sodax = class {
36839
38011
  instanceConfig;
@@ -36855,6 +38027,8 @@ var Sodax = class {
36855
38027
  // Recovery service for withdrawing stuck hub-wallet assets back to a spoke chain
36856
38028
  dex;
36857
38029
  // Dex service enabling DEX operations
38030
+ leverageYield;
38031
+ // Leverage-yield service: cross-chain deposits / withdrawals into ERC-4626 leverage vaults on Sonic
36858
38032
  config;
36859
38033
  // Config service enabling configuration data fetching from the backend API or fallbacking to default values
36860
38034
  hubProvider;
@@ -36862,9 +38036,10 @@ var Sodax = class {
36862
38036
  spoke;
36863
38037
  // spoke service enabling spoke chain operations
36864
38038
  constructor(config) {
38039
+ const logger = resolveLogger(config?.logger);
36865
38040
  this.instanceConfig = config ? mergeSodaxConfig(sodaxConfig, config) : sodaxConfig;
36866
- this.backendApi = new BackendApiService(this.instanceConfig.api);
36867
- this.config = new ConfigService({ api: this.backendApi, config: this.instanceConfig, userConfig: config });
38041
+ this.backendApi = new BackendApiService(this.instanceConfig.api, logger);
38042
+ this.config = new ConfigService({ api: this.backendApi, config: this.instanceConfig, userConfig: config, logger });
36868
38043
  this.hubProvider = new EvmHubProvider({ config: this.config });
36869
38044
  this.spoke = new SpokeService({ config: this.config, hubProvider: this.hubProvider });
36870
38045
  this.swaps = new SwapService({
@@ -36899,6 +38074,11 @@ var Sodax = class {
36899
38074
  config: this.config,
36900
38075
  spoke: this.spoke
36901
38076
  });
38077
+ this.leverageYield = new LeverageYieldService({
38078
+ hubProvider: this.hubProvider,
38079
+ config: this.config,
38080
+ spoke: this.spoke
38081
+ });
36902
38082
  }
36903
38083
  /**
36904
38084
  * Initializes the Sodax instance with dynamic configuration.
@@ -36939,4 +38119,4 @@ var isPartnerError = isCodeMember(PARTNER_CODES);
36939
38119
  (*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
36940
38120
  */
36941
38121
 
36942
- export { ALLOWANCE_CHECK_CODES, APPROVE_CODES, AssetService, BITCOIN_CHAIN_KEYS, BITCOIN_CHAIN_KEYS_SET, BTC_WALLET_ADDRESS_TYPES, BackendApiService, BalnSwapService, BigIntToHex, BigNumberZeroDecimal, BitcoinSpokeService, BnUSDMigrationService, BridgeService, CHAIN_KEYS, CONFIG_VERSION, CREATE_INTENT_CODES, ChainKeys, ChainTypeArr, ClService, ConfigService, CustomSorobanServer, CustomStellarAccount, DEFAULT_BACKEND_API_ENDPOINT, DEFAULT_BACKEND_API_HEADERS, DEFAULT_BACKEND_API_TIMEOUT, DEFAULT_DEADLINE_OFFSET, DEFAULT_MAX_RETRY, DEFAULT_RELAYER_API_ENDPOINT, DEFAULT_RELAY_TX_TIMEOUT, DEFAULT_RETRY_DELAY_MS, DexService, EVM_CHAIN_ID_TO_KEY, EVM_CHAIN_KEYS, EVM_CHAIN_KEYS_SET, EVM_SPOKE_ONLY_CHAIN_KEYS, EVM_SPOKE_ONLY_CHAIN_KEYS_SET, Erc20Service, Erc4626Service, EvmAssetManagerService, EvmHubProvider, EvmSolverService, EvmSpokeService, EvmVaultTokenService, FEE_PERCENTAGE_SCALE, GAS_ESTIMATION_CODES, HALF_RAY, HALF_WAD, HUB_CHAIN_KEY, HttpRelayError, HubVaultSymbols, ICON_CHAIN_KEYS, ICON_CHAIN_KEYS_SET, ICON_TX_RESULT_WAIT_MAX_RETRY, INJECTIVE_CHAIN_KEYS, INJECTIVE_CHAIN_KEYS_SET, INTENT_CHAIN_IDS, IconSpokeService, IcxMigrationService, Injective20Token, InjectiveSpokeService, IntentCreatedEventAbi, IntentDataType, IntentFilledEventAbi, IntentRelayChainIdToChainKey, IntentsAbi, LOOKUP_CODES, LTV_PRECISION, LendingPoolService, LockupMultiplier, LockupPeriod, MAX_UINT256, MigrationService, MintPositionEventAbi, MoneyMarketDataService, MoneyMarketService, NEAR_CHAIN_KEYS, NEAR_CHAIN_KEYS_SET, NEAR_DEFAULT_GAS, NearSpokeService, PartnerFeeClaimService, PartnerService, Permit2Service, ProtocolIntentsAbi, RAY, RAY_DECIMALS, RELAY_ERROR_CODES, RadfiApiError, RadfiProvider, RecoveryService, RelayChainIdMap, SECONDS_PER_YEAR, SODAX_ERROR_CODES, SODAX_FEATURES, SOLANA_CHAIN_KEYS, SOLANA_CHAIN_KEYS_SET, SONIC_CHAIN_KEYS, SONIC_CHAIN_KEYS_SET, STACKS_CHAIN_KEYS, STACKS_CHAIN_KEYS_SET, STELLAR_CHAIN_KEYS, STELLAR_CHAIN_KEYS_SET, SUI_CHAIN_KEYS, SUI_CHAIN_KEYS_SET, SodaTokens, Sodax, SodaxError, SolanaSpokeService, SolverApiService, SolverIntentErrorCode, SolverIntentStatusCode, SonicSpokeService, SpokeService, StacksSpokeService, StakingLogic, StakingService, StatATokenAddresses, StellarSpokeService, SuiSpokeService, SupportedMigrationTokens, SwapService, USD_DECIMALS, UiPoolDataProviderService, VAULT_TOKEN_DECIMALS, WAD, WAD_RAY_RATIO, WEI_DECIMALS, adjustAmountByFee, allowanceCheckFailed, apiConfig, approveFailed, arbitrumSupportedTokens, assetManagerAbi, avalancheSupportedTokens, balnSwapAbi, baseChainInfo, baseSupportedTokens, binomialApproximatedRayPow, bitcoinSupportedTokens, bnUSDLegacySpokeChainIds, bnUSDLegacyTokens, bridgeConfig, bridgeInvariant, bscSupportedTokens, calculateAllReserveIncentives, calculateAllUserIncentives, calculateAvailableBorrowsMarketReferenceCurrency, calculateCompoundedInterest, calculateCompoundedRate, calculateFeeAmount, calculateHealthFactorFromBalances, calculateHealthFactorFromBalancesBigUnits, calculateLinearInterest, calculatePercentageFeeAmount, clPoolAbi, clPoolManagerAbi, clPositionManagerAbi, clQuoterAbi, clRouterAbi, clTickLensAbi, concentratedLiquidityConfig, connectionAbi, convertTransactionInstructionToRaw, createInvariant, deepMerge, defaultHookAbi, detectBitcoinAddressType, dexConfig, dexInvariant, dexPools, encodeAddress, encodeBtcPayloadToBytes, encodeContractCalls, erc20Abi, erc4626Abi, estimateBitcoinTxSize, ethereumSupportedTokens, executionFailed, formatBasisPoints, formatEModeCategory, formatEModes, formatPercentage, formatReserve, formatReserveUSD, formatReserves, formatReservesAndIncentives, formatUserSummary, formatUserSummaryAndIncentives, gasEstimationFailed, getAllLegacybnUSDTokens, getAndFormatReserveEModes, getAssetManagerIdl, getAssetManagerProgram, getChainKeyFromRelayChainId, getChainType, getCompoundedBalance, getConcentratedLiquidityConfig, getConnectionIdl, getConnectionProgram, getEvmChainKeyByChainId, getEvmViemChain, getIconAddressBytes, getIntentRelayChainId, getLinearBalance, getMarketReferenceCurrencyAndUsdBalance, getPacket, getProvider, getRandomBytes, getReserveNormalizedIncome, getReservesEModes, getSolanaAddressBytes, getSolverConfig, getSupportedSolverTokens, getTransactionPackets, getbnUSDToken, hexToBigInt, hexToSolanaAddress, hubConfig, hyper, hyperevmSupportedTokens, iconSupportedTokens, injectiveSupportedTokens, intentCreationFailed, isBalnMigrateParams, isBitcoinChainKey, isBitcoinChainKeyType, isBitcoinWalletProviderType, isBridgeAllowanceCheckError, isBridgeApproveError, isBridgeCreateIntentError, isBridgeError, isBridgeLookupError, isBridgeOrchestrationError, isCodeMember, isDexError, isEvmChainKey, isEvmChainKeyType, isEvmSpokeChainConfig, isEvmSpokeOnlyChainKey, isEvmSpokeOnlyChainKeyType, isEvmWalletProviderType, isFeatureError, isHubChainKey, isHubChainKeyType, isIconAddress, isIconChainKey, isIconChainKeyType, isIcxCreateRevertMigrationParams, isIcxMigrateParams, isInjectiveChainKey, isInjectiveChainKeyType, isJsonRpcPayloadResponse, isLegacybnUSDChainId, isLegacybnUSDToken, isMigrateOrchestrationError, isMigrationAllowanceCheckError, isMigrationApproveError, isMigrationCreateIntentError, isMigrationError, isMigrationLookupError, isMoneyMarketAllowanceCheckError, isMoneyMarketApproveError, isMoneyMarketCreateIntentError, isMoneyMarketError, isMoneyMarketGasEstimationError, isMoneyMarketOrchestrationError, isNativeToken, isNearChainKey, isNearChainKeyType, isNewbnUSDChainId, isNewbnUSDToken, isOptionalBitcoinWalletProviderType, isOptionalEvmWalletProviderType, isOptionalStellarWalletProviderType, isPartnerError, isPartnerFeeAmount, isPartnerFeePercentage, isPostExecutionError, isRawDestinationParams, isRecoveryError, isResponseAddressType, isResponseSigningType, isRevertMigrationOrchestrationError, isSodaxError, isSolanaChainKey, isSolanaChainKeyType, isSolanaNativeToken, isSonicChainKey, isSonicChainKeyType, isSpokeApproveParamsEvmSpoke, isSpokeApproveParamsHub, isSpokeApproveParamsStellar, isSpokeChainKey, isSpokeIsAllowanceValidParamsEvmSpoke, isSpokeIsAllowanceValidParamsHub, isSpokeIsAllowanceValidParamsStellar, isStacksChainKey, isStacksChainKeyType, isStakeOrchestrationError, isStakingAllowanceCheckError, isStakingApproveError, isStakingCreateIntentError, isStakingError, isStakingInfoFetchError, isStakingOrchestrationError, isStellarChainKey, isStellarChainKeyType, isStellarWalletProviderType, isSubmitSwapTxResponse, isSubmitSwapTxStatusResponse, isSuiChainKey, isSuiChainKeyType, isSupportedBitcoinAddressType, isSwapCreateIntentError, isSwapError, isSwapSupportedToken, isUndefinedOrValidWalletProviderForChainKey, isUnifiedBnUSDMigrateParams, isValidWalletProviderForChainKey, kaiaSupportedTokens, lightlinkSupportedTokens, lookupFailed, mapRelayFailure, mergeSodaxConfig, messageOf, migrationInvariant, mintPositionParamsAbi, mmInvariant, modifyLiquidityParamsAbi, moneyMarketConfig, moneyMarketReserveAssets, moneyMarketSupportedTokens, nativeToUSD, nearSupportedTokens, newbnUSDSpokeChainIds, normalize, normalizeBN, normalizePsbtToBase64, normalizedToUsd, optimismSupportedTokens, pancakeSwapInfinityDefaultHookAbi, pancakeSwapInfinityPoolManagerAbi, pancakeSwapInfinityPositionManagerAbi, parseToStroops, parseTokenArrayFromJson, partnerInvariant, permit2Abi, polygonSupportedTokens, poolAbi, poolKeyAbi, randomUint256, rayDiv, rayMul, rayPow, rayToWad, recoveryInvariant, redbellySupportedTokens, relayConfig, relayTxAndWaitPacket, requestAddress, requestJsonRpc, requestSigning, retry, reverseEncodeAddress, serializeAddressData, sleep, sodaxConfig, sodaxInvariant, solanaSupportedTokens, solverConfig, sonicSupportedTokens, sonicWalletFactoryAbi, spokeAssetManagerAbi, spokeChainConfig, spokeChainKeysSet, stacksSupportedTokens, stakedSodaAbi, stakingInvariant, stakingRouterAbi, stataTokenFactoryAbi, stellarSupportedTokens, submitTransaction, suiSupportedTokens, supportedSpokeChains, supportedTokensByChain, swapExactInSingleParamsAbi, swapInvariant, swapSupportedTokens, swapsConfig, uiPoolDataAbi, universalRouterAbi, unknownFailed, valueToBigNumber, valueToZDBigNumber, variableDebtTokenAbi, vaultTokenAbi, verifyFailed, wadToRay, waitForStacksTransaction, waitForTransactionReceipt, waitUntilIntentExecuted, walletFactoryAbi, wrappedSonicAbi };
38122
+ export { ALLOWANCE_CHECK_CODES, APPROVE_CODES, AssetService, BITCOIN_CHAIN_KEYS, BITCOIN_CHAIN_KEYS_SET, BTC_WALLET_ADDRESS_TYPES, BackendApiService, BalnSwapService, BigIntToHex, BigNumberZeroDecimal, BitcoinSpokeService, BnUSDMigrationService, BridgeService, CHAIN_KEYS, CONFIG_VERSION, CREATE_INTENT_CODES, ChainKeys, ChainTypeArr, ClService, ConfigService, CustomSorobanServer, CustomStellarAccount, DEFAULT_BACKEND_API_ENDPOINT, DEFAULT_BACKEND_API_HEADERS, DEFAULT_BACKEND_API_TIMEOUT, DEFAULT_DEADLINE_OFFSET, DEFAULT_MAX_RETRY, DEFAULT_RELAYER_API_ENDPOINT, DEFAULT_RELAY_TX_TIMEOUT, DEFAULT_RETRY_DELAY_MS, DexService, EVM_CHAIN_ID_TO_KEY, EVM_CHAIN_KEYS, EVM_CHAIN_KEYS_SET, EVM_SPOKE_ONLY_CHAIN_KEYS, EVM_SPOKE_ONLY_CHAIN_KEYS_SET, Erc20Service, Erc4626Service, EvmAssetManagerService, EvmHubProvider, EvmSolverService, EvmSpokeService, EvmVaultTokenService, FEE_PERCENTAGE_SCALE, GAS_ESTIMATION_CODES, HALF_RAY, HALF_WAD, HUB_CHAIN_KEY, HttpRelayError, HubVaultSymbols, ICON_CHAIN_KEYS, ICON_CHAIN_KEYS_SET, ICON_TX_RESULT_WAIT_MAX_RETRY, INJECTIVE_CHAIN_KEYS, INJECTIVE_CHAIN_KEYS_SET, INTENT_CHAIN_IDS, IconSpokeService, IcxMigrationService, Injective20Token, InjectiveSpokeService, IntentCreatedEventAbi, IntentDataType, IntentFilledEventAbi, IntentRelayChainIdToChainKey, IntentsAbi, LOOKUP_CODES, LTV_PRECISION, LendingPoolService, LeverageYieldService, LockupMultiplier, LockupPeriod, LsodaSymbols, LsodaTokens, MAX_UINT256, MigrationService, MintPositionEventAbi, MoneyMarketDataService, MoneyMarketService, NEAR_CHAIN_KEYS, NEAR_CHAIN_KEYS_SET, NEAR_DEFAULT_GAS, NEAR_STORAGE_DEPOSIT, NearSpokeService, PartnerFeeClaimService, PartnerService, Permit2Service, ProtocolIntentsAbi, RAY, RAY_DECIMALS, RELAY_ERROR_CODES, RadfiApiError, RadfiProvider, RecoveryService, RelayChainIdMap, SECONDS_PER_YEAR, SODAX_ERROR_CODES, SODAX_FEATURES, SOLANA_CHAIN_KEYS, SOLANA_CHAIN_KEYS_SET, SONIC_CHAIN_KEYS, SONIC_CHAIN_KEYS_SET, STACKS_CHAIN_KEYS, STACKS_CHAIN_KEYS_SET, STELLAR_CHAIN_KEYS, STELLAR_CHAIN_KEYS_SET, SUI_CHAIN_KEYS, SUI_CHAIN_KEYS_SET, SodaTokens, Sodax, SodaxError, SolanaSpokeService, SolverApiService, SolverIntentErrorCode, SolverIntentStatusCode, SonicSpokeService, SpokeService, StacksSpokeService, StakingLogic, StakingService, StatATokenAddresses, StellarSpokeService, SuiSpokeService, SupportedMigrationTokens, SwapService, USD_DECIMALS, UiPoolDataProviderService, VAULT_TOKEN_DECIMALS, WAD, WAD_RAY_RATIO, WEI_DECIMALS, adjustAmountByFee, allowanceCheckFailed, apiConfig, approveFailed, arbitrumSupportedTokens, assetManagerAbi, avalancheSupportedTokens, balnSwapAbi, baseChainInfo, baseSupportedTokens, binomialApproximatedRayPow, bitcoinSupportedTokens, bnUSDLegacySpokeChainIds, bnUSDLegacyTokens, bridgeConfig, bridgeInvariant, bscSupportedTokens, calcOpReturnOutputVbytes, calculateAllReserveIncentives, calculateAllUserIncentives, calculateAvailableBorrowsMarketReferenceCurrency, calculateCompoundedInterest, calculateCompoundedRate, calculateFeeAmount, calculateHealthFactorFromBalances, calculateHealthFactorFromBalancesBigUnits, calculateLinearInterest, calculatePercentageFeeAmount, clPoolAbi, clPoolManagerAbi, clPositionManagerAbi, clQuoterAbi, clRouterAbi, clTickLensAbi, concentratedLiquidityConfig, connectionAbi, consoleLogger, convertTransactionInstructionToRaw, createInvariant, deepMerge, defaultHookAbi, detectBitcoinAddressType, dexConfig, dexInvariant, dexPools, encodeAddress, encodeBtcPayloadToBytes, encodeContractCalls, erc20Abi, erc4626Abi, estimateBitcoinTxSize, ethereumSupportedTokens, executionFailed, formatBasisPoints, formatEModeCategory, formatEModes, formatPercentage, formatReserve, formatReserveUSD, formatReserves, formatReservesAndIncentives, formatUserSummary, formatUserSummaryAndIncentives, gasEstimationFailed, getAllLegacybnUSDTokens, getAndFormatReserveEModes, getAssetManagerIdl, getAssetManagerProgram, getChainKeyFromRelayChainId, getChainType, getCompoundedBalance, getConcentratedLiquidityConfig, getConnectionIdl, getConnectionProgram, getEvmChainKeyByChainId, getEvmViemChain, getIconAddressBytes, getIntentRelayChainId, getLinearBalance, getMarketReferenceCurrencyAndUsdBalance, getPacket, getProvider, getRandomBytes, getReserveNormalizedIncome, getReservesEModes, getSolanaAddressBytes, getSolverConfig, getSupportedSolverTokens, getTransactionPackets, getbnUSDToken, hexToBigInt, hexToSolanaAddress, hubConfig, hyper, hyperevmSupportedTokens, iconSupportedTokens, injectiveSupportedTokens, intentCreationFailed, isBalnMigrateParams, isBitcoinChainKey, isBitcoinChainKeyType, isBitcoinWalletProviderType, isBridgeAllowanceCheckError, isBridgeApproveError, isBridgeCreateIntentError, isBridgeError, isBridgeLookupError, isBridgeOrchestrationError, isCodeMember, isDexError, isEvmChainKey, isEvmChainKeyType, isEvmSpokeChainConfig, isEvmSpokeOnlyChainKey, isEvmSpokeOnlyChainKeyType, isEvmWalletProviderType, isFeatureError, isHubChainKey, isHubChainKeyType, isIconAddress, isIconChainKey, isIconChainKeyType, isIcxCreateRevertMigrationParams, isIcxMigrateParams, isInjectiveChainKey, isInjectiveChainKeyType, isJsonRpcPayloadResponse, isLegacybnUSDChainId, isLegacybnUSDToken, isLeverageYieldAllowanceCheckError, isLeverageYieldApproveError, isLeverageYieldCreateIntentError, isLeverageYieldError, isLeverageYieldLookupError, isLeverageYieldPostExecutionError, isLeverageYieldSwapError, isMigrateOrchestrationError, isMigrationAllowanceCheckError, isMigrationApproveError, isMigrationCreateIntentError, isMigrationError, isMigrationLookupError, isMoneyMarketAllowanceCheckError, isMoneyMarketApproveError, isMoneyMarketCreateIntentError, isMoneyMarketError, isMoneyMarketGasEstimationError, isMoneyMarketOrchestrationError, isNativeToken, isNearChainKey, isNearChainKeyType, isNewbnUSDChainId, isNewbnUSDToken, isOptionalBitcoinWalletProviderType, isOptionalEvmWalletProviderType, isOptionalStellarWalletProviderType, isPartnerError, isPartnerFeeAmount, isPartnerFeePercentage, isPostExecutionError, isRawDestinationParams, isRecoveryError, isResponseAddressType, isResponseSigningType, isRevertMigrationOrchestrationError, isSodaxError, isSolanaChainKey, isSolanaChainKeyType, isSolanaNativeToken, isSonicChainKey, isSonicChainKeyType, isSpokeApproveParamsEvmSpoke, isSpokeApproveParamsHub, isSpokeApproveParamsStellar, isSpokeChainKey, isSpokeIsAllowanceValidParamsEvmSpoke, isSpokeIsAllowanceValidParamsHub, isSpokeIsAllowanceValidParamsStellar, isStacksChainKey, isStacksChainKeyType, isStakeOrchestrationError, isStakingAllowanceCheckError, isStakingApproveError, isStakingCreateIntentError, isStakingError, isStakingInfoFetchError, isStakingOrchestrationError, isStellarChainKey, isStellarChainKeyType, isStellarWalletProviderType, isSubmitSwapTxResponse, isSubmitSwapTxStatusResponse, isSuiChainKey, isSuiChainKeyType, isSupportedBitcoinAddressType, isSwapCreateIntentError, isSwapError, isSwapSupportedToken, isUndefinedOrValidWalletProviderForChainKey, isUnifiedBnUSDMigrateParams, isValidWalletProviderForChainKey, kaiaSupportedTokens, leverageYieldConfig, leverageYieldInvariant, leverageYieldVaults, lightlinkSupportedTokens, lookupFailed, mapRelayFailure, mergeSodaxConfig, messageOf, migrationInvariant, mintPositionParamsAbi, mmInvariant, modifyLiquidityParamsAbi, moneyMarketConfig, moneyMarketReserveAssets, moneyMarketSupportedTokens, nativeToUSD, nearSupportedTokens, newbnUSDSpokeChainIds, normalize, normalizeBN, normalizePsbtToBase64, normalizeSignatureToBase64, normalizedToUsd, optimismSupportedTokens, pancakeSwapInfinityDefaultHookAbi, pancakeSwapInfinityPoolManagerAbi, pancakeSwapInfinityPositionManagerAbi, parseToStroops, parseTokenArrayFromJson, partnerInvariant, permit2Abi, polygonSupportedTokens, poolAbi, poolKeyAbi, randomUint256, rayDiv, rayMul, rayPow, rayToWad, recoveryInvariant, redbellySupportedTokens, relayConfig, relayTxAndWaitPacket, requestAddress, requestJsonRpc, requestSigning, resolveLogger, retry, reverseEncodeAddress, serializeAddressData, silentLogger, sleep, sodaxConfig, sodaxInvariant, solanaSupportedTokens, solverConfig, sonicSupportedTokens, sonicWalletFactoryAbi, spokeAssetManagerAbi, spokeChainConfig, spokeChainKeysSet, stacksSupportedTokens, stakedSodaAbi, stakingInvariant, stakingRouterAbi, stataTokenFactoryAbi, stellarSupportedTokens, submitTransaction, suiSupportedTokens, supportedSpokeChains, supportedTokensByChain, swapExactInSingleParamsAbi, swapInvariant, swapSupportedTokens, swapsConfig, uiPoolDataAbi, universalRouterAbi, unknownFailed, usesBip322MessageSigning, valueToBigNumber, valueToZDBigNumber, variableDebtTokenAbi, vaultTokenAbi, verifyFailed, wadToRay, waitForStacksTransaction, waitForTransactionReceipt, waitUntilIntentExecuted, walletFactoryAbi, wrappedSonicAbi };