@sodax/sdk 1.3.1-beta-rc1 → 1.3.1-beta-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -17,6 +17,8 @@ var coreProtoTs = require('@injectivelabs/core-proto-ts');
17
17
  var rlp = require('rlp');
18
18
  var anchor = require('@coral-xyz/anchor');
19
19
  var BN = require('bn.js');
20
+ var bitcoin = require('bitcoinjs-lib');
21
+ var ecc = require('@bitcoinerlab/secp256k1');
20
22
 
21
23
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
22
24
 
@@ -44,6 +46,8 @@ var BigNumber4__default = /*#__PURE__*/_interopDefault(BigNumber4);
44
46
  var rlp__namespace = /*#__PURE__*/_interopNamespace(rlp);
45
47
  var anchor__namespace = /*#__PURE__*/_interopNamespace(anchor);
46
48
  var BN__default = /*#__PURE__*/_interopDefault(BN);
49
+ var bitcoin__namespace = /*#__PURE__*/_interopNamespace(bitcoin);
50
+ var ecc__namespace = /*#__PURE__*/_interopNamespace(ecc);
47
51
 
48
52
  // src/shared/abis/asset-manager.abi.ts
49
53
  var assetManagerAbi = [
@@ -6855,7 +6859,7 @@ var stakingRouterAbi = [
6855
6859
  ];
6856
6860
 
6857
6861
  // ../types/dist/constants/index.js
6858
- var CONFIG_VERSION = 27;
6862
+ var CONFIG_VERSION = 28;
6859
6863
  var AVALANCHE_MAINNET_CHAIN_ID = "0xa86a.avax";
6860
6864
  var ARBITRUM_MAINNET_CHAIN_ID = "0xa4b1.arbitrum";
6861
6865
  var BASE_MAINNET_CHAIN_ID = "0x2105.base";
@@ -6872,6 +6876,7 @@ var HYPEREVM_MAINNET_CHAIN_ID = "hyper";
6872
6876
  var LIGHTLINK_MAINNET_CHAIN_ID = "lightlink";
6873
6877
  var NEAR_MAINNET_CHAIN_ID = "near";
6874
6878
  var ETHEREUM_MAINNET_CHAIN_ID = "ethereum";
6879
+ var BITCOIN_MAINNET_CHAIN_ID = "bitcoin";
6875
6880
  var REDBELLY_MAINNET_CHAIN_ID = "redbelly";
6876
6881
  var KAIA_MAINNET_CHAIN_ID = "0x2019.kaia";
6877
6882
  var HUB_CHAIN_IDS = [SONIC_MAINNET_CHAIN_ID];
@@ -6892,6 +6897,7 @@ var CHAIN_IDS = [
6892
6897
  LIGHTLINK_MAINNET_CHAIN_ID,
6893
6898
  NEAR_MAINNET_CHAIN_ID,
6894
6899
  ETHEREUM_MAINNET_CHAIN_ID,
6900
+ BITCOIN_MAINNET_CHAIN_ID,
6895
6901
  REDBELLY_MAINNET_CHAIN_ID,
6896
6902
  KAIA_MAINNET_CHAIN_ID
6897
6903
  ];
@@ -7006,6 +7012,12 @@ var baseChainInfo = {
7006
7012
  type: "EVM",
7007
7013
  chainId: 1
7008
7014
  },
7015
+ [BITCOIN_MAINNET_CHAIN_ID]: {
7016
+ name: "Bitcoin",
7017
+ id: BITCOIN_MAINNET_CHAIN_ID,
7018
+ type: "BITCOIN",
7019
+ chainId: "bitcoin"
7020
+ },
7009
7021
  [REDBELLY_MAINNET_CHAIN_ID]: {
7010
7022
  name: "Redbelly",
7011
7023
  id: REDBELLY_MAINNET_CHAIN_ID,
@@ -7036,6 +7048,7 @@ var ChainIdToIntentRelayChainId = {
7036
7048
  [LIGHTLINK_MAINNET_CHAIN_ID]: 27756n,
7037
7049
  [NEAR_MAINNET_CHAIN_ID]: 15n,
7038
7050
  [ETHEREUM_MAINNET_CHAIN_ID]: 2n,
7051
+ [BITCOIN_MAINNET_CHAIN_ID]: 627463n,
7039
7052
  [REDBELLY_MAINNET_CHAIN_ID]: 726564n,
7040
7053
  [KAIA_MAINNET_CHAIN_ID]: 27489n
7041
7054
  };
@@ -7948,6 +7961,42 @@ var spokeChainConfig = {
7948
7961
  rpcUrl: "https://injective-rpc.publicnode.com:443",
7949
7962
  walletAddress: ""
7950
7963
  },
7964
+ [BITCOIN_MAINNET_CHAIN_ID]: {
7965
+ addresses: {
7966
+ assetManager: "bc1p4z9555xw0266vhq2x5un4zdmm9dt9zyet32fs7wa7u5ckdxusd9qsw4xfx"
7967
+ },
7968
+ chain: baseChainInfo[BITCOIN_MAINNET_CHAIN_ID],
7969
+ bnUSD: "no",
7970
+ nativeToken: "BTC",
7971
+ supportedTokens: {
7972
+ BTC: {
7973
+ symbol: "BTC",
7974
+ name: "Bitcoin",
7975
+ decimals: 8,
7976
+ address: "0:0",
7977
+ xChainId: BITCOIN_MAINNET_CHAIN_ID
7978
+ },
7979
+ bnUSD: {
7980
+ symbol: "bnUSD",
7981
+ name: "bnUSD",
7982
+ decimals: 18,
7983
+ address: "0:0",
7984
+ xChainId: BITCOIN_MAINNET_CHAIN_ID
7985
+ },
7986
+ BUSD: {
7987
+ symbol: "BUSD",
7988
+ name: "BUSDSTABLECOIN",
7989
+ decimals: 6,
7990
+ address: "897442:43",
7991
+ xChainId: BITCOIN_MAINNET_CHAIN_ID
7992
+ }
7993
+ },
7994
+ radfiApiUrl: "https://staging.api.radfi.co/api",
7995
+ radfiApiKey: "",
7996
+ radfiUmsUrl: "https://staging.ums.radfi.co/api",
7997
+ network: "MAINNET",
7998
+ rpcUrl: "https://mempool.space/api"
7999
+ },
7951
8000
  [STELLAR_MAINNET_CHAIN_ID]: {
7952
8001
  addresses: {
7953
8002
  connection: "CDFQDDPUPAM3XPGORHDOEFRNLMKOH3N3X6XTXNLSXJQXIU3RVCM3OPEP",
@@ -9444,6 +9493,22 @@ var hubAssets = {
9444
9493
  name: "RedBelly POL",
9445
9494
  vault: SodaTokens.sodaPOL.address
9446
9495
  }
9496
+ },
9497
+ [BITCOIN_MAINNET_CHAIN_ID]: {
9498
+ [spokeChainConfig[BITCOIN_MAINNET_CHAIN_ID].supportedTokens.BTC.address]: {
9499
+ asset: "0xeb0393893b5bf98a50073d6740738b08e575058b",
9500
+ decimal: 8,
9501
+ symbol: "BTC",
9502
+ name: "Bitcoin",
9503
+ vault: "0x7A1A5555842Ad2D0eD274d09b5c4406a95799D5d"
9504
+ },
9505
+ [spokeChainConfig[BITCOIN_MAINNET_CHAIN_ID].supportedTokens.BUSD.address]: {
9506
+ asset: "0xdb41c7d09406026d4582bc2fc6d6319c323fe1bb",
9507
+ decimal: 6,
9508
+ symbol: "BUSD",
9509
+ name: "BUSD.BUSD.BUSD",
9510
+ vault: "0xE801CA34E19aBCbFeA12025378D19c4FBE250131"
9511
+ }
9447
9512
  }
9448
9513
  };
9449
9514
  var solverConfig = {
@@ -9584,6 +9649,10 @@ var swapSupportedTokens = {
9584
9649
  spokeChainConfig[NEAR_MAINNET_CHAIN_ID].supportedTokens.USDC,
9585
9650
  spokeChainConfig[NEAR_MAINNET_CHAIN_ID].supportedTokens.USDT
9586
9651
  ],
9652
+ [BITCOIN_MAINNET_CHAIN_ID]: [
9653
+ spokeChainConfig[BITCOIN_MAINNET_CHAIN_ID].supportedTokens.BTC
9654
+ // spokeChainConfig[BITCOIN_MAINNET_CHAIN_ID].supportedTokens.BUSD, // TODO: re-enable when trading wallet balance is ready
9655
+ ],
9587
9656
  [ETHEREUM_MAINNET_CHAIN_ID]: [
9588
9657
  spokeChainConfig[ETHEREUM_MAINNET_CHAIN_ID].supportedTokens.ETH,
9589
9658
  spokeChainConfig[ETHEREUM_MAINNET_CHAIN_ID].supportedTokens.bnUSD,
@@ -9785,6 +9854,9 @@ var moneyMarketSupportedTokens = {
9785
9854
  spokeChainConfig[KAIA_MAINNET_CHAIN_ID].supportedTokens.bnUSD,
9786
9855
  spokeChainConfig[KAIA_MAINNET_CHAIN_ID].supportedTokens.USDT,
9787
9856
  spokeChainConfig[KAIA_MAINNET_CHAIN_ID].supportedTokens.SODA
9857
+ ],
9858
+ [BITCOIN_MAINNET_CHAIN_ID]: [
9859
+ spokeChainConfig[BITCOIN_MAINNET_CHAIN_ID].supportedTokens.BTC
9788
9860
  ]
9789
9861
  };
9790
9862
  var moneyMarketReserveAssets = [
@@ -9809,7 +9881,7 @@ var defaultSharedConfig = {
9809
9881
  };
9810
9882
 
9811
9883
  // ../types/dist/common/index.js
9812
- var ChainTypeArr = ["ICON", "EVM", "INJECTIVE", "SUI", "STELLAR", "SOLANA", "NEAR"];
9884
+ var ChainTypeArr = ["ICON", "EVM", "INJECTIVE", "SUI", "STELLAR", "SOLANA", "NEAR", "BITCOIN"];
9813
9885
 
9814
9886
  // ../types/dist/injective/index.js
9815
9887
  var InjectiveExecuteResponse = class _InjectiveExecuteResponse {
@@ -9828,6 +9900,17 @@ var InjectiveExecuteResponse = class _InjectiveExecuteResponse {
9828
9900
  return response;
9829
9901
  }
9830
9902
  };
9903
+
9904
+ // ../types/dist/btc/index.js
9905
+ function detectBitcoinAddressType(address) {
9906
+ if (address.startsWith("bc1p") || address.startsWith("tb1p"))
9907
+ return "P2TR";
9908
+ if (address.startsWith("bc1") || address.startsWith("tb1"))
9909
+ return "P2WPKH";
9910
+ if (address.startsWith("1") || address.startsWith("m") || address.startsWith("n"))
9911
+ return "P2PKH";
9912
+ throw new Error(`Unknown Bitcoin address type: ${address}`);
9913
+ }
9831
9914
  var DEFAULT_MAX_RETRY = 3;
9832
9915
  var DEFAULT_RELAY_TX_TIMEOUT = 12e4;
9833
9916
  var DEFAULT_RETRY_DELAY_MS = 2e3;
@@ -21206,6 +21289,714 @@ var SolanaSpokeService = class _SolanaSpokeService {
21206
21289
  }
21207
21290
  }
21208
21291
  };
21292
+
21293
+ // src/shared/entities/btc/RadfiProvider.ts
21294
+ var RadfiProvider = class {
21295
+ constructor(config) {
21296
+ this.config = config;
21297
+ }
21298
+ async authenticate(params) {
21299
+ const res = await this.request("/auth/authenticate", {
21300
+ method: "POST",
21301
+ body: JSON.stringify(params)
21302
+ });
21303
+ if (!res.ok) {
21304
+ const err = await res.json();
21305
+ throw new Error(err.message || "Radfi authentication failed");
21306
+ }
21307
+ return res.json().then((r) => ({
21308
+ accessToken: r.data?.accessToken ?? "",
21309
+ refreshToken: r.data?.refreshToken ?? "",
21310
+ tradingAddress: r.data?.tradingAddress ?? r.data?.wallet?.tradingAddress ?? ""
21311
+ }));
21312
+ }
21313
+ async refreshAccessToken(refreshToken) {
21314
+ const res = await this.request("/auth/refresh-token", {
21315
+ method: "POST",
21316
+ body: JSON.stringify({ refreshToken })
21317
+ });
21318
+ if (!res.ok) {
21319
+ const err = await res.json();
21320
+ throw new Error(err.message || "Token refresh failed");
21321
+ }
21322
+ return res.json().then((r) => ({
21323
+ accessToken: r.data?.accessToken ?? "",
21324
+ refreshToken: r.data?.refreshToken ?? refreshToken
21325
+ }));
21326
+ }
21327
+ async createTradingWallet(params, accessToken) {
21328
+ const res = await this.request("/wallets", {
21329
+ method: "POST",
21330
+ headers: {
21331
+ Authorization: `Bearer ${accessToken || this.config.apiKey}`
21332
+ },
21333
+ body: JSON.stringify(params)
21334
+ });
21335
+ if (!res.ok) {
21336
+ const err = await res.json();
21337
+ throw new Error(err.message || "Failed to create trading wallet");
21338
+ }
21339
+ return res.json().then((r) => r.data);
21340
+ }
21341
+ async getTradingWallet(userAddress, accessToken) {
21342
+ const res = await this.request(`/wallets/details/${userAddress}`, {
21343
+ method: "GET",
21344
+ headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
21345
+ });
21346
+ if (!res.ok) {
21347
+ throw new Error("Trading wallet not found");
21348
+ }
21349
+ const data = await res.json().then((r) => r.data);
21350
+ if (!data) throw new Error("Trading wallet not found");
21351
+ return data;
21352
+ }
21353
+ async getBalance(address) {
21354
+ if (!this.config.umsUrl) {
21355
+ throw new Error("RadfiConfig.umsUrl is required for getBalance");
21356
+ }
21357
+ const umsUrl = this.config.umsUrl;
21358
+ const res = await fetch(`${umsUrl}/wallets/balance?address=${address}`, {
21359
+ method: "GET",
21360
+ headers: { "Content-Type": "application/json" }
21361
+ });
21362
+ if (!res.ok) {
21363
+ throw new Error("Failed to fetch wallet balance");
21364
+ }
21365
+ const { data } = await res.json();
21366
+ return {
21367
+ btcSatoshi: BigInt(data.btcSatoshi ?? "0"),
21368
+ pendingSatoshi: BigInt(data.pendingSatoshi ?? "0"),
21369
+ externalPendingSatoshi: BigInt(data.externalPendingSatoshi ?? "0"),
21370
+ totalUtxos: Number(data.totalUtxos ?? 0)
21371
+ };
21372
+ }
21373
+ async checkIfTradingWalletExists(userAddress) {
21374
+ try {
21375
+ await this.getTradingWallet(userAddress);
21376
+ return true;
21377
+ } catch (error) {
21378
+ return false;
21379
+ }
21380
+ }
21381
+ async createWithdrawTransaction(params, accessToken) {
21382
+ const res = await this.request("/sodax/transaction", {
21383
+ method: "POST",
21384
+ headers: {
21385
+ Authorization: `Bearer ${accessToken ?? this.config.apiKey}`
21386
+ },
21387
+ body: JSON.stringify({
21388
+ type: "sodax-withdraw",
21389
+ params: {
21390
+ amount: params.amount.toString(),
21391
+ tokenId: params.token,
21392
+ sodaxData: params.data
21393
+ }
21394
+ })
21395
+ });
21396
+ if (!res.ok) {
21397
+ const err = await res.json();
21398
+ throw new Error(err.message || "Radfi transaction request failed");
21399
+ }
21400
+ return res.json().then((r) => r.data);
21401
+ }
21402
+ async requestRadfiSignature(params, accessToken) {
21403
+ const res = await this.request("/sodax/transaction/sign", {
21404
+ method: "POST",
21405
+ headers: {
21406
+ Authorization: `Bearer ${accessToken ?? this.config.apiKey}`
21407
+ },
21408
+ body: JSON.stringify({
21409
+ type: "sodax-withdraw",
21410
+ params
21411
+ })
21412
+ });
21413
+ if (!res.ok) {
21414
+ const err = await res.json();
21415
+ throw new Error(err.message || "Radfi signature request failed");
21416
+ }
21417
+ return res.json().then((r) => r.data.txId);
21418
+ }
21419
+ /**
21420
+ * Fetch expired (or near-expiry) UTXOs for a trading wallet address from UMS API.
21421
+ */
21422
+ async getExpiredUtxos(tradingAddress, params) {
21423
+ if (!this.config.umsUrl) {
21424
+ throw new Error("RadfiConfig.umsUrl is required for getExpiredUtxos");
21425
+ }
21426
+ const page = params?.page ?? 1;
21427
+ const pageSize = params?.pageSize ?? 100;
21428
+ const url = `${this.config.umsUrl}/utxos?address_eq=${tradingAddress}&isSpent_eq=false&isExpired_eq=true&page=${page}&pageSize=${pageSize}`;
21429
+ const res = await fetch(url, {
21430
+ method: "GET",
21431
+ headers: { "Content-Type": "application/json" }
21432
+ });
21433
+ if (!res.ok) {
21434
+ throw new Error("Failed to fetch expired UTXOs");
21435
+ }
21436
+ return res.json();
21437
+ }
21438
+ /**
21439
+ * Build a renew-utxo transaction via the Radfi API.
21440
+ * Returns a PSBT that needs to be signed by the user.
21441
+ */
21442
+ async buildRenewUtxoTransaction(params, accessToken) {
21443
+ const res = await this.request("/transactions", {
21444
+ method: "POST",
21445
+ headers: {
21446
+ Authorization: `Bearer ${accessToken}`
21447
+ },
21448
+ body: JSON.stringify({
21449
+ type: "renew-utxo",
21450
+ params: {
21451
+ userAddress: params.userAddress,
21452
+ txIdVouts: params.txIdVouts
21453
+ }
21454
+ })
21455
+ });
21456
+ if (!res.ok) {
21457
+ const err = await res.json();
21458
+ throw new Error(err.message || "Failed to build renew-utxo transaction");
21459
+ }
21460
+ return res.json().then((r) => r.data);
21461
+ }
21462
+ /**
21463
+ * Sign and broadcast a renew-utxo transaction via the Radfi API.
21464
+ * The user signs the PSBT first, then Radfi co-signs and broadcasts.
21465
+ */
21466
+ async signAndBroadcastRenewUtxo(params, accessToken) {
21467
+ const res = await this.request("/transactions/sign", {
21468
+ method: "POST",
21469
+ headers: {
21470
+ Authorization: `Bearer ${accessToken}`
21471
+ },
21472
+ body: JSON.stringify({
21473
+ type: "renew-utxo",
21474
+ params
21475
+ })
21476
+ });
21477
+ if (!res.ok) {
21478
+ const err = await res.json();
21479
+ throw new Error(err.message || "Failed to sign and broadcast renew-utxo transaction");
21480
+ }
21481
+ return res.json().then((r) => r.data.txId);
21482
+ }
21483
+ async request(endpoint, options) {
21484
+ return fetch(`${this.config.url}${endpoint}`, {
21485
+ ...options,
21486
+ headers: {
21487
+ "Content-Type": "application/json",
21488
+ ...options?.headers || {}
21489
+ }
21490
+ });
21491
+ }
21492
+ };
21493
+ bitcoin__namespace.initEccLib(ecc__namespace);
21494
+ var BITCOIN_DEFAULT_FEE_RATE = 3;
21495
+ var DUST_THRESHOLD = 546;
21496
+ function normalizePsbtToBase64(signedPsbt) {
21497
+ const isHex = /^[0-9a-fA-F]+$/.test(signedPsbt);
21498
+ return isHex ? Buffer.from(signedPsbt, "hex").toString("base64") : signedPsbt;
21499
+ }
21500
+ var BitcoinBaseSpokeProvider = class _BitcoinBaseSpokeProvider {
21501
+ rpcUrl;
21502
+ network;
21503
+ chainConfig;
21504
+ radfi;
21505
+ walletMode;
21506
+ radfiAccessToken = "";
21507
+ constructor(config, radfiConfig, walletMode = "USER", rpcURL) {
21508
+ this.chainConfig = config;
21509
+ this.rpcUrl = rpcURL ?? config.rpcUrl;
21510
+ this.network = config.network === "TESTNET" ? bitcoin__namespace.networks.testnet : bitcoin__namespace.networks.bitcoin;
21511
+ this.radfi = new RadfiProvider(radfiConfig);
21512
+ this.walletMode = walletMode;
21513
+ }
21514
+ setRadfiAccessToken(token) {
21515
+ this.radfiAccessToken = token;
21516
+ }
21517
+ /**
21518
+ * Get current fee estimates
21519
+ */
21520
+ async getFeeEstimate(targetBlocks = 6) {
21521
+ try {
21522
+ const response = await fetch(`${this.rpcUrl}/fee-estimates`);
21523
+ if (!response.ok) {
21524
+ return BITCOIN_DEFAULT_FEE_RATE;
21525
+ }
21526
+ const feeEstimates = await response.json();
21527
+ return feeEstimates[targetBlocks] ?? BITCOIN_DEFAULT_FEE_RATE;
21528
+ } catch {
21529
+ return BITCOIN_DEFAULT_FEE_RATE;
21530
+ }
21531
+ }
21532
+ static async getBalance(tokenAddress, provider) {
21533
+ const walletAddress = await provider.walletProvider.getWalletAddress();
21534
+ if (!tokenAddress || tokenAddress === "0x" || tokenAddress === "BTC") {
21535
+ const utxos = await provider.fetchUTXOs(walletAddress);
21536
+ const totalBalance = utxos.reduce((sum, utxo) => sum + utxo.value, 0);
21537
+ return BigInt(totalBalance);
21538
+ }
21539
+ throw new Error("Token balance queries not yet implemented for non-BTC assets");
21540
+ }
21541
+ async fetchScriptPubKey(utxo, provider) {
21542
+ const txHex = await provider.fetchRawTransaction(utxo.txid);
21543
+ const tx = bitcoin__namespace.Transaction.fromHex(txHex);
21544
+ const out = tx.outs[utxo.vout];
21545
+ if (!out) {
21546
+ throw new Error(`UTXO not found: ${utxo.txid}:${utxo.vout}`);
21547
+ }
21548
+ return out.script.toString("hex");
21549
+ }
21550
+ /**
21551
+ * Build a priority Bitcoin transaction with proper fee calculation
21552
+ */
21553
+ static async buildBitcoinTransaction(utxos, outputs, changeAddress, provider, feeRate) {
21554
+ const psbt = new bitcoin__namespace.Psbt({ network: provider.network });
21555
+ const effectiveFeeRate = feeRate ?? await provider.getFeeEstimate();
21556
+ let inputSum = 0;
21557
+ const outputSum = outputs.reduce((sum, o) => sum + o.value, 0);
21558
+ for (const utxo of utxos) {
21559
+ if (!utxo.status.confirmed) continue;
21560
+ const scriptPubKey = await provider.fetchScriptPubKey(utxo, provider);
21561
+ const isTaproot = scriptPubKey.startsWith("51");
21562
+ const isSegwitV0 = scriptPubKey.startsWith("00");
21563
+ if (isTaproot) {
21564
+ if (!provider.walletProvider.getPublicKey) {
21565
+ throw new Error("Missing public key for P2TR input");
21566
+ }
21567
+ const tapInternalKey = await provider.walletProvider.getPublicKey();
21568
+ psbt.addInput({
21569
+ hash: utxo.txid,
21570
+ index: utxo.vout,
21571
+ witnessUtxo: {
21572
+ script: Buffer.from(scriptPubKey, "hex"),
21573
+ value: utxo.value
21574
+ },
21575
+ tapInternalKey: Buffer.from(tapInternalKey, "hex")
21576
+ });
21577
+ } else if (isSegwitV0) {
21578
+ psbt.addInput({
21579
+ hash: utxo.txid,
21580
+ index: utxo.vout,
21581
+ witnessUtxo: {
21582
+ script: Buffer.from(scriptPubKey, "hex"),
21583
+ value: utxo.value
21584
+ }
21585
+ });
21586
+ } else {
21587
+ const txHex = await provider.fetchRawTransaction(utxo.txid);
21588
+ psbt.addInput({
21589
+ hash: utxo.txid,
21590
+ index: utxo.vout,
21591
+ nonWitnessUtxo: Buffer.from(txHex, "hex")
21592
+ });
21593
+ }
21594
+ inputSum += utxo.value;
21595
+ const estimatedSize = provider.estimateTxSize(psbt.inputCount, outputs.length);
21596
+ const estimatedFee = Math.ceil(effectiveFeeRate * estimatedSize);
21597
+ if (inputSum >= outputSum + estimatedFee + DUST_THRESHOLD) {
21598
+ break;
21599
+ }
21600
+ }
21601
+ for (const output of outputs) {
21602
+ psbt.addOutput({
21603
+ address: output.address,
21604
+ value: output.value
21605
+ });
21606
+ }
21607
+ const sizeWithChange = provider.estimateTxSize(psbt.inputCount, outputs.length + 1);
21608
+ const sizeWithoutChange = provider.estimateTxSize(psbt.inputCount, outputs.length);
21609
+ const feeWithChange = Math.ceil(effectiveFeeRate * sizeWithChange);
21610
+ const feeWithoutChange = Math.ceil(effectiveFeeRate * sizeWithoutChange);
21611
+ let change = inputSum - outputSum - feeWithChange;
21612
+ if (change < 0) {
21613
+ const confirmedCount = utxos.filter((u) => u.status.confirmed).length;
21614
+ const unconfirmedCount = utxos.length - confirmedCount;
21615
+ const hint = unconfirmedCount > 0 ? ` (${unconfirmedCount} unconfirmed UTXO(s) skipped \u2014 wait for confirmation)` : "";
21616
+ throw new Error(`Insufficient funds. Need ${outputSum + feeWithChange} satoshis, have ${inputSum}${hint}`);
21617
+ }
21618
+ if (change > DUST_THRESHOLD) {
21619
+ psbt.addOutput({
21620
+ address: changeAddress,
21621
+ value: change
21622
+ });
21623
+ } else {
21624
+ const finalFee = feeWithoutChange;
21625
+ change = inputSum - outputSum - finalFee;
21626
+ if (change < 0) {
21627
+ throw new Error(`Insufficient funds after dust handling. Need ${outputSum + finalFee}`);
21628
+ }
21629
+ }
21630
+ return psbt;
21631
+ }
21632
+ /**
21633
+ * Deposit operation - transfer BTC to the asset manager
21634
+ */
21635
+ static async deposit(token, amount, data, provider, raw, accessToken = "") {
21636
+ try {
21637
+ const walletAddress = await provider.walletProvider.getWalletAddress();
21638
+ const returnRawTx = (psbtBase64) => ({
21639
+ from: walletAddress,
21640
+ to: provider.chainConfig.addresses.assetManager,
21641
+ value: amount,
21642
+ data: psbtBase64
21643
+ });
21644
+ if (provider.walletMode === "TRADING") {
21645
+ const tokenId = Object.values(provider.chainConfig.supportedTokens).find((t) => t.address === token)?.address;
21646
+ if (!tokenId) {
21647
+ throw new Error(`Unsupported token: ${token}`);
21648
+ }
21649
+ data = data.startsWith("0x") ? data.slice(2) : data;
21650
+ data = data.length === 64 ? data : viem.keccak256(`0x${data}`).slice(2);
21651
+ accessToken = accessToken || provider.radfiAccessToken;
21652
+ const withdrawTx = await provider.radfi.createWithdrawTransaction(
21653
+ {
21654
+ token: tokenId,
21655
+ amount,
21656
+ recipient: provider.chainConfig.addresses.assetManager,
21657
+ userAddress: walletAddress,
21658
+ data
21659
+ },
21660
+ accessToken
21661
+ );
21662
+ if (raw || isBitcoinRawSpokeProvider(provider)) {
21663
+ return returnRawTx(withdrawTx.base64Psbt);
21664
+ }
21665
+ const signedTx = await provider.walletProvider.signTransaction(withdrawTx.base64Psbt, false);
21666
+ const signedBase64Tx = normalizePsbtToBase64(signedTx);
21667
+ return await provider.radfi.requestRadfiSignature(
21668
+ {
21669
+ userAddress: walletAddress,
21670
+ signedBase64Tx
21671
+ },
21672
+ accessToken
21673
+ );
21674
+ }
21675
+ const utxos = await provider.fetchUTXOs(walletAddress);
21676
+ if (!utxos?.length) {
21677
+ throw new Error("No UTXOs available for deposit");
21678
+ }
21679
+ const depositPsbt = await _BitcoinBaseSpokeProvider.buildDepositPsbt(
21680
+ walletAddress,
21681
+ token,
21682
+ amount,
21683
+ data,
21684
+ utxos,
21685
+ provider
21686
+ );
21687
+ if (raw || isBitcoinRawSpokeProvider(provider)) {
21688
+ return returnRawTx(depositPsbt.toBase64());
21689
+ }
21690
+ return await provider.signAndBroadcastTransaction(depositPsbt);
21691
+ } catch (error) {
21692
+ console.error("Error during deposit:", error);
21693
+ throw error;
21694
+ }
21695
+ }
21696
+ /**
21697
+ * Build deposit PSBT with embedded cross-chain data
21698
+ */
21699
+ static async buildDepositPsbt(walletAddress, token, amount, data, utxos, provider) {
21700
+ const assetManagerAddress = provider.chainConfig.addresses.assetManager;
21701
+ if (token.toLocaleLowerCase() === "btc") {
21702
+ const outputs = [
21703
+ {
21704
+ address: assetManagerAddress,
21705
+ value: Number(amount)
21706
+ }
21707
+ ];
21708
+ const psbt = await _BitcoinBaseSpokeProvider.buildBitcoinTransaction(utxos, outputs, walletAddress, provider);
21709
+ const OP_RADFI_SODAX_DATA = 49;
21710
+ const payload = Buffer.concat([Buffer.from([OP_RADFI_SODAX_DATA]), Buffer.from(data.slice(2), "hex")]);
21711
+ const OP_RETURN = bitcoin__namespace.opcodes.OP_RETURN;
21712
+ const OP_12 = bitcoin__namespace.opcodes.OP_12;
21713
+ if (OP_RETURN === void 0 || OP_12 === void 0) {
21714
+ throw new Error("bitcoinjs-lib opcodes OP_RETURN or OP_12 are undefined");
21715
+ }
21716
+ const script2 = bitcoin__namespace.script.compile([OP_RETURN, OP_12, payload]);
21717
+ psbt.addOutput({
21718
+ script: script2,
21719
+ value: 0
21720
+ });
21721
+ return psbt;
21722
+ }
21723
+ throw new Error(`Non-BTC token deposits not yet implemented (token: ${token})`);
21724
+ }
21725
+ /**
21726
+ * Fetch UTXOs for an address
21727
+ */
21728
+ async fetchUTXOs(address) {
21729
+ const response = await fetch(`${this.rpcUrl}/address/${address}/utxo`);
21730
+ if (!response.ok) {
21731
+ throw new Error(`Failed to fetch UTXOs: ${response.statusText}`);
21732
+ }
21733
+ return await response.json();
21734
+ }
21735
+ /**
21736
+ * Fetch raw transaction hex
21737
+ */
21738
+ async fetchRawTransaction(txid) {
21739
+ const response = await fetch(`${this.rpcUrl}/tx/${txid}/hex`);
21740
+ if (!response.ok) {
21741
+ throw new Error(`Failed to fetch transaction: ${response.statusText}`);
21742
+ }
21743
+ return await response.text();
21744
+ }
21745
+ /**
21746
+ * Estimate transaction size in vbytes
21747
+ */
21748
+ estimateTxSize(inputCount, outputCount) {
21749
+ return Math.ceil(10.5 + 44 + inputCount * 68 + outputCount * 31);
21750
+ }
21751
+ getAddressType(address) {
21752
+ return detectBitcoinAddressType(address);
21753
+ }
21754
+ encodePayloadToBytes(payload) {
21755
+ const ordered = {
21756
+ src_address: payload.src_address.toLowerCase(),
21757
+ data: payload.data.toLowerCase(),
21758
+ src_chain_id: payload.src_chain_id,
21759
+ dst_chain_id: payload.dst_chain_id,
21760
+ wallet_used: payload.wallet_used,
21761
+ timestamp: payload.timestamp,
21762
+ address_type: payload.address_type
21763
+ };
21764
+ const json = JSON.stringify(ordered);
21765
+ return json;
21766
+ }
21767
+ static async encodeWithdrawalData(dstChainId, data, provider, raw) {
21768
+ let srcAddress = await provider.walletProvider.getWalletAddress();
21769
+ const addressType = provider.getAddressType(srcAddress);
21770
+ if (provider.walletMode === "TRADING") {
21771
+ srcAddress = await provider.radfi.getTradingWallet(srcAddress).then((res) => res.tradingAddress).catch(() => srcAddress);
21772
+ }
21773
+ const payload = {
21774
+ src_address: srcAddress,
21775
+ data,
21776
+ src_chain_id: Number(getIntentRelayChainId(provider.chainConfig.chain.id)),
21777
+ dst_chain_id: Number(getIntentRelayChainId(dstChainId)),
21778
+ wallet_used: provider.walletMode,
21779
+ timestamp: Date.now(),
21780
+ address_type: addressType
21781
+ };
21782
+ const orderedPayload = provider.encodePayloadToBytes(payload);
21783
+ const onDemandWithdraw = {
21784
+ payload_hex: Buffer.from(orderedPayload).toString("hex"),
21785
+ signature: void 0
21786
+ };
21787
+ if (raw || isBitcoinRawSpokeProvider(provider)) {
21788
+ return JSON.stringify(onDemandWithdraw);
21789
+ }
21790
+ const signature = await provider.walletProvider.signEcdsaMessage(orderedPayload);
21791
+ onDemandWithdraw.signature = signature;
21792
+ return JSON.stringify(onDemandWithdraw);
21793
+ }
21794
+ };
21795
+ var BitcoinRawSpokeProvider = class extends BitcoinBaseSpokeProvider {
21796
+ walletProvider;
21797
+ raw = true;
21798
+ constructor(walletAddress, publicKey, chainConfig, radfiConfig, walletMode = "USER", rpcUrl) {
21799
+ super(chainConfig, radfiConfig, walletMode, rpcUrl);
21800
+ this.walletProvider = {
21801
+ getWalletAddress: async () => walletAddress,
21802
+ getPublicKey: async () => publicKey
21803
+ };
21804
+ }
21805
+ };
21806
+ var BitcoinSpokeProvider = class extends BitcoinBaseSpokeProvider {
21807
+ walletProvider;
21808
+ constructor(walletProvider, chainConfig, radfiConfig, walletMode = "USER", rpcUrl) {
21809
+ super(chainConfig, radfiConfig, walletMode, rpcUrl);
21810
+ this.walletProvider = walletProvider;
21811
+ }
21812
+ /**
21813
+ * Authenticate with Radfi: BIP322-sign a login message, then call the Radfi API.
21814
+ * Returns accessToken, refreshToken, and tradingAddress.
21815
+ */
21816
+ async authenticateWithWallet(cachedPublicKey) {
21817
+ const address = await this.walletProvider.getWalletAddress();
21818
+ let publicKey = cachedPublicKey;
21819
+ if (!publicKey) {
21820
+ if (!this.walletProvider.getPublicKey) {
21821
+ throw new Error("Wallet provider does not support getPublicKey");
21822
+ }
21823
+ publicKey = await this.walletProvider.getPublicKey();
21824
+ }
21825
+ if (!publicKey) {
21826
+ throw new Error("Failed to retrieve public key from wallet. Please unlock your wallet and try again.");
21827
+ }
21828
+ const message = `Login to Radfi via Sodax: ${Date.now()}`;
21829
+ const signature = await this.walletProvider.signBip322Message(message);
21830
+ const result = await this.radfi.authenticate({ message, signature, address, publicKey });
21831
+ this.setRadfiAccessToken(result.accessToken);
21832
+ return { ...result, publicKey };
21833
+ }
21834
+ /**
21835
+ * Ensure a valid Radfi access token is set on this provider.
21836
+ * No-op if a token is already present.
21837
+ */
21838
+ async ensureRadfiAccessToken() {
21839
+ if (this.radfiAccessToken) return;
21840
+ await this.authenticateWithWallet();
21841
+ }
21842
+ /**
21843
+ * Sign and broadcast a Bitcoin transaction
21844
+ */
21845
+ async signAndBroadcastTransaction(psbt) {
21846
+ const psbtBase64 = typeof psbt === "string" ? psbt : psbt.toBase64();
21847
+ const signedPsbtHex = await this.walletProvider.signTransaction(psbtBase64);
21848
+ const txHash = await this.broadcastTransaction(signedPsbtHex);
21849
+ return txHash;
21850
+ }
21851
+ /**
21852
+ * Broadcast a signed transaction
21853
+ */
21854
+ async broadcastTransaction(txHex) {
21855
+ const response = await fetch(`${this.rpcUrl}/tx`, {
21856
+ method: "POST",
21857
+ body: txHex
21858
+ });
21859
+ if (!response.ok) {
21860
+ const errorText = await response.text();
21861
+ throw new Error(`Failed to broadcast transaction: ${errorText}`);
21862
+ }
21863
+ return await response.text();
21864
+ }
21865
+ };
21866
+
21867
+ // src/shared/services/spoke/BitcoinSpokeService.ts
21868
+ var BitcoinSpokeService = class _BitcoinSpokeService {
21869
+ constructor() {
21870
+ }
21871
+ /**
21872
+ * Estimate transaction fee for a Bitcoin transaction
21873
+ *
21874
+ * @param {Hex} rawTx - The raw transaction parameters
21875
+ * @param {BitcoinSpokeProviderType} spokeProvider - The Bitcoin spoke provider
21876
+ * @returns {Promise<bigint>} Estimated fee in satoshis
21877
+ */
21878
+ static async estimateGas(rawTx, spokeProvider) {
21879
+ const txBytes = Buffer.from(rawTx, "hex");
21880
+ const vsize = Math.ceil(txBytes.length);
21881
+ const feeRate = await spokeProvider.getFeeEstimate();
21882
+ const feeRateBigInt = typeof feeRate === "bigint" ? feeRate : BigInt(Math.ceil(feeRate));
21883
+ return BigInt(vsize) * feeRateBigInt;
21884
+ }
21885
+ /**
21886
+ * Deposit tokens to the spoke chain and bridge to hub
21887
+ *
21888
+ * @param {BitcoinSpokeDepositParams} params - Deposit parameters
21889
+ * @param {BitcoinSpokeProviderType} spokeProvider - The Bitcoin spoke provider
21890
+ * @param {EvmHubProvider} EvmHubProvider - The hub chain provider
21891
+ * @param {boolean} raw - Whether to return raw PSBT or transaction hash
21892
+ * @returns {Promise<TxReturnType<BitcoinSpokeProviderType, R>>} Transaction hash or raw PSBT
21893
+ */
21894
+ static async deposit(params, spokeProvider, raw) {
21895
+ return _BitcoinSpokeService.transfer(
21896
+ {
21897
+ token: params.token,
21898
+ amount: params.amount,
21899
+ data: params.data ?? "0x",
21900
+ accessToken: params.accessToken
21901
+ },
21902
+ spokeProvider,
21903
+ raw
21904
+ );
21905
+ }
21906
+ /**
21907
+ * Get the balance of deposited tokens in the asset manager
21908
+ *
21909
+ * @param {string} token - Token identifier ('BTC' for native Bitcoin)
21910
+ * @param {BitcoinSpokeProviderType} spokeProvider - The Bitcoin spoke provider
21911
+ * @returns {Promise<bigint>} Balance in satoshis
21912
+ */
21913
+ static async getDeposit(token, spokeProvider) {
21914
+ const assetManagerAddress = spokeProvider.chainConfig.addresses.assetManager;
21915
+ const utxos = await spokeProvider.fetchUTXOs(assetManagerAddress);
21916
+ const totalBalance = utxos.reduce((sum, utxo) => sum + utxo.value, 0);
21917
+ return BigInt(totalBalance);
21918
+ }
21919
+ /**
21920
+ * Generate simulation parameters for deposit
21921
+ *
21922
+ * @param {BitcoinSpokeDepositParams} params - Deposit parameters
21923
+ * @param {BitcoinSpokeProviderType} spokeProvider - The Bitcoin spoke provider
21924
+ * @param {EvmHubProvider} EvmHubProvider - The hub chain provider
21925
+ * @returns {Promise<DepositSimulationParams>} Simulation parameters
21926
+ */
21927
+ static async getSimulateDepositParams(params, spokeProvider, EvmHubProvider2) {
21928
+ const to = params.to ?? await EvmWalletAbstraction.getUserHubWalletAddress(
21929
+ spokeProvider.chainConfig.chain.id,
21930
+ encodeAddress(spokeProvider.chainConfig.chain.id, params.from),
21931
+ EvmHubProvider2
21932
+ );
21933
+ const tokenEntry = Object.values(spokeProvider.chainConfig.supportedTokens).find((t) => t.address === params.token);
21934
+ const token = tokenEntry?.address ?? params.token;
21935
+ return {
21936
+ spokeChainID: spokeProvider.chainConfig.chain.id,
21937
+ token: encodeAddress(spokeProvider.chainConfig.chain.id, token),
21938
+ from: encodeAddress(spokeProvider.chainConfig.chain.id, params.from),
21939
+ to,
21940
+ amount: params.amount,
21941
+ data: params.data,
21942
+ srcAddress: encodeAddress(spokeProvider.chainConfig.chain.id, spokeProvider.chainConfig.addresses.assetManager)
21943
+ };
21944
+ }
21945
+ /**
21946
+ * Fund the Radfi trading wallet by sending BTC from the user's personal wallet
21947
+ *
21948
+ * @param {bigint} amount - Amount in satoshis to send
21949
+ * @param {BitcoinSpokeProvider} spokeProvider - The Bitcoin spoke provider (must have signing capability)
21950
+ * @returns {Promise<string>} Transaction ID of the funding transaction
21951
+ */
21952
+ static async fundTradingWallet(amount, spokeProvider) {
21953
+ const walletAddress = await spokeProvider.walletProvider.getWalletAddress();
21954
+ const { tradingAddress } = await spokeProvider.radfi.getTradingWallet(walletAddress);
21955
+ return spokeProvider.walletProvider.sendBitcoin(tradingAddress, amount);
21956
+ }
21957
+ /**
21958
+ * Call a contract on the hub chain from Bitcoin spoke
21959
+ *
21960
+ * @param {HubAddress} from - The hub wallet address
21961
+ * @param {Hex} payload - The payload to send
21962
+ * @param {BitcoinSpokeProviderType} spokeProvider - The Bitcoin spoke provider
21963
+ * @param {EvmHubProvider} EvmHubProvider - The hub chain provider
21964
+ * @param {boolean} raw - Whether to return raw PSBT or transaction hash
21965
+ * @returns {Promise<TxReturnType<BitcoinSpokeProviderType, R>>} Stringified JSON for payload and signature
21966
+ */
21967
+ static async callWallet(from, payload, spokeProvider, EvmHubProvider2, raw) {
21968
+ return _BitcoinSpokeService.call(EvmHubProvider2.chainConfig.chain.id, from, payload, spokeProvider, raw);
21969
+ }
21970
+ /**
21971
+ * Transfer tokens to the hub chain
21972
+ *
21973
+ * @param {BitcoinTransferToHubParams} params - Transfer parameters
21974
+ * @param {BitcoinSpokeProviderType} spokeProvider - The Bitcoin spoke provider
21975
+ * @param {boolean} raw - Whether to return raw PSBT or transaction hash
21976
+ * @returns {Promise<TxReturnType<BitcoinSpokeProviderType, R>>} Transaction hash or raw PSBT
21977
+ */
21978
+ static async transfer({ token, amount, data = "0x", accessToken }, spokeProvider, raw) {
21979
+ return await BitcoinBaseSpokeProvider.deposit(token, amount, data, spokeProvider, raw, accessToken);
21980
+ }
21981
+ /**
21982
+ * Send a message to the hub chain
21983
+ *
21984
+ * @param {HubChainId} dstChainId - Destination chain ID
21985
+ * @param {HubAddress} dstAddress - Destination address on hub
21986
+ * @param {Hex} payload - Message payload
21987
+ * @param {BitcoinSpokeProviderType} spokeProvider - The Bitcoin spoke provider
21988
+ * @param {boolean} raw - Whether to return raw PSBT or transaction hash
21989
+ * @returns {Promise<TxReturnType<BitcoinSpokeProviderType, R>>} Transaction hash or raw PSBT
21990
+ */
21991
+ static async call(dstChainId, dstAddress, payload, spokeProvider, raw) {
21992
+ return await BitcoinBaseSpokeProvider.encodeWithdrawalData(
21993
+ dstChainId,
21994
+ payload,
21995
+ spokeProvider,
21996
+ raw
21997
+ );
21998
+ }
21999
+ };
21209
22000
  var NearSpokeService = class _NearSpokeService {
21210
22001
  constructor() {
21211
22002
  }
@@ -21564,6 +22355,14 @@ var SpokeService = class _SpokeService {
21564
22355
  raw
21565
22356
  );
21566
22357
  }
22358
+ if (isBitcoinSpokeProviderType(spokeProvider)) {
22359
+ await _SpokeService.verifyDepositSimulation(params, spokeProvider, hubProvider, skipSimulation);
22360
+ return BitcoinSpokeService.deposit(
22361
+ params,
22362
+ spokeProvider,
22363
+ raw
22364
+ );
22365
+ }
21567
22366
  if (isNearSpokeProviderType(spokeProvider)) {
21568
22367
  await _SpokeService.verifyDepositSimulation(params, spokeProvider, hubProvider, skipSimulation);
21569
22368
  return NearSpokeService.deposit(
@@ -21618,6 +22417,13 @@ var SpokeService = class _SpokeService {
21618
22417
  hubProvider
21619
22418
  );
21620
22419
  }
22420
+ if (isBitcoinSpokeProviderType(spokeProvider)) {
22421
+ return BitcoinSpokeService.getSimulateDepositParams(
22422
+ params,
22423
+ spokeProvider,
22424
+ hubProvider
22425
+ );
22426
+ }
21621
22427
  if (isNearSpokeProviderType(spokeProvider)) {
21622
22428
  return NearSpokeService.getSimulateDepositParams(
21623
22429
  params,
@@ -21664,6 +22470,9 @@ var SpokeService = class _SpokeService {
21664
22470
  if (isSonicSpokeProviderType(spokeProvider)) {
21665
22471
  return SonicSpokeService.getDeposit(token, spokeProvider);
21666
22472
  }
22473
+ if (isBitcoinSpokeProviderType(spokeProvider)) {
22474
+ return BitcoinSpokeService.getDeposit(token, spokeProvider);
22475
+ }
21667
22476
  if (isNearSpokeProviderType(spokeProvider)) {
21668
22477
  return NearSpokeService.getDeposit(token, spokeProvider);
21669
22478
  }
@@ -21681,15 +22490,24 @@ var SpokeService = class _SpokeService {
21681
22490
  if (isSonicSpokeProviderType(spokeProvider)) {
21682
22491
  return await SonicSpokeService.callWallet(payload, spokeProvider, raw);
21683
22492
  }
22493
+ let srcAddress = encodeAddress(
22494
+ spokeProvider.chainConfig.chain.id,
22495
+ await spokeProvider.walletProvider.getWalletAddress()
22496
+ );
22497
+ if (isBitcoinSpokeProvider(spokeProvider)) {
22498
+ if (spokeProvider.walletMode === "TRADING") {
22499
+ const tradingWalletAddress = await spokeProvider.radfi.getTradingWallet(
22500
+ await spokeProvider.walletProvider.getWalletAddress()
22501
+ );
22502
+ srcAddress = encodeAddress(spokeProvider.chainConfig.chain.id, tradingWalletAddress.tradingAddress);
22503
+ }
22504
+ }
21684
22505
  if (!skipSimulation) {
21685
22506
  const result = await _SpokeService.simulateRecvMessage(
21686
22507
  {
21687
22508
  target: from,
21688
22509
  srcChainId: getIntentRelayChainId(spokeProvider.chainConfig.chain.id),
21689
- srcAddress: encodeAddress(
21690
- spokeProvider.chainConfig.chain.id,
21691
- await spokeProvider.walletProvider.getWalletAddress()
21692
- ),
22510
+ srcAddress,
21693
22511
  payload
21694
22512
  },
21695
22513
  hubProvider
@@ -21740,6 +22558,16 @@ var SpokeService = class _SpokeService {
21740
22558
  raw
21741
22559
  );
21742
22560
  }
22561
+ if (isBitcoinSpokeProviderType(spokeProvider)) {
22562
+ await _SpokeService.verifySimulation(from, payload, spokeProvider, hubProvider, skipSimulation);
22563
+ return await BitcoinSpokeService.callWallet(
22564
+ from,
22565
+ payload,
22566
+ spokeProvider,
22567
+ hubProvider,
22568
+ raw
22569
+ );
22570
+ }
21743
22571
  if (isNearSpokeProviderType(spokeProvider)) {
21744
22572
  await _SpokeService.verifySimulation(from, payload, spokeProvider, hubProvider, skipSimulation);
21745
22573
  return await NearSpokeService.callWallet(from, payload, spokeProvider, hubProvider, raw);
@@ -21748,14 +22576,26 @@ var SpokeService = class _SpokeService {
21748
22576
  }
21749
22577
  static async verifySimulation(from, payload, spokeProvider, hubProvider, skipSimulation) {
21750
22578
  if (!skipSimulation) {
22579
+ let srcAddress = encodeAddress(
22580
+ spokeProvider.chainConfig.chain.id,
22581
+ await spokeProvider.walletProvider.getWalletAddress()
22582
+ );
22583
+ if (isBitcoinSpokeProvider(spokeProvider)) {
22584
+ if (spokeProvider.walletMode === "TRADING") {
22585
+ const tradingWalletAddress = await spokeProvider.radfi.getTradingWallet(
22586
+ await spokeProvider.walletProvider.getWalletAddress()
22587
+ );
22588
+ srcAddress = encodeAddress(
22589
+ spokeProvider.chainConfig.chain.id,
22590
+ tradingWalletAddress.tradingAddress
22591
+ );
22592
+ }
22593
+ }
21751
22594
  const result = await _SpokeService.simulateRecvMessage(
21752
22595
  {
21753
22596
  target: from,
21754
22597
  srcChainId: getIntentRelayChainId(spokeProvider.chainConfig.chain.id),
21755
- srcAddress: encodeAddress(
21756
- spokeProvider.chainConfig.chain.id,
21757
- await spokeProvider.walletProvider.getWalletAddress()
21758
- ),
22598
+ srcAddress,
21759
22599
  payload
21760
22600
  },
21761
22601
  hubProvider
@@ -22189,7 +23029,7 @@ var SwapService = class {
22189
23029
  let dstIntentTxHash;
22190
23030
  if (spokeProvider.chainConfig.chain.id !== this.hubProvider.chainConfig.chain.id) {
22191
23031
  const intentRelayChainId = getIntentRelayChainId(params.srcChain).toString();
22192
- const submitPayload = params.srcChain === SOLANA_MAINNET_CHAIN_ID && data ? {
23032
+ const submitPayload = (params.srcChain === SOLANA_MAINNET_CHAIN_ID || params.srcChain === BITCOIN_MAINNET_CHAIN_ID) && data ? {
22193
23033
  action: "submit",
22194
23034
  params: {
22195
23035
  chain_id: intentRelayChainId,
@@ -22514,17 +23354,51 @@ var SwapService = class {
22514
23354
  this.configService.isValidSpokeChainId(params.dstChain),
22515
23355
  `Invalid spoke chain (params.dstChain): ${params.dstChain}`
22516
23356
  );
23357
+ if (params.dstChain === BITCOIN_MAINNET_CHAIN_ID && params.outputToken === "BTC") {
23358
+ invariant6__default.default(
23359
+ params.minOutputAmount >= 546n,
23360
+ `Invalid minOutputAmount (params.minOutputAmount): ${params.minOutputAmount}`
23361
+ );
23362
+ }
22517
23363
  try {
22518
- const walletAddress = await spokeProvider.walletProvider.getWalletAddress();
23364
+ console.log("[SwapService.createIntent] start", {
23365
+ srcChain: params.srcChain,
23366
+ dstChain: params.dstChain,
23367
+ inputToken: params.inputToken,
23368
+ inputAmount: params.inputAmount.toString()
23369
+ });
23370
+ let walletAddress = await spokeProvider.walletProvider.getWalletAddress();
23371
+ console.log("[SwapService.createIntent] walletAddress", walletAddress, "srcAddress", params.srcAddress);
22519
23372
  invariant6__default.default(
22520
23373
  params.srcAddress.toLowerCase() === walletAddress.toLowerCase(),
22521
23374
  "srcAddress must be the same as wallet address"
22522
23375
  );
23376
+ if (isBitcoinSpokeProvider(spokeProvider)) {
23377
+ console.log(
23378
+ "[SwapService.createIntent] Bitcoin detected, walletMode:",
23379
+ spokeProvider.walletMode,
23380
+ "hasToken:",
23381
+ !!spokeProvider.radfiAccessToken
23382
+ );
23383
+ await spokeProvider.ensureRadfiAccessToken();
23384
+ console.log(
23385
+ "[SwapService.createIntent] ensureRadfiAccessToken done, hasToken:",
23386
+ !!spokeProvider.radfiAccessToken
23387
+ );
23388
+ if (spokeProvider.walletMode === "TRADING") {
23389
+ const tradingWalletAddress = await spokeProvider.radfi.getTradingWallet(
23390
+ await spokeProvider.walletProvider.getWalletAddress()
23391
+ );
23392
+ console.log("[SwapService.createIntent] tradingWalletAddress", tradingWalletAddress);
23393
+ walletAddress = tradingWalletAddress.tradingAddress;
23394
+ }
23395
+ }
22523
23396
  const creatorHubWalletAddress = await deriveUserWalletAddress(
22524
23397
  this.hubProvider,
22525
23398
  spokeProvider.chainConfig.chain.id,
22526
23399
  walletAddress
22527
23400
  );
23401
+ console.log("[SwapService.createIntent] creatorHubWalletAddress", creatorHubWalletAddress);
22528
23402
  if (spokeProvider.chainConfig.chain.id === this.hubProvider.chainConfig.chain.id && isSonicSpokeProviderType(spokeProvider)) {
22529
23403
  const [txResult, intent, feeAmount, data] = await SonicSpokeService.createSwapIntent(
22530
23404
  params,
@@ -22555,6 +23429,11 @@ var SwapService = class {
22555
23429
  this.configService,
22556
23430
  fee
22557
23431
  );
23432
+ console.log("[SwapService.createIntent] intent data constructed", {
23433
+ data,
23434
+ intentId: intent.intentId?.toString()
23435
+ });
23436
+ console.log("[SwapService.createIntent] calling SpokeService.deposit...");
22558
23437
  const txResult = await SpokeService.deposit(
22559
23438
  {
22560
23439
  from: walletAddress,
@@ -22568,12 +23447,14 @@ var SwapService = class {
22568
23447
  raw,
22569
23448
  skipSimulation
22570
23449
  );
23450
+ console.log("[SwapService.createIntent] SpokeService.deposit done, txResult:", txResult);
22571
23451
  return {
22572
23452
  ok: true,
22573
23453
  value: [txResult, { ...intent, feeAmount }, data]
22574
23454
  };
22575
23455
  }
22576
23456
  } catch (error) {
23457
+ console.error("[SwapService.createIntent] FAILED", error);
22577
23458
  return {
22578
23459
  ok: false,
22579
23460
  error: {
@@ -25632,9 +26513,15 @@ function isNearSpokeProvider(value) {
25632
26513
  function isStellarSpokeProviderType(value) {
25633
26514
  return typeof value === "object" && value !== null && (isStellarSpokeProvider(value) || isStellarRawSpokeProvider(value));
25634
26515
  }
26516
+ function isBitcoinSpokeProviderType(value) {
26517
+ return typeof value === "object" && value !== null && (isBitcoinSpokeProvider(value) || isBitcoinRawSpokeProvider(value));
26518
+ }
25635
26519
  function isStellarSpokeProvider(value) {
25636
26520
  return typeof value === "object" && value !== null && value instanceof StellarSpokeProvider && !("raw" in value) && value.chainConfig.chain.type === "STELLAR";
25637
26521
  }
26522
+ function isBitcoinSpokeProvider(value) {
26523
+ return typeof value === "object" && value !== null && value instanceof BitcoinSpokeProvider && !("raw" in value) && value.chainConfig.chain.type === "BITCOIN";
26524
+ }
25638
26525
  function isNearSpokeProviderType(value) {
25639
26526
  return typeof value === "object" && value !== null && (isNearSpokeProvider(value) || isNearRawSpokeProvider(value));
25640
26527
  }
@@ -25731,6 +26618,9 @@ function isSolanaRawSpokeProvider(value) {
25731
26618
  function isStellarRawSpokeProvider(value) {
25732
26619
  return isRawSpokeProvider(value) && value.chainConfig.chain.type === "STELLAR";
25733
26620
  }
26621
+ function isBitcoinRawSpokeProvider(value) {
26622
+ return isRawSpokeProvider(value) && value.chainConfig.chain.type === "BITCOIN";
26623
+ }
25734
26624
  function isIconRawSpokeProvider(value) {
25735
26625
  return isRawSpokeProvider(value) && value.chainConfig.chain.type === "ICON";
25736
26626
  }
@@ -25839,8 +26729,6 @@ function encodeAddress(spokeChainId, address) {
25839
26729
  case "0xa4b1.arbitrum":
25840
26730
  case "sonic":
25841
26731
  return address;
25842
- case "injective-1":
25843
- return viem.toHex(Buffer.from(address, "utf-8"));
25844
26732
  case "0x1.icon":
25845
26733
  return viem.toHex(Buffer.from(address.replace("cx", "01").replace("hx", "00") ?? "f8", "hex"));
25846
26734
  case "sui":
@@ -25849,7 +26737,9 @@ function encodeAddress(spokeChainId, address) {
25849
26737
  return viem.toHex(Buffer.from(new web3_js.PublicKey(address).toBytes()));
25850
26738
  case "stellar":
25851
26739
  return `0x${stellarSdk.Address.fromString(address).toScVal().toXDR("hex")}`;
26740
+ case "bitcoin":
25852
26741
  case "near":
26742
+ case "injective-1":
25853
26743
  return viem.toHex(Buffer.from(address, "utf-8"));
25854
26744
  default:
25855
26745
  return address;
@@ -26473,7 +27363,7 @@ var BridgeService = class {
26473
27363
  }
26474
27364
  const packetResult = await relayTxAndWaitPacket(
26475
27365
  txResult.value,
26476
- spokeProvider instanceof SolanaSpokeProvider ? txResult.data : void 0,
27366
+ spokeProvider instanceof SolanaSpokeProvider || spokeProvider instanceof BitcoinSpokeProvider ? txResult.data : void 0,
26477
27367
  spokeProvider,
26478
27368
  this.relayerApiEndpoint,
26479
27369
  timeout
@@ -26546,7 +27436,11 @@ var BridgeService = class {
26546
27436
  const dstAssetInfo = this.configService.getHubAssetInfo(params.dstChainId, params.dstAsset);
26547
27437
  invariant6__default.default(srcAssetInfo, `Unsupported spoke chain (${params.srcChainId}) token: ${params.srcAsset}`);
26548
27438
  invariant6__default.default(dstAssetInfo, `Unsupported spoke chain (${params.dstChainId}) token: ${params.dstAsset}`);
26549
- const walletAddress = await spokeProvider.walletProvider.getWalletAddress();
27439
+ let walletAddress = await spokeProvider.walletProvider.getWalletAddress();
27440
+ if (spokeProvider instanceof BitcoinSpokeProvider && spokeProvider.walletMode === "TRADING") {
27441
+ const tradingWallet = await spokeProvider.radfi.getTradingWallet(walletAddress);
27442
+ walletAddress = tradingWallet.tradingAddress;
27443
+ }
26550
27444
  const hubWallet = await WalletAbstractionService.getUserAbstractedWalletAddress(
26551
27445
  walletAddress,
26552
27446
  spokeProvider,
@@ -30080,11 +30974,16 @@ var BalnSwapService = class {
30080
30974
  exports.ARBITRUM_MAINNET_CHAIN_ID = ARBITRUM_MAINNET_CHAIN_ID;
30081
30975
  exports.AVALANCHE_MAINNET_CHAIN_ID = AVALANCHE_MAINNET_CHAIN_ID;
30082
30976
  exports.BASE_MAINNET_CHAIN_ID = BASE_MAINNET_CHAIN_ID;
30977
+ exports.BITCOIN_MAINNET_CHAIN_ID = BITCOIN_MAINNET_CHAIN_ID;
30083
30978
  exports.BSC_MAINNET_CHAIN_ID = BSC_MAINNET_CHAIN_ID;
30084
30979
  exports.BackendApiService = BackendApiService;
30085
30980
  exports.BalnSwapService = BalnSwapService;
30086
30981
  exports.BigIntToHex = BigIntToHex;
30087
30982
  exports.BigNumberZeroDecimal = BigNumberZeroDecimal;
30983
+ exports.BitcoinBaseSpokeProvider = BitcoinBaseSpokeProvider;
30984
+ exports.BitcoinRawSpokeProvider = BitcoinRawSpokeProvider;
30985
+ exports.BitcoinSpokeProvider = BitcoinSpokeProvider;
30986
+ exports.BitcoinSpokeService = BitcoinSpokeService;
30088
30987
  exports.BnUSDMigrationService = BnUSDMigrationService;
30089
30988
  exports.BridgeService = BridgeService;
30090
30989
  exports.CHAIN_IDS = CHAIN_IDS;
@@ -30161,6 +31060,7 @@ exports.ProtocolIntentsAbi = ProtocolIntentsAbi;
30161
31060
  exports.RAY = RAY;
30162
31061
  exports.RAY_DECIMALS = RAY_DECIMALS;
30163
31062
  exports.REDBELLY_MAINNET_CHAIN_ID = REDBELLY_MAINNET_CHAIN_ID;
31063
+ exports.RadfiProvider = RadfiProvider;
30164
31064
  exports.SECONDS_PER_YEAR = SECONDS_PER_YEAR;
30165
31065
  exports.SOLANA_MAINNET_CHAIN_ID = SOLANA_MAINNET_CHAIN_ID;
30166
31066
  exports.SONIC_MAINNET_CHAIN_ID = SONIC_MAINNET_CHAIN_ID;
@@ -30226,6 +31126,7 @@ exports.convertTransactionInstructionToRaw = convertTransactionInstructionToRaw;
30226
31126
  exports.defaultSharedConfig = defaultSharedConfig;
30227
31127
  exports.defaultSodaxConfig = defaultSodaxConfig;
30228
31128
  exports.deriveUserWalletAddress = deriveUserWalletAddress;
31129
+ exports.detectBitcoinAddressType = detectBitcoinAddressType;
30229
31130
  exports.encodeAddress = encodeAddress;
30230
31131
  exports.encodeContractCalls = encodeContractCalls;
30231
31132
  exports.erc20Abi = erc20Abi;
@@ -30264,6 +31165,9 @@ exports.hubChainConfig = hubChainConfig;
30264
31165
  exports.hyper = hyper;
30265
31166
  exports.isAddressString = isAddressString;
30266
31167
  exports.isBalnMigrateParams = isBalnMigrateParams;
31168
+ exports.isBitcoinRawSpokeProvider = isBitcoinRawSpokeProvider;
31169
+ exports.isBitcoinSpokeProvider = isBitcoinSpokeProvider;
31170
+ exports.isBitcoinSpokeProviderType = isBitcoinSpokeProviderType;
30267
31171
  exports.isConfiguredMoneyMarketConfig = isConfiguredMoneyMarketConfig;
30268
31172
  exports.isConfiguredSolverConfig = isConfiguredSolverConfig;
30269
31173
  exports.isCreateIntentAutoSwapError = isCreateIntentAutoSwapError;
@@ -30345,6 +31249,7 @@ exports.nativeToUSD = nativeToUSD;
30345
31249
  exports.newbnUSDSpokeChainIds = newbnUSDSpokeChainIds;
30346
31250
  exports.normalize = normalize;
30347
31251
  exports.normalizeBN = normalizeBN;
31252
+ exports.normalizePsbtToBase64 = normalizePsbtToBase64;
30348
31253
  exports.normalizedToUsd = normalizedToUsd;
30349
31254
  exports.parseToStroops = parseToStroops;
30350
31255
  exports.parseTokenArrayFromJson = parseTokenArrayFromJson;