@liquidium/client 0.2.1 → 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/README.md CHANGED
@@ -38,8 +38,8 @@ Liquidium supports two borrowing and lending integration paths:
38
38
 
39
39
  | Path | Use when | Main SDK calls |
40
40
  | --- | --- | --- |
41
- | Recommended: accountless instant loans | You want a short checkout-style flow where users borrow against collateral without creating a Liquidium profile first | `client.instantLoans.create(...)`, `client.instantLoans.get(...)`, `client.activities.list(...)` |
42
- | Account-based profile flows | You want users to keep a Liquidium profile, manage positions across sessions, supply funds, borrow, withdraw, or use ETH contract-interaction deposits | `client.accounts.createProfile(...)`, `client.lending.supply(...)`, `client.lending.borrow(...)`, `client.lending.withdraw(...)`, `client.positions.list(...)` |
41
+ | Recommended: accountless instant loans | You want a short checkout-style flow where users borrow against collateral without creating a Liquidium profile first | `client.instantLoans.create(...)`, `client.instantLoans.get(...)`, `client.instantLoans.find(...)`, `client.activities.list(...)` |
42
+ | Account-based profile flows | You want users to keep a Liquidium profile, manage positions across sessions, supply funds, borrow, withdraw, or use ETH contract-interaction deposits | `client.accounts.createProfile(...)`, `client.lending.supply(...)`, `client.lending.borrow(...)`, `client.lending.withdraw(...)`, `client.positions.listPositions(...)` |
43
43
 
44
44
  Use accountless instant loans for new borrow flows unless you need profile-level position management. Use account-based flows when your app owns the full lending dashboard experience.
45
45
 
@@ -118,6 +118,11 @@ const activities = await client.activities.list({ shortRef: loan.ref });
118
118
 
119
119
  console.log("Loan activities:", activities);
120
120
 
