@liquidium/client 0.1.2 → 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/README.md +17 -13
- package/dist/index.cjs +264 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -41
- package/dist/index.d.ts +85 -41
- package/dist/index.js +265 -99
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { encodeFunctionData,
|
|
1
|
+
import { encodeFunctionData, createPublicClient, http, isAddress, getAddress } from 'viem';
|
|
2
2
|
import { mainnet } from 'viem/chains';
|
|
3
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
|
|
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
|
|
3512
|
-
refundDestination
|
|
3624
|
+
borrowDestination,
|
|
3625
|
+
refundDestination
|
|
3513
3626
|
});
|
|
3514
|
-
|
|
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
|
-
|
|
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
|
|
3739
|
-
const
|
|
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 ??
|
|
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
|
-
|
|
3755
|
-
|
|
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
|
-
|
|
3771
|
-
|
|
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
|
|
4022
|
+
return InstantLoanStatus.active;
|
|
3882
4023
|
}
|
|
3883
4024
|
if (input.collateralAmount > 0n) {
|
|
3884
|
-
return
|
|
3885
|
-
}
|
|
3886
|
-
if (input.started) {
|
|
3887
|
-
return "closed";
|
|
4025
|
+
return InstantLoanStatus.depositDetected;
|
|
3888
4026
|
}
|
|
3889
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
4098
|
-
wire.
|
|
4099
|
-
"collateral amount
|
|
4235
|
+
collateralAmount: parseBigintWire(
|
|
4236
|
+
wire.collateralAmount,
|
|
4237
|
+
"collateral amount"
|
|
4100
4238
|
)
|
|
4101
4239
|
};
|
|
4102
4240
|
}
|
|
4103
4241
|
function parseBigintWire(value, label) {
|
|
4104
|
-
if (
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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 {
|
|
5004
|
+
return {
|
|
5005
|
+
totalFee: roundInflowFeeEstimate(request, result.Ok)
|
|
5006
|
+
};
|
|
4835
5007
|
}
|
|
4836
5008
|
if (request.asset === Asset.BTC && request.chain === Chain.BTC) {
|
|
4837
|
-
|
|
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 = "_";
|