@liquidium/client 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { encodeFunctionData, createPublicClient, http, isAddress, getAddress } from 'viem';
2
2
  import { mainnet } from 'viem/chains';
3
- import { HttpAgent, Actor } from '@icp-sdk/core/agent';
3
+ import { Actor, HttpAgent } from '@icp-sdk/core/agent';
4
4
  import { Principal } from '@icp-sdk/core/principal';
5
5
  import { encodeIcrcAccount } from '@dfinity/ledger-icrc';
6
6
  import { Principal as Principal$1 } from '@dfinity/principal';
7
+ import { validate, Network } from 'bitcoin-address-validation';
7
8
 
8
9
  // src/client.ts
9
10
 
@@ -57,7 +58,8 @@ function resolveCanisterIds(environment = DEFAULT_ENVIRONMENT, overrides) {
57
58
  }
58
59
  var DEFAULT_TIMEOUT_MS = 3e4;
59
60
  var CK_CANISTER_IDS = {
60
- btcMinter: "mqygn-kiaaa-aaaar-qaadq-cai"};
61
+ btcMinter: "mqygn-kiaaa-aaaar-qaadq-cai",
62
+ btcLedger: "mxzaz-hqaaa-aaaar-qaada-cai"};
61
63
 
62
64
  // src/core/errors.ts