121
+ const foundLoans = await client.instantLoans.find(loan.ref);
122
+
123
+ console.log("Found loan reference:", foundLoans[0]?.ref);
124
+ console.log("Found loan collateral:", foundLoans[0]?.collateral.amount.toString());
125
+
121
126
  function requirePool(pools: Pool[], asset: string): Pool {
122
127
  const pool = pools.find((candidatePool) => candidatePool.asset === asset);
123
128
 
@@ -150,7 +155,7 @@ Instant-loan integrations use this sequence:
150
155
  | Load market data | `client.market.listPools()` and `client.market.getAssetPrices()` | Show supported collateral and borrow assets |
151
156
  | Validate amounts | `client.quote.calculateLtv(...)` | Block too-small borrow amounts, invalid LTV, or frozen-pool input before creating a loan |
152
157
  | Create loan | `client.instantLoans.create(...)` | Store `loan.ref` and show `loan.initialDeposit.amount` plus `loan.initialDeposit.target` |
153
- | Track loan | `client.instantLoans.get({ ref })` and `client.activities.list({ shortRef: ref })` | Reload loan state, initial deposit quote, and repayment activity |
158
+ | Track loan | `client.instantLoans.get({ ref })`, `client.instantLoans.find(...)`, and `client.activities.list({ shortRef: ref })` | Reload loan state, initial deposit quote, and repayment activity |
154
159
  | Repay loan | Read `loan.repayment` | Ask the user to send `loan.repayment.amount` to `loan.repayment.target`; amount is `0n` when no repayment is due |
155
160
 
156
161
  `client.instantLoans.create(...)` and `client.instantLoans.get(...)` return the generated Liquidium profile, current position state, an initial deposit quote with its transfer target, and a repayment quote with its transfer target. Repayment amount fields are zero when the loan has no debt. Users do not manage the generated profile.
@@ -188,11 +193,33 @@ Loads loan state by numeric canister loan id.
188
193
 
189
194
  Use this when your backend stores the numeric id instead of the short reference.
190
195
 
191
- ### `client.instantLoans.findByAddress(address)`
196
+ ### `client.instantLoans.find(query)`
197
+
198
+ Finds instant loans by short reference, numeric canister loan id string, generated deposit or repayment address, borrow or refund destination address, or indexed transaction id/hash through the SDK API search index.
199
+
200
+ Use this for recovery and manage pages where the user may paste any loan identifier. The method returns lightweight matches because address and transaction-id lookups can match many loans. Use `client.instantLoans.get(...)` after the user selects a match.
201
+
202
+ ```ts
203
+ const results = await client.instantLoans.find("bc1q...");
204
+ const byRef = await client.instantLoans.find("ABC123");
205
+ const byLoanId = await client.instantLoans.find("42");
206
+
207
+ for (const result of results) {
208
+ console.log(result.ref);
209
+ console.log(result.loanId);
210
+ console.log(result.collateral.asset, result.borrow.asset);
211
+ console.log(result.collateral.amount);
212
+ }
213
+
214
+ const selectedLoan = await client.instantLoans.get({ loanId: results[0].loanId });
215
+ ```
192
216
 
193
- Finds candidate loans associated with a borrow or refund address.
217
+ Call `get(...)` when you already have an exact canister identifier and want direct canister lookup without an array:
194
218
 
195
- Use this only for recovery. The method returns candidate matches; call `client.instantLoans.get({ ref })` or `client.instantLoans.get({ loanId })` before showing loan state or transfer targets.
219
+ ```ts
220
+ const loanById = await client.instantLoans.get({ loanId: 123n });
221
+ const loanByRef = await client.instantLoans.get({ ref: "ABC123" });
222
+ ```
196
223
 
197
224
  ### `client.quote.calculateLtv(request, pools, prices)`
198
225
 
@@ -212,6 +239,10 @@ The SDK enforces product minimums before borrow creation:
212
239
 
213
240
  Use `getMinimumBorrowAmount(asset)` to display the same minimum that `client.quote.calculateLtv(...)`, `client.quote.getQuote(...)`, `client.instantLoans.create(...)`, and `client.lending.prepareBorrow(...)` enforce.
214
241
 
242
+ ### Profile full withdraw amounts
243
+
244
+ For profile-based withdraw flows, call `client.positions.getFullWithdrawAmount(profileId, poolId)` before building the withdraw request. The helper returns `{ amount, decimals }`: pass `amount` to `client.lending.withdraw(...)` or `client.lending.prepareWithdraw(...)`, and use `decimals` for display formatting. Do not add `earnedInterest`; the returned amount already uses the current supplied balance.
245
+
215
246
  ## Response Fields
216
247
 
217
248
  Most instant-loan UIs show or store these fields:
@@ -223,10 +254,14 @@ Most instant-loan UIs show or store these fields:
223
254
  | `loan.initialDeposit.amount` | Fee-inclusive collateral amount to send after creation or restore |
224
255
  | `loan.initialDeposit.collateralAmount` | Intended credited collateral target used for LTV |
225
256
  | `loan.initialDeposit.target` | Address or ICRC account where the user sends collateral |
257
+ | `loan.initialDeposit.detectedTimestamp` | Unix timestamp in seconds when the collateral deposit was detected, or `null` before detection |
258
+ | `loan.initialDeposit.expiryTimestamp` | Unix timestamp in seconds when the collateral deposit window expires, or `null` before detection when unavailable |
226
259
  | `loan.repayment.amount` | Full amount to repay, including fee and interest buffer. Zero when no repayment is due |
227
260
  | `loan.repayment.target` | Address or ICRC account where the user sends repayment |
228
261
  | `loan.position` | Current collateral, debt, and interest state for the generated profile |
229
262
 
263
+ `client.instantLoans.find(...)` returns lightweight search matches with `loanId`, `ref`, `createdAt`, `profileId`, `collateral`, and `borrow`. Use `client.instantLoans.get(...)` to load full loan fields, and use `client.activities.list(...)` separately when you need deposit, borrow, repay, or withdraw activity.
264
+
230
265
  ## Amounts
231
266
 
232
267
  The SDK returns amount fields as `bigint` values in the asset's smallest unit.
@@ -266,7 +301,7 @@ Start browser integrations with the examples.
266
301
 
267
302
  | Example | What it shows |
268
303
  | --- | --- |
269
- | [`examples/instant-loans-flow`](https://github.com/Liquidium-Inc/liquidium-sdk/tree/main/examples/instant-loans-flow) | Accountless instant loan UX with manual destination addresses, pool selection, LTV preview, loan creation, status reload, activity status, and address recovery |
304
+ | [`examples/instant-loans-flow`](https://github.com/Liquidium-Inc/liquidium-sdk/tree/main/examples/instant-loans-flow) | Accountless instant loan UX with manual destination addresses, pool selection, LTV preview, loan creation, status reload, activity status, and loan recovery |
270
305
  | [`examples/sdk-method-query`](https://github.com/Liquidium-Inc/liquidium-sdk/tree/main/examples/sdk-method-query) | Developer tool for calling SDK methods, including instant loan method templates |
271
306
 
272
307
  Run the instant loan example:
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,
@@ -3615,6 +3650,14 @@ var RATE_SCALE2 = 10n ** 27n;
3615
3650
  var SECONDS_PER_YEAR = 31536000n;
3616
3651
  var ETH_STABLECOIN_INFLOW_FEE_FALLBACK = 1500000n;
3617
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
+ ];
3618
3661
  var InstantLoansModule = class {
3619
3662
  constructor(canisterContext, apiClient, lending, positions) {
3620
3663
  this.canisterContext = canisterContext;
@@ -3671,7 +3714,19 @@ var InstantLoansModule = class {
3671
3714
  borrowDestination,
3672
3715
  refundDestination
3673
3716
  });
3674
- 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);
3675
3730
  }
3676
3731
  /**
3677
3732
  * Resolves canonical canister state by loan id or short reference.
@@ -3684,21 +3739,30 @@ var InstantLoansModule = class {
3684
3739
  */
3685
3740
  async get(request) {
3686
3741
  const loanId = "loanId" in request ? request.loanId : decodeRef(request.ref);
3687
- try {
3688
- const result = await createInstantLoansActor(
3689
- this.canisterContext
3690
- ).get_loan(loanId);
3691
- if ("Err" in result) {
3692
- throw mapInstantLoansErrorToLiquidiumError(result.Err);
3693
- }
3694
- const collateralAmount = await this.getCollateralAmountHint(loanId);
3695
- return await this.mapLoanRecord(result.Ok, collateralAmount);
3696
- } catch (error) {
3697
- if (error instanceof LiquidiumError) {
3698
- throw error;
3699
- }
3700
- throw mapCanisterCallErrorToLiquidiumError("get_loan", error);
3701
- }
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
+ }));
3702
3766
  }
