@liquidium/client 0.2.0 → 0.3.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
@@ -1593,9 +1593,9 @@ function buildActivityStatusPath(request) {
1593
1593
  request.id
1594
1594
  )}/status?${query.toString()}`;
1595
1595
  }
1596
- function buildInstantLoanAddressLookupPath(request) {
1597
- const query = new URLSearchParams({ address: request.address });
1598
- return `${INSTANT_LOANS}/address?${query.toString()}`;
1596
+ function buildInstantLoanFindPath(request) {
1597
+ const query = new URLSearchParams({ query: request.query });
1598
+ return `${INSTANT_LOANS}/find?${query.toString()}`;
1599
1599
  }
1600
1600
  function buildInstantLoanCollateralHintPath(request) {
1601
1601
  return `${INSTANT_LOANS}/${encodeURIComponent(
@@ -2224,6 +2224,41 @@ function mapHistoryStatusToApi(status) {
2224
2224
  return status.toUpperCase();
2225
2225
  }
2226
2226
 
2227
+ // src/core/utils/api-response-parsers.ts
2228
+ var MILLISECONDS_PER_SECOND2 = 1e3;
2229
+ function parseNonEmptyApiString(value, field) {
2230
+ if (!value) {
2231
+ throwInvalidApiResponseField(field);
2232
+ }
2233
+ return value;
2234
+ }
2235
+ function parseUnsignedApiBigint(value, field) {
2236
+ if (!value || !/^\d+$/.test(value)) {
2237
+ throwInvalidApiResponseField(field);
2238
+ }
2239
+ return BigInt(value);
2240
+ }
2241
+ function parseIsoApiTimestampToUnixSeconds(value, field) {
2242
+ const timestamp = parseNonEmptyApiString(value, field);
2243
+ const timestampMilliseconds = Date.parse(timestamp);
2244
+ if (!Number.isFinite(timestampMilliseconds)) {
2245
+ throwInvalidApiResponseField(field);
2246
+ }
2247
+ return BigInt(Math.floor(timestampMilliseconds / MILLISECONDS_PER_SECOND2));
2248
+ }
2249
+ function parseApiStringUnion(value, allowedValues, field) {
2250
+ if (value !== void 0 && allowedValues.includes(value)) {
2251
+ return value;
2252
+ }
2253
+ throwInvalidApiResponseField(field);
2254
+ }
2255
+ function throwInvalidApiResponseField(field) {
2256
+ throw new LiquidiumError(
2257
+ LiquidiumErrorCode.VALIDATION_ERROR,
2258
+ `Invalid ${field.context} ${field.label}`
2259
+ );
2260
+ }
2261
+
2227
2262
  // src/core/utils/asset-decimals.ts
2228
2263
  var ASSET_NATIVE_DECIMALS = {
2229
2264
  BTC: 8n,
@@ -3107,6 +3142,39 @@ function assertSupportsIcrcAccountInflowTarget(asset) {
3107
3142
  );
3108
3143
  }
3109
3144
 
3145
+ // src/core/borrow-minimums.ts
3146
+ var MIN_BORROW_AMOUNTS_BY_ASSET = {
3147
+ BTC: 5100n,
3148
+ // 5,100 sats = 0.000051 BTC
3149
+ USDC: 1000000n,
3150
+ // 1 USDC
3151
+ USDT: 1000000n
3152
+ // 1 USDT
3153
+ };
3154
+ function getMinimumBorrowAmount(asset) {
3155
+ if (!isMinimumBorrowAsset(asset)) {
3156
+ return 0n;
3157
+ }
3158
+ return MIN_BORROW_AMOUNTS_BY_ASSET[asset];
3159
+ }
3160
+ function getBorrowAmountMinimumValidationError(params) {
3161
+ const minimumAmount = getMinimumBorrowAmount(params.asset);
3162
+ if (minimumAmount <= 0n || params.amount >= minimumAmount) {
3163
+ return null;
3164
+ }
3165
+ return {
3166
+ asset: params.asset,
3167
+ minimumAmount,
3168
+ message: formatMinimumBorrowAmountMessage(params.asset, minimumAmount)
3169
+ };
3170
+ }
3171
+ function formatMinimumBorrowAmountMessage(asset, minimumAmount) {
3172
+ return `Borrow amount must be at least ${minimumAmount} base units for ${asset}`;
3173
+ }
3174
+ function isMinimumBorrowAsset(asset) {
3175
+ return Object.hasOwn(MIN_BORROW_AMOUNTS_BY_ASSET, asset);
3176
+ }
3177
+
3110
3178
  // src/modules/quote/types.ts
3111
3179
  var QuoteValidationErrorCode = /* @__PURE__ */ ((QuoteValidationErrorCode2) => {
3112
3180
  QuoteValidationErrorCode2["INVALID_LTV"] = "INVALID_LTV";
@@ -3129,7 +3197,6 @@ var BASIS_POINTS_DENOMINATOR = 10000n;
3129
3197
  var BPS_PER_PERCENT = 100n;
3130
3198
  var MIN_LTV_BPS = 0n;
3131
3199
  var HIGH_LTV_WARNING_THRESHOLD_BPS = 8000n;
3132
- var MIN_BORROW_AMOUNT_BASE_UNITS = 5000n;
3133
3200
  var INTERNAL_USD_DECIMAL_PLACES = 8;
3134
3201
  var PRICE_SCALE_DECIMAL_PLACES = 8;
3135
3202
  var QuoteModule = class {
@@ -3187,11 +3254,12 @@ var QuoteModule = class {
3187
3254
  message: `Price not available for collateral asset: ${collateralPool.asset}`
3188
3255
  });
3189
3256
  }
3190
- if (request.borrowAmount <= 0n) {
3191
- validationErrors.push({
3192
- code: "BORROW_AMOUNT_TOO_LOW" /* BORROW_AMOUNT_TOO_LOW */,
3193
- message: "Borrow amount must be greater than 0"
3194
- });
3257
+ const borrowAmountError = createBorrowAmountValidationError({
3258
+ amount: request.borrowAmount,
3259
+ asset: borrowPool.asset
3260
+ });
3261
+ if (borrowAmountError) {
3262
+ validationErrors.push(borrowAmountError);
3195
3263
  }
3196
3264
  if (request.collateralAmount <= 0n) {
3197
3265
  validationErrors.push({
@@ -3313,16 +3381,12 @@ var QuoteModule = class {
3313
3381
  message: `Price not available for collateral asset: ${collateralAsset}`
3314
3382
  });
3315
3383
  }
3316
- if (borrowAmount < 0n) {
3317
- validationErrors.push({
3318
- code: "BORROW_AMOUNT_TOO_LOW" /* BORROW_AMOUNT_TOO_LOW */,
3319
- message: `Borrow amount must be non-negative`
3320
- });
3321
- } else if (borrowAmount < MIN_BORROW_AMOUNT_BASE_UNITS) {
3322
- validationErrors.push({
3323
- code: "BORROW_AMOUNT_TOO_LOW" /* BORROW_AMOUNT_TOO_LOW */,
3324
- message: `Borrow amount must be at least ${MIN_BORROW_AMOUNT_BASE_UNITS} base units`
3325
- });
3384
+ const borrowAmountError = createBorrowAmountValidationError({
3385
+ amount: borrowAmount,
3386
+ asset: borrowAsset
3387
+ });
3388
+ if (borrowAmountError) {
3389
+ validationErrors.push(borrowAmountError);
3326
3390
  }
3327
3391
  if (targetLtvBps <= MIN_LTV_BPS) {
3328
3392
  validationErrors.push({
@@ -3445,6 +3509,22 @@ function createQuoteResult(params) {
3445
3509
  warnings: params.warnings
3446
3510
  };
3447
3511
  }
3512
+ function createBorrowAmountValidationError(params) {
3513
+ if (params.amount <= 0n) {
3514
+ return {
3515
+ code: "BORROW_AMOUNT_TOO_LOW" /* BORROW_AMOUNT_TOO_LOW */,
3516
+ message: "Borrow amount must be greater than 0"
3517
+ };
3518
+ }
3519
+ const minimumBorrowAmountError = getBorrowAmountMinimumValidationError(params);
3520
+ if (!minimumBorrowAmountError) {
3521
+ return null;
3522
+ }
3523
+ return {
3524
+ code: "BORROW_AMOUNT_TOO_LOW" /* BORROW_AMOUNT_TOO_LOW */,
3525
+ message: minimumBorrowAmountError.message
3526
+ };
3527
+ }
3448
3528
  function computeUsdInternalFromBaseUnits(params) {
3449
3529
  const { amountBaseUnits, priceScaled, assetDecimalPlaces } = params;
3450
3530
  const scaleDiff = INTERNAL_USD_DECIMAL_PLACES - PRICE_SCALE_DECIMAL_PLACES;
@@ -3570,6 +3650,14 @@ var RATE_SCALE2 = 10n ** 27n;
3570
3650
  var SECONDS_PER_YEAR = 31536000n;
3571
3651
  var ETH_STABLECOIN_INFLOW_FEE_FALLBACK = 1500000n;
3572
3652
  var INSTANT_LOAN_MIN_SLIPPAGE_BUFFER_BPS = 200n;
3653
+ var INSTANT_LOAN_FIND_QUERY_MAX_LENGTH = 256;
3654
+ var INSTANT_LOAN_WIRE_CONTEXT = "instant loan";
3655
+ var INSTANT_LOAN_ASSETS = [
3656
+ Asset.BTC,
3657
+ Asset.SOL,
3658
+ Asset.USDC,
3659
+ Asset.USDT
3660
+ ];
3573
3661
  var InstantLoansModule = class {
3574
3662
  constructor(canisterContext, apiClient, lending, positions) {
3575
3663
  this.canisterContext = canisterContext;
@@ -3626,7 +3714,19 @@ var InstantLoansModule = class {
3626
3714
  borrowDestination,
3627
3715
  refundDestination
3628
3716
  });
3629
- return await this.mapLoanWire(response.loan);
3717
+ const loanId = parseUnsignedApiBigint(response.loan.loanId, {
3718
+ context: INSTANT_LOAN_WIRE_CONTEXT,
3719
+ label: "loan ID"
3720
+ });
3721
+ const collateralAmount = parseUnsignedApiBigint(
3722
+ response.loan.collateral.amountHint,
3723
+ {
3724
+ context: INSTANT_LOAN_WIRE_CONTEXT,
3725
+ label: "collateral amount"
3726
+ }
3727
+ );
3728
+ const record = await this.getLoanRecord(loanId);
3729
+ return await this.mapLoanRecord(record, collateralAmount);
3630
3730
  }
3631
3731
  /**
3632
3732
  * Resolves canonical canister state by loan id or short reference.
@@ -3639,21 +3739,30 @@ var InstantLoansModule = class {
3639
3739
  */
3640
3740
  async get(request) {
3641
3741
  const loanId = "loanId" in request ? request.loanId : decodeRef(request.ref);
3642
- try {
3643
- const result = await createInstantLoansActor(
3644
- this.canisterContext
3645
- ).get_loan(loanId);
3646
- if ("Err" in result) {
3647
- throw mapInstantLoansErrorToLiquidiumError(result.Err);
3648
- }
3649
- const collateralAmount = await this.getCollateralAmountHint(loanId);
3650
- return await this.mapLoanRecord(result.Ok, collateralAmount);
3651
- } catch (error) {
3652
- if (error instanceof LiquidiumError) {
3653
- throw error;
3654
- }
3655
- throw mapCanisterCallErrorToLiquidiumError("get_loan", error);
3656
- }
3742
+ const record = await this.getLoanRecord(loanId);
3743
+ const collateralAmount = await this.getCollateralAmountHint(loanId);
3744
+ return await this.mapLoanRecord(record, collateralAmount);
3745
+ }
3746
+ /**
3747
+ * Finds instant loans by short reference, numeric loan id string, address, or transaction id.
3748
+ *
3749
+ * Search returns lightweight matches. Call `get({ loanId })` or `get({ ref })`
3750
+ * when the user selects a match and you need hydrated loan state.
3751
+ *
3752
+ * @param query - Short reference, address, transaction id/hash, or numeric loan id string.
3753
+ * @returns Matching loan ids and references from the search index.
3754
+ */
3755
+ async find(query) {
3756
+ const validatedQuery = validateInstantLoanFindQuery(query);
3757
+ const candidates = await this.findCandidateLoansByQuery(validatedQuery);
3758
+ return uniqueInstantLoanFindCandidates(candidates).map((candidate) => ({
3759
+ loanId: candidate.loanId,
3760
+ ref: candidate.ref,
3761
+ createdAt: candidate.createdAt,
3762
+ collateral: candidate.collateral,
3763
+ borrow: candidate.borrow,
3764
+ profileId: candidate.profileId
3765
+ }));
3657
3766
  }
3658
3767
  /**
3659
3768
  * Returns the active instant-loans canister config via direct query.
@@ -3751,28 +3860,29 @@ var InstantLoansModule = class {
3751
3860
  throw mapCanisterCallErrorToLiquidiumError("list_warmed_profiles", error);
3752
3861
  }
3753
3862
  }
3754
- /**
3755
- * Finds candidate loans associated with an address through the Liquidium SDK
3756
- * API. Returns discovery candidates only; call `get(...)` to hydrate canister state.
3757
- *
3758
- * Candidates are useful for recovery flows where the user knows a borrow or
3759
- * refund address but not the loan reference.
3760
- *
3761
- * @param address - Borrow or refund address to search for.
3762
- * @returns Lightweight loan candidates associated with the address.
3763
- */
3764
- async findByAddress(address) {
3765
- const trimmedAddress = address.trim();
3766
- if (!trimmedAddress) {
3767
- throw new LiquidiumError(
3768
- LiquidiumErrorCode.VALIDATION_ERROR,
3769
- "Address lookup requires a non-empty address"
3770
- );
3771
- }
3772
- const apiClient = this.requireApi("Instant loan address lookup");
3773
- const response = await apiClient.get(buildInstantLoanAddressLookupPath({ address: trimmedAddress }));
3863
+ async findCandidateLoansByQuery(query) {
3864
+ const apiClient = this.requireApi("Instant loan find");
3865
+ const response = await apiClient.get(
3866
+ buildInstantLoanFindPath({ query })
3867
+ );
3774
3868
  return (response.candidates ?? response.loans ?? []).map(mapCandidateWire);
3775
3869
  }
3870
+ async getLoanRecord(loanId) {
3871
+ try {
3872
+ const result = await createInstantLoansActor(
3873
+ this.canisterContext
3874
+ ).get_loan(loanId);
3875
+ if ("Err" in result) {
3876
+ throw mapInstantLoansErrorToLiquidiumError(result.Err);
3877
+ }
3878
+ return result.Ok;
3879
+ } catch (error) {
3880
+ if (error instanceof LiquidiumError) {
3881
+ throw error;
3882
+ }
3883
+ throw mapCanisterCallErrorToLiquidiumError("get_loan", error);
3884
+ }
3885
+ }
3776
3886
  async mapLoanRecord(record, collateralAmount) {
3777
3887
  return await this.hydrateLoan({
3778
3888
  loanId: record.id,
@@ -3786,35 +3896,22 @@ var InstantLoansModule = class {
3786
3896
  borrowAsset: getVariantKey(record.borrow_asset),
3787
3897
  borrowAmount: record.borrow_amount,
3788
3898
  borrowDestination: accountFromCanister(record.borrow_destination),
3789
- refundDestination: accountFromCanister(record.refund_destination)
3899
+ refundDestination: accountFromCanister(record.refund_destination),
3900
+ depositDetectedTimestamp: record.deposit_detected_ts[0] ?? null,
3901
+ expiryTimestamp: record.expires_at[0] ?? deriveDepositExpiryTimestamp({
3902
+ depositDetectedTimestamp: record.deposit_detected_ts[0] ?? null,
3903
+ depositWindowSeconds: record.ltv_timer_s
3904
+ })
3790
3905
  });
3791
3906
  }
3792
3907
  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
- }
3797
- async mapLoanWire(loan) {
3798
- const loanId = parseBigintWire(loan.loanId, "loan ID");
3799
- return await this.hydrateLoan({
3800
- loanId,
3801
- profileId: loan.profileId,
3802
- ltvMaxBps: parseBigintWire(loan.ltvMaxBps, "max LTV"),
3803
- depositWindowSeconds: parseBigintWire(
3804
- loan.depositWindowSeconds,
3805
- "deposit window"
3806
- ),
3807
- collateralPoolId: loan.collateral.poolId,
3808
- collateralAmount: parseBigintWire(
3809
- loan.collateral.amountHint,
3810
- "collateral amount"
3811
- ),
3812
- borrowPoolId: loan.borrow.poolId,
3813
- collateralAsset: loan.collateral.asset,
3814
- borrowAsset: loan.borrow.asset,
3815
- borrowAmount: parseBigintWire(loan.borrow.amount, "borrow amount"),
3816
- borrowDestination: accountFromWire(loan.borrow.destination),
3817
- refundDestination: accountFromWire(loan.refundDestination)
3908
+ const apiClient = this.requireApi("Instant loan collateral hint");
3909
+ const response = await apiClient.get(
3910
+ buildInstantLoanCollateralHintPath({ loanId })
3911
+ );
3912
+ return parseUnsignedApiBigint(response.collateralAmountHint, {
3913
+ context: INSTANT_LOAN_WIRE_CONTEXT,
3914
+ label: "collateral amount"
3818
3915
  });
3819
3916
  }
3820
3917
  async hydrateLoan(input) {
@@ -3868,7 +3965,9 @@ var InstantLoansModule = class {
3868
3965
  collateralAmount,
3869
3966
  decimals: collateralDecimals,
3870
3967
  asset: collateralAsset,
3871
- target: depositTarget
3968
+ target: depositTarget,
3969
+ detectedTimestamp: input.depositDetectedTimestamp,
3970
+ expiryTimestamp: input.expiryTimestamp
3872
3971
  });
3873
3972
  const repayment = {
3874
3973
  amount: repaymentAmount,
@@ -3932,7 +4031,9 @@ var InstantLoansModule = class {
3932
4031
  inflowFeeAmount: inflowFee.totalFee,
3933
4032
  asset: input.asset,
3934
4033
  chain: input.target.chain,
3935
- target: input.target
4034
+ target: input.target,
4035
+ detectedTimestamp: input.detectedTimestamp,
4036
+ expiryTimestamp: input.expiryTimestamp
3936
4037
  };
3937
4038
  }
3938
4039
  async estimateRepaymentInflowFee(asset, chain) {
@@ -4028,6 +4129,12 @@ function deriveInstantLoanStatus(input) {
4028
4129
  }
4029
4130
  return InstantLoanStatus.awaitingDeposit;
4030
4131
  }
4132
+ function deriveDepositExpiryTimestamp(input) {
4133
+ if (input.depositDetectedTimestamp === null) {
4134
+ return null;
4135
+ }
4136
+ return input.depositDetectedTimestamp + input.depositWindowSeconds;
4137
+ }
4031
4138
  function validateCreateRequest(request) {
4032
4139
  if (request.collateralAmount <= 0n) {
4033
4140
  throw new LiquidiumError(
@@ -4076,18 +4183,6 @@ function accountFromCanister(account) {
4076
4183
  }
4077
4184
  return { type: "External", address: account.External };
4078
4185
  }
4079
- function accountFromWire(account) {
4080
- if ("Native" in account) {
4081
- return { type: "Native", principal: account.Native };
4082
- }
4083
- if ("External" in account) {
4084
- return { type: "External", address: account.External };
4085
- }
4086
- if (account.type === "Native") {
4087
- return { type: "Native", principal: account.principal };
4088
- }
4089
- return { type: "External", address: account.address };
4090
- }
4091
4186
  function mapInstantLoanEvent(event) {
4092
4187
  return {
4093
4188
  id: event.id,
@@ -4206,55 +4301,80 @@ function decodeRef(ref) {
4206
4301
  );
4207
4302
  }
4208
4303
  }
4209
- function mapCandidateWire(wire) {
4210
- const loanId = parseBigintWire(wire.loanId ?? wire.loan_id, "loan ID");
4211
- const ref = wire.ref ?? wire.short_ref ?? publicIdFromInt(loanId);
4212
- const createdAt = wire.createdAt ?? wire.created_at;
4213
- return {
4214
- loanId,
4215
- ref,
4216
- profileId: requiredString(
4217
- wire.profileId ?? wire.lending_profile,
4218
- "profile ID"
4219
- ),
4220
- ...createdAt ? { createdAt: new Date(createdAt) } : {},
4221
- collateralPoolId: requiredString(
4222
- wire.collateralPoolId ?? wire.lend_pool_ic_id,
4223
- "collateral pool ID"
4224
- ),
4225
- borrowPoolId: requiredString(
4226
- wire.borrowPoolId ?? wire.borrow_pool_ic_id,
4227
- "borrow pool ID"
4228
- ),
4229
- collateralAsset: requiredString(
4230
- wire.collateralAsset ?? wire.lend_asset,
4231
- "collateral asset"
4232
- ),
4233
- borrowAsset: requiredString(
4234
- wire.borrowAsset ?? wire.borrow_asset,
4235
- "borrow asset"
4236
- ),
4237
- collateralAmount: parseBigintWire(
4238
- wire.collateralAmount,
4239
- "collateral amount"
4240
- )
4241
- };
4242
- }
4243
- function parseBigintWire(value, label) {
4244
- if (!value || !/^\d+$/.test(value)) {
4304
+ function validateInstantLoanFindQuery(query) {
4305
+ if (typeof query !== "string") {
4245
4306
  throw new LiquidiumError(
4246
4307
  LiquidiumErrorCode.VALIDATION_ERROR,
4247
- `Invalid instant loan ${label}`
4308
+ "Instant loan find query must be a string"
4248
4309
  );
4249
4310
  }
4250
- return BigInt(value);
4311
+ const trimmedQuery = query.trim();
4312
+ if (!trimmedQuery) {
4313
+ throw new LiquidiumError(
4314
+ LiquidiumErrorCode.VALIDATION_ERROR,
4315
+ "Instant loan find query must be non-empty"
4316
+ );
4317
+ }
4318
+ if (trimmedQuery.length > INSTANT_LOAN_FIND_QUERY_MAX_LENGTH) {
4319
+ throw new LiquidiumError(
4320
+ LiquidiumErrorCode.VALIDATION_ERROR,
4321
+ `Instant loan find query must be at most ${INSTANT_LOAN_FIND_QUERY_MAX_LENGTH.toString()} characters`
4322
+ );
4323
+ }
4324
+ return trimmedQuery;
4251
4325
  }
4252
- function requiredString(value, label) {
4253
- if (value?.trim()) return value;
4254
- throw new LiquidiumError(
4255
- LiquidiumErrorCode.VALIDATION_ERROR,
4256
- `Missing instant loan ${label}`
4257
- );
4326
+ function uniqueInstantLoanFindCandidates(candidates) {
4327
+ const candidatesByLoanId = /* @__PURE__ */ new Map();
4328
+ for (const candidate of candidates) {
4329
+ if (!candidatesByLoanId.has(candidate.loanId)) {
4330
+ candidatesByLoanId.set(candidate.loanId, candidate);
4331
+ }
4332
+ }
4333
+ return [...candidatesByLoanId.values()];
4334
+ }
4335
+ function mapCandidateWire(wire) {
4336
+ return {
4337
+ loanId: parseUnsignedApiBigint(wire.loan_id, {
4338
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4339
+ label: "loan ID"
4340
+ }),
4341
+ ref: parseNonEmptyApiString(wire.short_ref, {
4342
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4343
+ label: "short reference"
4344
+ }),
4345
+ createdAt: parseIsoApiTimestampToUnixSeconds(wire.created_at, {
4346
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4347
+ label: "creation timestamp"
4348
+ }),
4349
+ collateral: {
4350
+ poolId: parseNonEmptyApiString(wire.lend_pool_ic_id, {
4351
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4352
+ label: "lend pool ID"
4353
+ }),
4354
+ asset: parseApiStringUnion(wire.lend_asset, INSTANT_LOAN_ASSETS, {
4355
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4356
+ label: "lend asset"
4357
+ }),
4358
+ amount: parseUnsignedApiBigint(wire.collateral_amount, {
4359
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4360
+ label: "collateral amount"
4361
+ })
4362
+ },
4363
+ borrow: {
4364
+ poolId: parseNonEmptyApiString(wire.borrow_pool_ic_id, {
4365
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4366
+ label: "borrow pool ID"
4367
+ }),
4368
+ asset: parseApiStringUnion(wire.borrow_asset, INSTANT_LOAN_ASSETS, {
4369
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4370
+ label: "borrow asset"
4371
+ })
4372
+ },
4373
+ profileId: parseNonEmptyApiString(wire.profile, {
4374
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4375
+ label: "profile ID"
4376
+ })
4377
+ };
4258
4378
  }
4259
4379
  function mapInstantLoansErrorToLiquidiumError(error) {
4260
4380
  const [key, payload] = Object.entries(error)[0];
@@ -4715,9 +4835,28 @@ var LendingModule = class {
4715
4835
  "Borrow requires a signer account"
4716
4836
  );
4717
4837
  }
4718
- const receiverAddress = await this.normalizeOutflowReceiverAddress({
4719
- poolId: request.poolId,
4720
- receiverAddress: destinationAccount
4838
+ if (request.amount <= 0n) {
4839
+ throw new LiquidiumError(
4840
+ LiquidiumErrorCode.VALIDATION_ERROR,
4841
+ "Borrow amount must be greater than 0"
4842
+ );
4843
+ }
4844
+ const selectedPool = await this.getPoolById(request.poolId);
4845
+ const selectedAsset = getVariantKey(selectedPool.asset);
4846
+ const minimumBorrowAmountError = getBorrowAmountMinimumValidationError({
4847
+ amount: request.amount,
4848
+ asset: selectedAsset
4849
+ });
4850
+ if (minimumBorrowAmountError) {
4851
+ throw new LiquidiumError(
4852
+ LiquidiumErrorCode.VALIDATION_ERROR,
4853
+ minimumBorrowAmountError.message
4854
+ );
4855
+ }
4856
+ const receiverAddress = normalizeExternalAddress({
4857
+ address: destinationAccount,
4858
+ asset: selectedAsset,
4859
+ chain: getVariantKey(selectedPool.chain)
4721
4860
  });
4722
4861
  const lendingActor = createLendingActor(this.canisterContext);
4723
4862
  try {
@@ -5840,7 +5979,7 @@ var PositionsModule = class {
5840
5979
  pool,
5841
5980
  priceUsd,
5842
5981
  suppliedUsd: nativeAmountToUsdScaled(
5843
- position.deposited + position.earnedInterest,
5982
+ position.deposited,
5844
5983
  position.depositedDecimals,
5845
5984
  priceUsd
5846
5985
  ),
@@ -5875,6 +6014,24 @@ var PositionsModule = class {
5875
6014
  const buffered = rawDebt * (BPS_SCALE + bufferBps) / BPS_SCALE;
5876
6015
  return { amount: buffered, decimals: position.borrowedDecimals };
5877
6016
  }
6017
+ /**
6018
+ * Returns the current full withdraw amount for a position.
6019
+ *
6020
+ * `Position.deposited` already reflects the current supplied balance at the
6021
+ * latest lending index; do not add `earnedInterest` to this amount.
6022
+ * Pass `amount` to withdraw calls and use `decimals` for display formatting.
6023
+ *
6024
+ * @param profileId - The Liquidium profile principal text.
6025
+ * @param poolId - The pool principal text.
6026
+ * @returns Full withdraw amount in the supplied asset's base units.
6027
+ */
6028
+ async getFullWithdrawAmount(profileId, poolId) {
6029
+ const position = await this.getPosition(profileId, poolId);
6030
+ if (!position) {
6031
+ return { amount: 0n, decimals: 0n };
6032
+ }
6033
+ return { amount: position.deposited, decimals: position.depositedDecimals };
6034
+ }
5878
6035
  };
5879
6036
  function nativeAmountToUsdScaled(amount, nativeDecimals, priceUsd) {
5880
6037
  if (amount <= 0n || priceUsd <= 0) {
@@ -5985,6 +6142,7 @@ exports.LendingModule = LendingModule;
5985
6142
  exports.LiquidiumClient = LiquidiumClient;
5986
6143
  exports.LiquidiumError = LiquidiumError;
5987
6144
  exports.LiquidiumErrorCode = LiquidiumErrorCode;
6145
+ exports.MIN_BORROW_AMOUNTS_BY_ASSET = MIN_BORROW_AMOUNTS_BY_ASSET;
5988
6146
  exports.MarketModule = MarketModule;
5989
6147
  exports.OutflowType = OutflowType;
5990
6148
  exports.PositionsModule = PositionsModule;
@@ -6002,6 +6160,7 @@ exports.UserHistoryStatus = UserHistoryStatus;
6002
6160
  exports.WalletExecutionKind = WalletExecutionKind;
6003
6161
  exports.createTransferErc20Transaction = createTransferErc20Transaction;
6004
6162
  exports.executeWith = executeWith;
6163
+ exports.getMinimumBorrowAmount = getMinimumBorrowAmount;
6005
6164
  exports.intFromPublicId = intFromPublicId;
6006
6165
  exports.publicIdFromInt = publicIdFromInt;
6007
6166
  //# sourceMappingURL=index.cjs.map