@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/README.md +26 -15
- package/dist/index.cjs +273 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -56
- package/dist/index.d.ts +100 -56
- package/dist/index.js +267 -101
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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
|
|
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
|
|
3514
|
-
refundDestination
|
|
3626
|
+
borrowDestination,
|
|
3627
|
+
refundDestination
|
|
3515
3628
|
});
|
|
3516
|
-
|
|
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
|
-
|
|
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
|
|
3741
|
-
const
|
|
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 ??
|
|
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
|
-
|
|
3757
|
-
|
|
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
|
-
|
|
3773
|
-
|
|
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
|
|
4024
|
+
return InstantLoanStatus.active;
|
|
3884
4025
|
}
|
|
3885
4026
|
if (input.collateralAmount > 0n) {
|
|
3886
|
-
return
|
|
3887
|
-
}
|
|
3888
|
-
if (input.started) {
|
|
3889
|
-
return "closed";
|
|
4027
|
+
return InstantLoanStatus.depositDetected;
|
|
3890
4028
|
}
|
|
3891
|
-
return
|
|
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
|
|
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
|
|
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
|
-
|
|
4100
|
-
wire.
|
|
4101
|
-
"collateral amount
|
|
4237
|
+
collateralAmount: parseBigintWire(
|
|
4238
|
+
wire.collateralAmount,
|
|
4239
|
+
"collateral amount"
|
|
4102
4240
|
)
|
|
4103
4241
|
};
|
|
4104
4242
|
}
|
|
4105
4243
|
function parseBigintWire(value, label) {
|
|
4106
|
-
if (
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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 {
|
|
5006
|
+
return {
|
|
5007
|
+
totalFee: roundInflowFeeEstimate(request, result.Ok)
|
|
5008
|
+
};
|
|
4837
5009
|
}
|
|
4838
5010
|
if (request.asset === Asset.BTC && request.chain === Chain.BTC) {
|
|
4839
|
-
|
|
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
|
|
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;
|