63
65
  var LiquidiumErrorCode = {
@@ -1593,6 +1595,11 @@ function buildInstantLoanAddressLookupPath(request) {
1593
1595
  const query = new URLSearchParams({ address: request.address });
1594
1596
  return `${INSTANT_LOANS}/address?${query.toString()}`;
1595
1597
  }
1598
+ function buildInstantLoanCollateralHintPath(request) {
1599
+ return `${INSTANT_LOANS}/${encodeURIComponent(
1600
+ request.loanId.toString()
1601
+ )}/collateral-hint`;
1602
+ }
1596
1603
  var SdkApiPath = {
1597
1604
  inflow: INFLOW,
1598
1605
  instantLoans: INSTANT_LOANS
@@ -2215,6 +2222,24 @@ function mapHistoryStatusToApi(status) {
2215
2222
  return status.toUpperCase();
2216
2223
  }
2217
2224
 
2225
+ // src/core/utils/asset-decimals.ts
2226
+ var ASSET_NATIVE_DECIMALS = {
2227
+ BTC: 8n,
2228
+ USDC: 6n,
2229
+ USDT: 6n,
2230
+ SOL: 9n
2231
+ };
2232
+ function getAssetNativeDecimals(asset) {
2233
+ const decimals = ASSET_NATIVE_DECIMALS[asset];
2234
+ if (decimals === void 0) {
2235
+ throw new LiquidiumError(
2236
+ LiquidiumErrorCode.VALIDATION_ERROR,
2237
+ `Native decimals are not configured for asset: ${asset}`
2238
+ );
2239
+ }
2240
+ return decimals;
2241
+ }
2242
+
2218
2243
  // src/generated/canisters/ck-btc-minter/declaration.js
2219
2244
  var idlFactory3 = ({ IDL }) => {
2220
2245
  const Mode = IDL.Variant({
@@ -3460,6 +3485,82 @@ function formatBpsAsPercent(bps) {
3460
3485
  const padded = fractional.toString().padStart(2, "0").replace(/0+$/, "");
3461
3486
  return `${whole}.${padded}`;
3462
3487
  }
3488
+ var BTC_MAINNET_ADDRESS_ERROR = "Address must be a valid mainnet BTC address";
3489
+ var EVM_ADDRESS_ERROR = "Address must be a valid EVM address";
3490
+ var ADDRESS_CHAIN_MISMATCH_ERROR = "Address chain must match asset";
3491
+ function normalizeExternalAddress(params) {
3492
+ if (params.asset === Asset.BTC) {
3493
+ if (params.chain !== Chain.BTC) {
3494
+ throw new LiquidiumError(
3495
+ LiquidiumErrorCode.INVALID_ADDRESS,
3496
+ ADDRESS_CHAIN_MISMATCH_ERROR
3497
+ );
3498
+ }
3499
+ if (!validate(params.address, Network.mainnet)) {
3500
+ throw new LiquidiumError(
3501
+ LiquidiumErrorCode.INVALID_ADDRESS,
3502
+ BTC_MAINNET_ADDRESS_ERROR
3503
+ );
3504
+ }
3505
+ return params.address;
3506
+ }
3507
+ if (isEthStablecoin2(params.asset)) {
3508
+ if (params.chain !== Chain.ETH) {
3509
+ throw new LiquidiumError(
3510
+ LiquidiumErrorCode.INVALID_ADDRESS,
3511
+ ADDRESS_CHAIN_MISMATCH_ERROR
3512
+ );
3513
+ }
3514
+ if (!isAddress(params.address)) {
3515
+ throw new LiquidiumError(
3516
+ LiquidiumErrorCode.INVALID_ADDRESS,
3517
+ EVM_ADDRESS_ERROR
3518
+ );
3519
+ }
3520
+ return getAddress(params.address);
3521
+ }
3522
+ return params.address;
3523
+ }
3524
+ function normalizeAndValidateEvmAddress(address, errorMessage) {
3525
+ if (!isAddress(address)) {
3526
+ throw new LiquidiumError(LiquidiumErrorCode.INVALID_ADDRESS, errorMessage);
3527
+ }
3528
+ return getAddress(address);
3529
+ }
3530
+ function isEthStablecoin2(asset) {
3531
+ return asset === Asset.USDC || asset === Asset.USDT;
3532
+ }
3533
+
3534
+ // src/modules/instant-loans/_internal/address-validation.ts
3535
+ function validateInstantLoanBorrowDestination(address, asset) {
3536
+ return normalizeExternalAddress({
3537
+ address,
3538
+ asset,
3539
+ chain: getChainForInstantLoanAsset(asset)
3540
+ });
3541
+ }
3542
+ function validateInstantLoanRefundDestination(address, asset) {
3543
+ return normalizeExternalAddress({
3544
+ address,
3545
+ asset,
3546
+ chain: getChainForInstantLoanAsset(asset)
3547
+ });
3548
+ }
3549
+ function getChainForInstantLoanAsset(asset) {
3550
+ if (asset === Asset.BTC) return Chain.BTC;
3551
+ if (asset === Asset.USDC || asset === Asset.USDT) return Chain.ETH;
3552
+ return asset;
3553
+ }
3554
+
3555
+ // src/modules/instant-loans/types.ts
3556
+ var InstantLoanStatus = {
3557
+ awaitingDeposit: "awaiting_deposit",
3558
+ depositDetected: "deposit_detected",
3559
+ active: "active",
3560
+ settling: "settling",
3561
+ closed: "closed",
3562
+ expired: "expired"
3563
+ };
3463
3564
 
3464
3565
  // src/modules/instant-loans/instant-loans.ts
3465
3566
  var REPAYMENT_BUFFER_SECONDS = 86400n;
@@ -3480,7 +3581,7 @@ var InstantLoansModule = class {
3480
3581
  positions;
3481
3582
  /**
3482
3583
  * Creates a profileless instant loan and returns canonical canister state plus
3483
- * deposit/repay targets for the generated lending profile.
3584
+ * generated initial-deposit and repayment quote targets.
3484
3585
  *
3485
3586
  * Choose `collateralPoolId` and `borrowPoolId` from
3486
3587
  * `client.market.listPools()`, convert UI amounts to base units with the
@@ -3493,10 +3594,22 @@ var InstantLoansModule = class {
3493
3594
  * SDK maps it to the canister's internal `ltv_timer_s` field.
3494
3595
  *
3495
3596
  * @param request - Pool ids, assets, base-unit amounts, LTV limit, timeout, and destinations.
3496
- * @returns Hydrated loan state plus generated deposit and repayment targets.
3597
+ * @returns Hydrated loan state plus generated initial-deposit and repayment quote targets.
3497
3598
  */
3498
3599
  async create(request) {
3499
3600
  validateCreateRequest(request);
3601
+ const borrowDestination = {
3602
+ External: validateInstantLoanBorrowDestination(
3603
+ addressFromAccountInput(request.borrowDestination),
3604
+ request.borrowAsset
3605
+ )
3606
+ };
3607
+ const refundDestination = {
3608
+ External: validateInstantLoanRefundDestination(
3609
+ addressFromAccountInput(request.refundDestination),
3610
+ request.collateralAsset
3611
+ )
3612
+ };
3500
3613
  const apiClient = this.requireApi("Instant loan creation");
3501
3614
  await this.validateInstantLoanLtvPolicy(request);
3502
3615
  const response = await apiClient.post(SdkApiPath.instantLoans, {
@@ -3508,17 +3621,10 @@ var InstantLoansModule = class {
3508
3621
  borrowAmount: request.borrowAmount.toString(),
3509
3622
  ltvMaxBps: request.ltvMaxBps.toString(),
3510
3623
  depositWindowSeconds: request.depositWindowSeconds.toString(),
3511
- borrowDestination: accountWireFromInput(request.borrowDestination),
3512
- refundDestination: accountWireFromInput(request.refundDestination)
3624
+ borrowDestination,
3625
+ refundDestination
3513
3626
  });
3514
- const loan = await this.mapLoanWire(response.loan);
3515
- return {
3516
- ...loan,
3517
- collateral: {
3518
- ...loan.collateral,
3519
- amount: request.collateralAmount
3520
- }
3521
- };
3627
+ return await this.mapLoanWire(response.loan);
3522
3628
  }
3523
3629
  /**
3524
3630
  * Resolves canonical canister state by loan id or short reference.
@@ -3527,7 +3633,7 @@ var InstantLoansModule = class {
3527
3633
  * from the instant-loans canister.
3528
3634
  *
3529
3635
  * @param request - Canister loan id or short public reference.
3530
- * @returns Hydrated loan state plus generated deposit and repayment targets.
3636
+ * @returns Hydrated loan state plus generated initial-deposit and repayment quote targets.
3531
3637
  */
3532
3638
  async get(request) {
3533
3639
  const loanId = "loanId" in request ? request.loanId : decodeRef(request.ref);
@@ -3538,7 +3644,8 @@ var InstantLoansModule = class {
3538
3644
  if ("Err" in result) {
3539
3645
  throw mapInstantLoansErrorToLiquidiumError(result.Err);
3540
3646
  }
3541
- return await this.mapLoanRecord(result.Ok);
3647
+ const collateralAmount = await this.getCollateralAmountHint(loanId);
3648
+ return await this.mapLoanRecord(result.Ok, collateralAmount);
3542
3649
  } catch (error) {
3543
3650
  if (error instanceof LiquidiumError) {
3544
3651
  throw error;
@@ -3664,22 +3771,27 @@ var InstantLoansModule = class {
3664
3771
  const response = await apiClient.get(buildInstantLoanAddressLookupPath({ address: trimmedAddress }));
3665
3772
  return (response.candidates ?? response.loans ?? []).map(mapCandidateWire);
3666
3773
  }
3667
- async mapLoanRecord(record) {
3774
+ async mapLoanRecord(record, collateralAmount) {
3668
3775
  return await this.hydrateLoan({
3669
3776
  loanId: record.id,
3670
3777
  profileId: record.lending_profile.toText(),
3671
3778
  ltvMaxBps: record.ltv_max_bps,
3672
3779
  depositWindowSeconds: record.ltv_timer_s,
3673
3780
  collateralPoolId: record.lend_pool_id.toText(),
3781
+ collateralAmount,
3674
3782
  borrowPoolId: record.borrow_pool_id.toText(),
3675
3783
  collateralAsset: getVariantKey(record.lend_asset),
3676
3784
  borrowAsset: getVariantKey(record.borrow_asset),
3677
3785
  borrowAmount: record.borrow_amount,
3678
3786
  borrowDestination: accountFromCanister(record.borrow_destination),
3679
- refundDestination: accountFromCanister(record.refund_destination),
3680
- started: record.started
3787
+ refundDestination: accountFromCanister(record.refund_destination)
3681
3788
  });
3682
3789
  }
3790
+ async getCollateralAmountHint(loanId) {
3791
+ const apiClient = this.requireApi("Instant loan lookup");
3792
+ const response = await apiClient.get(buildInstantLoanCollateralHintPath({ loanId }));
3793
+ return parseBigintWire(response.collateralAmountHint, "collateral amount");
3794
+ }
3683
3795
  async mapLoanWire(loan) {
3684
3796
  const loanId = parseBigintWire(loan.loanId, "loan ID");
3685
3797
  return await this.hydrateLoan({
@@ -3691,6 +3803,10 @@ var InstantLoansModule = class {
3691
3803
  "deposit window"
3692
3804
  ),
3693
3805
  collateralPoolId: loan.collateral.poolId,
3806
+ collateralAmount: parseBigintWire(
3807
+ loan.collateral.amountHint,
3808
+ "collateral amount"
3809
+ ),
3694
3810
  borrowPoolId: loan.borrow.poolId,
3695
3811
  collateralAsset: loan.collateral.asset,
3696
3812
  borrowAsset: loan.borrow.asset,
@@ -3735,54 +3851,64 @@ var InstantLoansModule = class {
3735
3851
  );
3736
3852
  const repaymentInflowFee = totalDebtAmount > 0n ? await this.estimateRepaymentInflowFee(borrowAsset, repayTarget.chain) : { totalFee: 0n, estimateAvailable: false };
3737
3853
  const repaymentAmount = totalDebtAmount + interestBufferAmount + repaymentInflowFee.totalFee;
3738
- const collateralAmount = collateralPosition?.deposited ?? 0n;
3739
- const collateralDecimals = collateralPosition?.depositedDecimals ?? 0n;
3854
+ const currentCollateralAmount = collateralPosition?.deposited ?? 0n;
3855
+ const collateralAmount = input.collateralAmount;
3856
+ const collateralDecimals = collateralPosition?.depositedDecimals ?? getAssetNativeDecimals(collateralAsset);
3740
3857
  const collateralInterestAmount = collateralPosition?.earnedInterest ?? 0n;
3741
3858
  const borrowedAmount = borrowPosition?.borrowed ?? 0n;
3742
- const borrowedDecimals = borrowPosition?.borrowedDecimals ?? 0n;
3859
+ const borrowedDecimals = borrowPosition?.borrowedDecimals ?? getAssetNativeDecimals(borrowAsset);
3743
3860
  const debtInterestAmount = borrowPosition?.debtInterest ?? 0n;
3744
3861
  const status = deriveInstantLoanStatus({
3745
- collateralAmount,
3746
- started: input.started,
3862
+ collateralAmount: currentCollateralAmount,
3747
3863
  totalDebtAmount
3748
3864
  });
3865
+ const initialDeposit = await this.createInitialDepositQuote({
3866
+ collateralAmount,
3867
+ decimals: collateralDecimals,
3868
+ asset: collateralAsset,
3869
+ target: depositTarget
3870
+ });
3871
+ const repayment = {
3872
+ amount: repaymentAmount,
3873
+ decimals: borrowedDecimals,
3874
+ debtAmount: totalDebtAmount,
3875
+ interestBufferAmount,
3876
+ interestBufferSeconds: REPAYMENT_BUFFER_SECONDS,
3877
+ inflowFeeAmount: repaymentInflowFee.totalFee,
3878
+ inflowFeeEstimateAvailable: repaymentInflowFee.estimateAvailable,
3879
+ asset: borrowAsset,
3880
+ chain: repayTarget.chain,
3881
+ target: repayTarget
3882
+ };
3749
3883
  return {
3750
3884
  loanId: input.loanId,
3751
3885
  ref: publicIdFromInt(input.loanId),
3752
3886
  status,
3753
3887
  profileId,
3754
- ltvMaxBps: input.ltvMaxBps,
3755
- depositWindowSeconds: input.depositWindowSeconds,
3888
+ terms: {
3889
+ ltvMaxBps: input.ltvMaxBps,
3890
+ depositWindowSeconds: input.depositWindowSeconds
3891
+ },
3756
3892
  collateral: {
3757
3893
  poolId: collateralPoolId,
3758
3894
  asset: collateralAsset,
3759
3895
  chain: depositTarget.chain,
3896
+ decimals: collateralDecimals,
3760
3897
  amount: collateralAmount
3761
3898
  },
3762
3899
  borrow: {
3763
3900
  poolId: borrowPoolId,
3764
3901
  asset: borrowAsset,
3765
3902
  chain: repayTarget.chain,
3903
+ decimals: borrowedDecimals,
3766
3904
  amount: input.borrowAmount,
3767
3905
  destination: input.borrowDestination
3768
3906
  },
3769
3907
  refundDestination: input.refundDestination,
3770
- depositTarget,
3771
- repayTarget,
3772
- repayment: {
3773
- amount: repaymentAmount,
3774
- decimals: borrowedDecimals,
3775
- debtAmount: totalDebtAmount,
3776
- interestBufferAmount,
3777
- interestBufferSeconds: REPAYMENT_BUFFER_SECONDS,
3778
- inflowFeeAmount: repaymentInflowFee.totalFee,
3779
- inflowFeeEstimateAvailable: repaymentInflowFee.estimateAvailable,
3780
- asset: borrowAsset,
3781
- chain: repayTarget.chain,
3782
- target: repayTarget
3783
- },
3908
+ initialDeposit,
3909
+ repayment,
3784
3910
  position: {
3785
- collateralAmount,
3911
+ collateralAmount: currentCollateralAmount,
3786
3912
  collateralDecimals,
3787
3913
  collateralInterestAmount,
3788
3914
  borrowedAmount,
@@ -3792,6 +3918,21 @@ var InstantLoansModule = class {
3792
3918
  }
3793
3919
  };
3794
3920
  }
3921
+ async createInitialDepositQuote(input) {
3922
+ const inflowFee = await this.lending.estimateInflowFee({
3923
+ asset: input.asset,
3924
+ chain: input.target.chain
3925
+ });
3926
+ return {
3927
+ amount: input.collateralAmount + inflowFee.totalFee,
3928
+ decimals: input.decimals,
3929
+ collateralAmount: input.collateralAmount,
3930
+ inflowFeeAmount: inflowFee.totalFee,
3931
+ asset: input.asset,
3932
+ chain: input.target.chain,
3933
+ target: input.target
3934
+ };
3935
+ }
3795
3936
  async estimateRepaymentInflowFee(asset, chain) {
3796
3937
  try {
3797
3938
  const fee = await this.lending.estimateInflowFee({
@@ -3878,15 +4019,12 @@ function calculateTotalDebtAmount(borrowPosition) {
3878
4019
  }
3879
4020
  function deriveInstantLoanStatus(input) {
3880
4021
  if (input.totalDebtAmount > 0n) {
3881
- return "active";
4022
+ return InstantLoanStatus.active;
3882
4023
  }
3883
4024
  if (input.collateralAmount > 0n) {
3884
- return input.started ? "settling" : "deposit_detected";
3885
- }
3886
- if (input.started) {
3887
- return "closed";
4025
+ return InstantLoanStatus.depositDetected;
3888
4026
  }
3889
- return "awaiting_deposit";
4027
+ return InstantLoanStatus.awaitingDeposit;
3890
4028
  }
3891
4029
  function validateCreateRequest(request) {
3892
4030
  if (request.collateralAmount <= 0n) {
@@ -3920,7 +4058,7 @@ function throwLtvCalculationError(error) {
3920
4058
  error?.message ?? "Unable to calculate instant loan LTV"
3921
4059
  );
3922
4060
  }
3923
- function accountWireFromInput(account) {
4061
+ function addressFromAccountInput(account) {
3924
4062
  const address = typeof account === "string" ? account.trim() : account.address.trim();
3925
4063
  if (!address) {
3926
4064
  throw new LiquidiumError(
@@ -3928,7 +4066,7 @@ function accountWireFromInput(account) {
3928
4066
  "Instant loan account address must be non-empty"
3929
4067
  );
3930
4068
  }
3931
- return { External: address };
4069
+ return address;
3932
4070
  }
3933
4071
  function accountFromCanister(account) {
3934
4072
  if ("Native" in account) {
@@ -4094,19 +4232,20 @@ function mapCandidateWire(wire) {
4094
4232
  wire.borrowAsset ?? wire.borrow_asset,
4095
4233
  "borrow asset"
4096
4234
  ),
4097
- collateralAmountHint: parseBigintWire(
4098
- wire.collateralAmountHint ?? wire.min_deposit_hint,
4099
- "collateral amount hint"
4235
+ collateralAmount: parseBigintWire(
4236
+ wire.collateralAmount,
4237
+ "collateral amount"
4100
4238
  )
4101
4239
  };
4102
4240
  }
4103
4241
  function parseBigintWire(value, label) {
4104
- if (typeof value === "bigint") return value;
4105
- if (typeof value === "string" && /^\d+$/.test(value)) return BigInt(value);
4106
- throw new LiquidiumError(
4107
- LiquidiumErrorCode.VALIDATION_ERROR,
4108
- `Invalid instant loan ${label}`
4109
- );
4242
+ if (!value || !/^\d+$/.test(value)) {
4243
+ throw new LiquidiumError(
4244
+ LiquidiumErrorCode.VALIDATION_ERROR,
4245
+ `Invalid instant loan ${label}`
4246
+ );
4247
+ }
4248
+ return BigInt(value);
4110
4249
  }
4111
4250
  function requiredString(value, label) {
4112
4251
  if (value?.trim()) return value;
@@ -4177,15 +4316,6 @@ function formatBpsAsPercent2(value) {
4177
4316
  const fractionalPart = (absoluteValue % 100n).toString().padStart(2, "0");
4178
4317
  return `${sign}${wholePart.toString()}.${fractionalPart}%`;
4179
4318
  }
4180
-
4181
- // src/modules/instant-loans/types.ts
4182
- var InstantLoanStatus = {
4183
- awaitingDeposit: "awaiting_deposit",
4184
- depositDetected: "deposit_detected",
4185
- active: "active",
4186
- settling: "settling",
4187
- closed: "closed"
4188
- };
4189
4319
  function createApproveTransaction(params) {
4190
4320
  return {
4191
4321
  to: params.tokenAddress,
@@ -4262,6 +4392,16 @@ function encodeBytes32Hex(bytes) {
4262
4392
  (byte) => byte.toString(16).padStart(2, "0")
4263
4393
  ).join("")}`;
4264
4394
  }
4395
+ var idlFactory5 = ({ IDL }) => IDL.Service({
4396
+ icrc1_fee: IDL.Func([], [IDL.Nat], ["query"])
4397
+ });
4398
+ function createCkBtcLedgerActor(canisterContext) {
4399
+ const canisterId = CK_CANISTER_IDS.btcLedger;
4400
+ return Actor.createActor(idlFactory5, {
4401
+ agent: canisterContext.agent,
4402
+ canisterId
4403
+ });
4404
+ }
4265
4405
 
4266
4406
  // src/core/canisters/lending/messages.ts
4267
4407
  function createBorrowAssetMessage(request, nonce) {
@@ -4344,6 +4484,28 @@ function assertValidRetryOptions(options) {
4344
4484
  }
4345
4485
  }
4346
4486
 
4487
+ // src/modules/lending/_internal/inflow-fee-rounding.ts
4488
+ var ETH_STABLECOIN_INFLOW_FEE_ROUND_UP_TO_NEAREST_10_CENTS = 100000n;
4489
+ var BTC_INFLOW_FEE_ROUND_UP_TO_NEAREST_SATS = 500n;
4490
+ function roundInflowFeeEstimate(request, totalFee) {
4491
+ if (totalFee <= 0n) {
4492
+ return 0n;
4493
+ }
4494
+ if (isEthStablecoin(request.asset, request.chain)) {
4495
+ return roundUpToNearest(
4496
+ totalFee,
4497
+ ETH_STABLECOIN_INFLOW_FEE_ROUND_UP_TO_NEAREST_10_CENTS
4498
+ );
4499
+ }
4500
+ if (request.asset === Asset.BTC && request.chain === Chain.BTC) {
4501
+ return roundUpToNearest(totalFee, BTC_INFLOW_FEE_ROUND_UP_TO_NEAREST_SATS);
4502
+ }
4503
+ return totalFee;
4504
+ }
4505
+ function roundUpToNearest(amount, roundingUnit) {
4506
+ return ceilDivBigint(amount, roundingUnit) * roundingUnit;
4507
+ }
4508
+
4347
4509
  // src/modules/lending/mappers.ts
4348
4510
  function mapCanisterOutflowDetails(outflow) {
4349
4511
  const rawOutflowType = getVariantKey(outflow.outflow_type);
@@ -4432,6 +4594,10 @@ var LendingModule = class {
4432
4594
  "Withdraw requires a signer account"
4433
4595
  );
4434
4596
  }
4597
+ const receiverAddress = await this.normalizeOutflowReceiverAddress({
4598
+ poolId: request.poolId,
4599
+ receiverAddress: destinationAccount
4600
+ });
4435
4601
  const lendingActor = createLendingActor(this.canisterContext);
4436
4602
  try {
4437
4603
  const expiryTimestamp = computeExpiryTimestampFromNow();
@@ -4440,7 +4606,7 @@ var LendingModule = class {
4440
4606
  profileId: request.profileId,
4441
4607
  poolId: request.poolId,
4442
4608
  amount: request.amount,
4443
- receiverAddress: destinationAccount,
4609
+ receiverAddress,
4444
4610
  signerWalletAddress: signerAccount,
4445
4611
  expiryTimestamp
4446
4612
  };
@@ -4454,7 +4620,7 @@ var LendingModule = class {
4454
4620
  {
4455
4621
  pool_id: request.poolId,
4456
4622
  amount: request.amount.toString(),
4457
- account: { type: "External", data: destinationAccount },
4623
+ account: { type: "External", data: receiverAddress },
4458
4624
  expiry_timestamp: expiryTimestamp
4459
4625
  },
4460
4626
  nonce
@@ -4547,6 +4713,10 @@ var LendingModule = class {
4547
4713
  "Borrow requires a signer account"
4548
4714
  );
4549
4715
  }
4716
+ const receiverAddress = await this.normalizeOutflowReceiverAddress({
4717
+ poolId: request.poolId,
4718
+ receiverAddress: destinationAccount
4719
+ });
4550
4720
  const lendingActor = createLendingActor(this.canisterContext);
4551
4721
  try {
4552
4722
  const expiryTimestamp = computeExpiryTimestampFromNow();
@@ -4555,7 +4725,7 @@ var LendingModule = class {
4555
4725
  profileId: request.profileId,
4556
4726
  poolId: request.poolId,
4557
4727
  amount: request.amount,
4558
- receiverAddress: destinationAccount,
4728
+ receiverAddress,
4559
4729
  signerWalletAddress: signerAccount,
4560
4730
  expiryTimestamp
4561
4731
  };
@@ -4569,7 +4739,7 @@ var LendingModule = class {
4569
4739
  {
4570
4740
  pool_id: request.poolId,
4571
4741
  amount: request.amount.toString(),
4572
- account: { type: "External", data: destinationAccount },
4742
+ account: { type: "External", data: receiverAddress },
4573
4743
  expiry_timestamp: expiryTimestamp
4574
4744
  },
4575
4745
  nonce
@@ -4818,10 +4988,10 @@ var LendingModule = class {
4818
4988
  * Estimates the network/deposit fee for an inflow target.
4819
4989
  *
4820
4990
  * ETH stablecoin deposit-address estimates are served by the deposit-address
4821
- * canister. BTC estimates are not exposed by this SDK yet and return zero.
4991
+ * canister. BTC estimates include the ckBTC minter deposit fee and ledger fee.
4822
4992
  *
4823
4993
  * @param request - Asset and chain pair to estimate for.
4824
- * @returns Total fee estimate in the asset's base units.
4994
+ * @returns Total fee estimate rounded up in the asset's base units.
4825
4995
  */
4826
4996
  async estimateInflowFee(request) {
4827
4997
  if (isEthStablecoin(request.asset, request.chain)) {
@@ -4831,16 +5001,28 @@ var LendingModule = class {
4831
5001
  if ("Err" in result) {
4832
5002
  throw mapDepositAccountErrorToLiquidiumError(result.Err);
4833
5003
  }
4834
- return { totalFee: result.Ok };
5004
+ return {
5005
+ totalFee: roundInflowFeeEstimate(request, result.Ok)
5006
+ };
4835
5007
  }
4836
5008
  if (request.asset === Asset.BTC && request.chain === Chain.BTC) {
4837
- return { totalFee: 0n };
5009
+ const estimate = await this.estimateBtcInflowFee();
5010
+ return {
5011
+ totalFee: roundInflowFeeEstimate(request, estimate.totalFee)
5012
+ };
4838
5013
  }
4839
5014
  throw new LiquidiumError(
4840
5015
  LiquidiumErrorCode.VALIDATION_ERROR,
4841
5016
  `Inflow fee estimates are not supported for ${request.asset} on ${request.chain}`
4842
5017
  );
4843
5018
  }
5019
+ async estimateBtcInflowFee() {
5020
+ const [minterFee, ledgerFee] = await Promise.all([
5021
+ createCkBtcMinterActor(this.canisterContext).get_deposit_fee(),
5022
+ createCkBtcLedgerActor(this.canisterContext).icrc1_fee()
5023
+ ]);
5024
+ return { totalFee: minterFee + ledgerFee };
5025
+ }
4844
5026
  async sendAndSubmitNativeSupplyInflow(params) {
4845
5027
  const { request, instruction, defaultSubmitInflowRequest } = params;
4846
5028
  if (instruction.target.type !== "nativeAddress") {
@@ -5133,6 +5315,14 @@ var LendingModule = class {
5133
5315
  }
5134
5316
  return selectedPool;
5135
5317
  }
5318
+ async normalizeOutflowReceiverAddress(params) {
5319
+ const selectedPool = await this.getPoolById(params.poolId);
5320
+ return normalizeExternalAddress({
5321
+ address: params.receiverAddress,
5322
+ asset: getVariantKey(selectedPool.asset),
5323
+ chain: getVariantKey(selectedPool.chain)
5324
+ });
5325
+ }
5136
5326
  async sendEthContractTransaction(walletAdapter, walletAddress, request, actionType) {
5137
5327
  if (!walletAdapter.sendEthTransaction) {
5138
5328
  throw new LiquidiumError(
@@ -5207,12 +5397,6 @@ function getApprovalStrategy(params) {
5207
5397
  }
5208
5398
  return EvmSupplyApprovalStrategy.resetThenApproveMax;
5209
5399
  }
5210
- function normalizeAndValidateEvmAddress(address, errorMessage) {
5211
- if (!isAddress(address)) {
5212
- throw new LiquidiumError(LiquidiumErrorCode.INVALID_ADDRESS, errorMessage);
5213
- }
5214
- return getAddress(address);
5215
- }
5216
5400
  function mapExpectedOutflowDetails(details, expectedOutflowType, operation) {
5217
5401
  if (details.outflowType !== expectedOutflowType) {
5218
5402
  throw new LiquidiumError(
@@ -5262,24 +5446,6 @@ function shouldSubmitInflow(params) {
5262
5446
  return !isEthStablecoin(params.instruction.asset, params.instruction.chain);
5263
5447
  }
5264
5448
 
5265
- // src/core/utils/asset-decimals.ts
5266
- var ASSET_NATIVE_DECIMALS = {
5267
- BTC: 8n,
5268
- USDC: 6n,
5269
- USDT: 6n,
5270
- SOL: 9n
5271
- };
5272
- function getAssetNativeDecimals(asset) {
5273
- const decimals = ASSET_NATIVE_DECIMALS[asset];
5274
- if (decimals === void 0) {
5275
- throw new LiquidiumError(
5276
- LiquidiumErrorCode.VALIDATION_ERROR,
5277
- `Native decimals are not configured for asset: ${asset}`
5278
- );
5279
- }
5280
- return decimals;
5281
- }
5282
-
5283
5449
  // src/modules/market/mappers.ts
5284
5450
  var DECIMAL_BASE = 10;
5285
5451
  var PAIR_SEPARATOR = "_";
@@ -5646,7 +5812,7 @@ var PositionsModule = class {
5646
5812
  * Returns the per-reserve breakdown of a profile's supplies and borrows,
5647
5813
  * joined with pool metadata, rates, and current USD prices.
5648
5814
  *
5649
- * USD values are scaled to {@link USD_VALUE_SCALE_DECIMALS} (27).
5815
+ * USD values are scaled to 27 decimals.
5650
5816
  *
5651
5817
  * @param profileId - The Liquidium profile principal text.
5652
5818
  * @returns Per-reserve position rows joined with pool metadata and USD values.
@@ -5798,6 +5964,6 @@ function resolveEvmReadClient(config) {
5798
5964
  });
5799
5965
  }
5800
5966
 
5801
- export { ActivityDirection, ActivityFilter, ActivityKind, ActivityStatus, Asset, CK_ETH_DEPOSIT_CONTRACT_ADDRESS, Chain, Environment, EvmSupplyApprovalStrategy, InflowSubmitType, InstantLoanStatus, LiquidiumClient, LiquidiumError, LiquidiumErrorCode, OutflowType, QuoteValidationErrorCode, QuoteWarningCode, RATE_DECIMALS, RATE_SCALE, SupplyAction, SupplyPlanType, TransferMode, USDC_CONTRACT_ADDRESS, USDT_CONTRACT_ADDRESS, UserHistoryStatus, WalletExecutionKind, createTransferErc20Transaction, executeWith, intFromPublicId, publicIdFromInt };
5967
+ export { AccountsModule, ActivitiesModule, ActivityDirection, ActivityFilter, ActivityKind, ActivityStatus, Asset, CK_ETH_DEPOSIT_CONTRACT_ADDRESS, Chain, Environment, EvmSupplyApprovalStrategy, HistoryModule, InflowSubmitType, InstantLoanStatus, InstantLoansModule, LendingModule, LiquidiumClient, LiquidiumError, LiquidiumErrorCode, MarketModule, OutflowType, PositionsModule, QuoteModule, QuoteValidationErrorCode, QuoteWarningCode, RATE_DECIMALS, RATE_SCALE, SupplyAction, SupplyPlanType, TransferMode, USDC_CONTRACT_ADDRESS, USDT_CONTRACT_ADDRESS, UserHistoryStatus, WalletExecutionKind, createTransferErc20Transaction, executeWith, intFromPublicId, publicIdFromInt };
5802
5968
  //# sourceMappingURL=index.js.map
5803
5969
  //# sourceMappingURL=index.js.map