3703
3767
  /**
3704
3768
  * Returns the active instant-loans canister config via direct query.
@@ -3796,30 +3860,29 @@ var InstantLoansModule = class {
3796
3860
  throw mapCanisterCallErrorToLiquidiumError("list_warmed_profiles", error);
3797
3861
  }
3798
3862
  }
3799
- /**
3800
- * Finds candidate loans associated with an address through the Liquidium SDK
3801
- * API. Returns discovery candidates only; call `get(...)` to hydrate canister state.
3802
- *
3803
- * Candidates are useful for recovery flows where the user knows a borrow or
3804
- * refund address but not the loan reference.
3805
- *
3806
- * @param address - Borrow or refund address to search for.
3807
- * @returns Lightweight loan candidates associated with the address.
3808
- */
3809
- async findByAddress(address) {
3810
- const trimmedAddress = address.trim();
3811
- if (!trimmedAddress) {
3812
- throw new LiquidiumError(
3813
- LiquidiumErrorCode.VALIDATION_ERROR,
3814
- "Address lookup requires a non-empty address"
3815
- );
3816
- }
3817
- const apiClient = this.requireApi("Instant loan address lookup");
3863
+ async findCandidateLoansByQuery(query) {
3864
+ const apiClient = this.requireApi("Instant loan find");
3818
3865
  const response = await apiClient.get(
3819
- buildInstantLoanAddressLookupPath({ address: trimmedAddress })
3866
+ buildInstantLoanFindPath({ query })
3820
3867
  );
3821
3868
  return (response.candidates ?? response.loans ?? []).map(mapCandidateWire);
3822
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
+ }
3823
3886
  async mapLoanRecord(record, collateralAmount) {
3824
3887
  return await this.hydrateLoan({
3825
3888
  loanId: record.id,
@@ -3833,37 +3896,22 @@ var InstantLoansModule = class {
3833
3896
  borrowAsset: getVariantKey(record.borrow_asset),
3834
3897
  borrowAmount: record.borrow_amount,
3835
3898
  borrowDestination: accountFromCanister(record.borrow_destination),
3836
- 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
+ })
3837
3905
  });
3838
3906
  }
