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