3839
3907
  async getCollateralAmountHint(loanId) {
3840
- const apiClient = this.requireApi("Instant loan lookup");
3908
+ const apiClient = this.requireApi("Instant loan collateral hint");
3841
3909
  const response = await apiClient.get(
3842
3910
  buildInstantLoanCollateralHintPath({ loanId })
3843
3911
  );
3844
- return parseBigintWire(response.collateralAmountHint, "collateral amount");
3845
- }
3846
- async mapLoanWire(loan) {
3847
- const loanId = parseBigintWire(loan.loanId, "loan ID");
3848
- return await this.hydrateLoan({
3849
- loanId,
3850
- profileId: loan.profileId,
3851
- ltvMaxBps: parseBigintWire(loan.ltvMaxBps, "max LTV"),
3852
- depositWindowSeconds: parseBigintWire(
3853
- loan.depositWindowSeconds,
3854
- "deposit window"
3855
- ),
3856
- collateralPoolId: loan.collateral.poolId,
3857
- collateralAmount: parseBigintWire(
3858
- loan.collateral.amountHint,
3859
- "collateral amount"
3860
- ),
3861
- borrowPoolId: loan.borrow.poolId,
3862
- collateralAsset: loan.collateral.asset,
3863
- borrowAsset: loan.borrow.asset,
3864
- borrowAmount: parseBigintWire(loan.borrow.amount, "borrow amount"),
3865
- borrowDestination: accountFromWire(loan.borrow.destination),
3866
- refundDestination: accountFromWire(loan.refundDestination)
3912
+ return parseUnsignedApiBigint(response.collateralAmountHint, {
3913
+ context: INSTANT_LOAN_WIRE_CONTEXT,
3914
+ label: "collateral amount"
3867
3915
  });
3868
3916
  }
3869
3917
  async hydrateLoan(input) {
@@ -3917,7 +3965,9 @@ var InstantLoansModule = class {
3917
3965
  collateralAmount,
3918
3966
  decimals: collateralDecimals,
3919
3967
  asset: collateralAsset,
3920
- target: depositTarget
3968
+ target: depositTarget,
3969
+ detectedTimestamp: input.depositDetectedTimestamp,
3970
+ expiryTimestamp: input.expiryTimestamp
3921
3971
  });
3922
3972
  const repayment = {
3923
3973
  amount: repaymentAmount,
@@ -3981,7 +4031,9 @@ var InstantLoansModule = class {
3981
4031
  inflowFeeAmount: inflowFee.totalFee,
3982
4032
  asset: input.asset,
3983
4033
  chain: input.target.chain,
3984
- target: input.target
4034
+ target: input.target,
4035
+ detectedTimestamp: input.detectedTimestamp,
4036
+ expiryTimestamp: input.expiryTimestamp
3985
4037
  };
3986
4038
  }
3987
4039
  async estimateRepaymentInflowFee(asset, chain) {
@@ -4077,6 +4129,12 @@ function deriveInstantLoanStatus(input) {
4077
4129
  }
4078
4130
  return InstantLoanStatus.awaitingDeposit;
4079
4131
  }
4132
+ function deriveDepositExpiryTimestamp(input) {
4133
+ if (input.depositDetectedTimestamp === null) {
4134
+ return null;
4135
+ }
4136
+ return input.depositDetectedTimestamp + input.depositWindowSeconds;
4137
+ }
4080
4138
  function validateCreateRequest(request) {
4081
4139
  if (request.collateralAmount <= 0n) {
4082
4140
  throw new LiquidiumError(
@@ -4125,18 +4183,6 @@ function accountFromCanister(account) {
4125
4183
  }
4126
4184
  return { type: "External", address: account.External };
4127
4185
  }
4128
- function accountFromWire(account) {
4129
- if ("Native" in account) {
4130
- return { type: "Native", principal: account.Native };
4131
- }
4132
- if ("External" in account) {
4133
- return { type: "External", address: account.External };
4134
- }
4135
- if (account.type === "Native") {
4136
- return { type: "Native", principal: account.principal };
4137
- }
4138
- return { type: "External", address: account.address };
4139
- }
4140
4186
  function mapInstantLoanEvent(event) {
4141
4187
  return {
4142
4188
  id: event.id,
@@ -4255,55 +4301,80 @@ function decodeRef(ref) {
4255
4301
  );
4256
4302
  }
4257
4303
  }
4258
- function mapCandidateWire(wire) {
4259
- const loanId = parseBigintWire(wire.loanId ?? wire.loan_id, "loan ID");
4260
- const ref = wire.ref ?? wire.short_ref ?? publicIdFromInt(loanId);
4261
- const createdAt = wire.createdAt ?? wire.created_at;
4262
- return {
4263
- loanId,
4264
- ref,
4265
- profileId: requiredString(
4266
- wire.profileId ?? wire.lending_profile,
4267
- "profile ID"
4268
- ),
4269
- ...createdAt ? { createdAt: new Date(createdAt) } : {},
4270
- collateralPoolId: requiredString(
4271
- wire.collateralPoolId ?? wire.lend_pool_ic_id,
4272
- "collateral pool ID"
4273
- ),
4274
- borrowPoolId: requiredString(
4275
- wire.borrowPoolId ?? wire.borrow_pool_ic_id,
4276
- "borrow pool ID"
4277
- ),
4278
- collateralAsset: requiredString(
4279
- wire.collateralAsset ?? wire.lend_asset,
4280
- "collateral asset"
4281
- ),
4282
- borrowAsset: requiredString(
4283
- wire.borrowAsset ?? wire.borrow_asset,
4284
- "borrow asset"
4285
- ),
4286
- collateralAmount: parseBigintWire(
4287
- wire.collateralAmount,
4288
- "collateral amount"
4289
- )
4290
- };
4291
- }
4292
- function parseBigintWire(value, label) {
4293
- if (!value || !/^\d+$/.test(value)) {
4304
+ function validateInstantLoanFindQuery(query) {
4305
+ if (typeof query !== "string") {
4294
4306
  throw new LiquidiumError(
4295
4307
  LiquidiumErrorCode.VALIDATION_ERROR,
4296
- `Invalid instant loan ${label}`
4308
+ "Instant loan find query must be a string"
4297
4309
  );
4298
4310
  }
4299
- 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;
4300
4325
  }
4301
- function requiredString(value, label) {
4302
- if (value?.trim()) return value;
4303
- throw new LiquidiumError(
4304
- LiquidiumErrorCode.VALIDATION_ERROR,
4305
- `Missing instant loan ${label}`
4306
- );
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
+ };
4307
4378
  }
4308
4379
  function mapInstantLoansErrorToLiquidiumError(error) {
4309
4380
  const [key, payload] = Object.entries(error)[0];
@@ -5908,7 +5979,7 @@ var PositionsModule = class {
5908
5979
  pool,
5909
5980
  priceUsd,
5910
5981
  suppliedUsd: nativeAmountToUsdScaled(
5911
- position.deposited + position.earnedInterest,
5982
+ position.deposited,
5912
5983
  position.depositedDecimals,
5913
5984
  priceUsd
5914
5985
  ),
@@ -5943,6 +6014,24 @@ var PositionsModule = class {
5943
6014
  const buffered = rawDebt * (BPS_SCALE + bufferBps) / BPS_SCALE;
5944
6015
  return { amount: buffered, decimals: position.borrowedDecimals };
5945
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
+ }
5946
6035
  };
5947
6036
  function nativeAmountToUsdScaled(amount, nativeDecimals, priceUsd) {
5948
6037
  if (amount <= 0n || priceUsd <= 0) {