@liquidium/client 0.6.0 → 0.7.1

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.js CHANGED
@@ -45,6 +45,7 @@ var LiquidiumErrorCode = {
45
45
  WITHDRAW_TOO_LOW: "WITHDRAW_TOO_LOW",
46
46
  REPAYMENT_EXCEEDS_DEBT: "REPAYMENT_EXCEEDS_DEBT",
47
47
  INVALID_ADDRESS: "INVALID_ADDRESS",
48
+ CONTRACT_DESTINATION_UNSUPPORTED: "CONTRACT_DESTINATION_UNSUPPORTED",
48
49
  DEPOSIT_ADDRESS_ERROR: "DEPOSIT_ADDRESS_ERROR",
49
50
  // Transport
50
51
  NETWORK_ERROR: "NETWORK_ERROR",
@@ -1053,8 +1054,11 @@ function normalizeEvmAddress(address) {
1053
1054
  // src/core/utils/time.ts
1054
1055
  var MILLISECONDS_PER_SECOND = 1e3;
1055
1056
  var SIGNATURE_VALIDITY_5_MINUTES_IN_SECONDS = 5n * 60n;
1057
+ function getCurrentUnixTimestampSeconds() {
1058
+ return BigInt(Math.floor(Date.now() / MILLISECONDS_PER_SECOND));
1059
+ }
1056
1060
  function computeExpiryTimestampFromNow(signatureValidityInSeconds = SIGNATURE_VALIDITY_5_MINUTES_IN_SECONDS) {
1057
- return BigInt(Math.floor(Date.now() / MILLISECONDS_PER_SECOND)) + signatureValidityInSeconds;
1061
+ return getCurrentUnixTimestampSeconds() + signatureValidityInSeconds;
1058
1062
  }
1059
1063
 
1060
1064
  // src/core/utils/variant.ts
@@ -1274,6 +1278,31 @@ var AccountsModule = class {
1274
1278
  throw mapCanisterCallErrorToLiquidiumError("get_wallet_profile", error);
1275
1279
  }
1276
1280
  }
1281
+ /**
1282
+ * Checks whether a profile is registered with the protocol.
1283
+ *
1284
+ * Production profile registration always links an initial wallet, and the
1285
+ * protocol prevents removal of a profile's final wallet. This method uses
1286
+ * that invariant because the current canister API has no direct existence
1287
+ * query.
1288
+ *
1289
+ * @param profileId - The Liquidium profile principal text.
1290
+ * @returns `true` when the profile has at least one linked wallet.
1291
+ */
1292
+ async profileExists(profileId) {
1293
+ try {
1294
+ const profilePrincipal = parseProfilePrincipal(profileId);
1295
+ const wallets = await createLendingActor(
1296
+ this.canisterContext
1297
+ ).get_profile_wallets(profilePrincipal);
1298
+ return wallets.length > 0;
1299
+ } catch (error) {
1300
+ if (error instanceof LiquidiumError) {
1301
+ throw error;
1302
+ }
1303
+ throw mapCanisterCallErrorToLiquidiumError("get_profile_wallets", error);
1304
+ }
1305
+ }
1277
1306
  /**
1278
1307
  * Returns the current nonce for a wallet address.
1279
1308
  *
@@ -1379,6 +1408,17 @@ var AccountsModule = class {
1379
1408
  function normalizeProfileAccount(account) {
1380
1409
  return normalizeEvmAddress(account);
1381
1410
  }
1411
+ function parseProfilePrincipal(profileId) {
1412
+ try {
1413
+ return Principal.fromText(profileId);
1414
+ } catch (error) {
1415
+ throw new LiquidiumError(
1416
+ LiquidiumErrorCode.VALIDATION_ERROR,
1417
+ `Invalid profile id: ${profileId}`,
1418
+ error
1419
+ );
1420
+ }
1421
+ }
1382
1422
  function createInitializeAccountMessage(expiryTimestamp, nonce) {
1383
1423
  return `Liquidium: Initialize Account
1384
1424
 
@@ -1742,19 +1782,29 @@ var SdkApiQueryParam = {
1742
1782
  to: "to"
1743
1783
  };
1744
1784
  var ACTIVITIES = `${SDK_API_V2_PATH}/activities`;
1785
+ var HISTORY_ACTIVITIES = `${SDK_API_V2_PATH}/history/activities`;
1745
1786
  var HISTORY_USERS = `${SDK_API_V2_PATH}/history/users`;
1746
1787
  var INFLOW = `${SDK_API_V2_PATH}/inflow`;
1747
1788
  var SIMPLE_LOANS = `${SDK_API_V1_PATH}/instant-loans`;
1748
- function buildHistoryUserTransactionsPath(user, query) {
1749
- const base = `${HISTORY_USERS}/${encodeURIComponent(user)}/transactions`;
1789
+ function buildEthereumAddressBytecodePath(request) {
1790
+ return `${SDK_API_V2_PATH}/ethereum/addresses/${encodeURIComponent(
1791
+ request.address
1792
+ )}/bytecode`;
1793
+ }
1794
+ function buildHistoryUserTransactionsPath(profileId, query) {
1795
+ const base = `${HISTORY_USERS}/${encodeURIComponent(profileId)}/transactions`;
1750
1796
  const qs = query.toString();
1751
1797
  return qs ? `${base}?${qs}` : base;
1752
1798
  }
1753
- function buildHistoryUserLiquidationsPath(user, query) {
1754
- const base = `${HISTORY_USERS}/${encodeURIComponent(user)}/liquidations`;
1799
+ function buildHistoryUserLiquidationsPath(profileId, query) {
1800
+ const base = `${HISTORY_USERS}/${encodeURIComponent(profileId)}/liquidations`;
1755
1801
  const qs = query.toString();
1756
1802
  return qs ? `${base}?${qs}` : base;
1757
1803
  }
1804
+ function buildProtocolActivityPath(query) {
1805
+ const qs = query.toString();
1806
+ return qs ? `${HISTORY_ACTIVITIES}?${qs}` : HISTORY_ACTIVITIES;
1807
+ }
1758
1808
  function buildActivitiesPath(request) {
1759
1809
  const query = new URLSearchParams({
1760
1810
  [SdkApiQueryParam.profileId]: request.profileId
@@ -2053,6 +2103,9 @@ function mapActivityTopUp(wire) {
2053
2103
  }
2054
2104
 
2055
2105
  // src/modules/history/history.ts
2106
+ var MIN_HISTORY_LIMIT = 1;
2107
+ var MAX_USER_HISTORY_LIMIT = 200;
2108
+ var MAX_PROTOCOL_ACTIVITY_LIMIT = 100;
2056
2109
  var HistoryModule = class {
2057
2110
  constructor(apiClient) {
2058
2111
  this.apiClient = apiClient;
@@ -2070,11 +2123,11 @@ var HistoryModule = class {
2070
2123
  /**
2071
2124
  * Returns transaction history for a user.
2072
2125
  *
2073
- * @param user - The Liquidium profile principal text.
2126
+ * @param profileId - The Liquidium profile principal text.
2074
2127
  * @param filters - Optional pool, operation, state, time range, and pagination filters.
2075
2128
  * @returns Paginated user history entries.
2076
2129
  */
2077
- async getUserTransactionHistory(user, filters = {}) {
2130
+ async getUserTransactionHistory(profileId, filters = {}) {
2078
2131
  const apiClient = this.requireApi();
2079
2132
  const query = new URLSearchParams();
2080
2133
  if (filters.cursor) {
@@ -2102,9 +2155,10 @@ var HistoryModule = class {
2102
2155
  query.set(SdkApiQueryParam.to, filters.to);
2103
2156
  }
2104
2157
  if (filters.limit !== void 0) {
2158
+ validateHistoryLimit(filters.limit, MAX_USER_HISTORY_LIMIT);
2105
2159
  query.set(SdkApiQueryParam.limit, String(filters.limit));
2106
2160
  }
2107
- const requestPath = buildHistoryUserTransactionsPath(user, query);
2161
+ const requestPath = buildHistoryUserTransactionsPath(profileId, query);
2108
2162
  const response = await apiClient.get(requestPath);
2109
2163
  return {
2110
2164
  items: response.items.map(mapUserTransactionHistoryEntry),
@@ -2114,11 +2168,11 @@ var HistoryModule = class {
2114
2168
  /**
2115
2169
  * Returns liquidation history for a user.
2116
2170
  *
2117
- * @param user - The Liquidium profile principal text.
2171
+ * @param profileId - The Liquidium profile principal text.
2118
2172
  * @param filters - Optional pool, time range, and pagination filters.
2119
2173
  * @returns Paginated liquidation history entries.
2120
2174
  */
2121
- async getLiquidationHistory(user, filters = {}) {
2175
+ async getLiquidationHistory(profileId, filters = {}) {
2122
2176
  const apiClient = this.requireApi();
2123
2177
  const query = new URLSearchParams();
2124
2178
  if (filters.cursor) {
@@ -2137,16 +2191,52 @@ var HistoryModule = class {
2137
2191
  query.set(SdkApiQueryParam.to, filters.to);
2138
2192
  }
2139
2193
  if (filters.limit !== void 0) {
2194
+ validateHistoryLimit(filters.limit, MAX_USER_HISTORY_LIMIT);
2140
2195
  query.set(SdkApiQueryParam.limit, String(filters.limit));
2141
2196
  }
2142
- const requestPath = buildHistoryUserLiquidationsPath(user, query);
2197
+ const requestPath = buildHistoryUserLiquidationsPath(profileId, query);
2143
2198
  const response = await apiClient.get(requestPath);
2144
2199
  return {
2145
2200
  items: response.items.map(mapUserLiquidationHistoryEntry),
2146
2201
  nextCursor: response.nextCursor
2147
2202
  };
2148
2203
  }
2204
+ /**
2205
+ * Returns recent protocol-wide lending activity across all users.
2206
+ *
2207
+ * @param filters - Optional pool, operation, and limit filters.
2208
+ * @returns Recent confirmed lending activity entries.
2209
+ */
2210
+ async getProtocolActivity(filters = {}) {
2211
+ const apiClient = this.requireApi();
2212
+ const query = new URLSearchParams();
2213
+ if (filters.poolId) {
2214
+ query.set(SdkApiQueryParam.poolId, filters.poolId);
2215
+ }
2216
+ if (filters.operations?.length) {
2217
+ query.set(SdkApiQueryParam.operations, filters.operations.join(","));
2218
+ }
2219
+ if (filters.limit !== void 0) {
2220
+ validateHistoryLimit(filters.limit, MAX_PROTOCOL_ACTIVITY_LIMIT);
2221
+ query.set(SdkApiQueryParam.limit, String(filters.limit));
2222
+ }
2223
+ const requestPath = buildProtocolActivityPath(query);
2224
+ const response = await apiClient.get(requestPath);
2225
+ return response.items.map(mapProtocolActivityEntry);
2226
+ }
2149
2227
  };
2228
+ function mapProtocolActivityEntry(item) {
2229
+ return {
2230
+ id: item.id,
2231
+ operation: item.operation,
2232
+ poolId: item.poolId,
2233
+ asset: item.asset,
2234
+ decimals: item.decimals,
2235
+ amount: parseBigInt(item.amount, "protocol activity amount"),
2236
+ timestamp: item.timestamp,
2237
+ txids: item.txids
2238
+ };
2239
+ }
2150
2240
  function mapUserTransactionHistoryEntry(item) {
2151
2241
  return {
2152
2242
  id: item.id,
@@ -2169,7 +2259,12 @@ function mapUserLiquidationHistoryEntry(item) {
2169
2259
  amount: parseBigInt(item.amount, "history user amount"),
2170
2260
  poolId: item.poolId,
2171
2261
  timestamp: item.timestamp,
2172
- status: item.status,
2262
+ status: {
2263
+ operation: "liquidation",
2264
+ state: "completed",
2265
+ confirmations: null,
2266
+ requiredConfirmations: null
2267
+ },
2173
2268
  txids: item.txids
2174
2269
  };
2175
2270
  }
@@ -2194,6 +2289,15 @@ function validateHistoryStateFilter(state) {
2194
2289
  );
2195
2290
  }
2196
2291
  }
2292
+ function validateHistoryLimit(limit, maximumLimit) {
2293
+ if (Number.isInteger(limit) && limit >= MIN_HISTORY_LIMIT && limit <= maximumLimit) {
2294
+ return;
2295
+ }
2296
+ throw new LiquidiumError(
2297
+ LiquidiumErrorCode.VALIDATION_ERROR,
2298
+ `History limit must be an integer between ${MIN_HISTORY_LIMIT} and ${maximumLimit}`
2299
+ );
2300
+ }
2197
2301
 
2198
2302
  // src/core/utils/inflow-subaccount.ts
2199
2303
  var INFLOW_DEPOSIT_PREFIX = 1;
@@ -3243,6 +3347,71 @@ function accountTypeToString(accountType) {
3243
3347
  return `Principal:${accountType.data}`;
3244
3348
  }
3245
3349
  }
3350
+ var EMPTY_EVM_BYTECODE = "0x";
3351
+ var LAST_ETHEREUM_MAINNET_PRECOMPILE_ADDRESS = 0x11n;
3352
+ var CONTRACT_DESTINATION_UNSUPPORTED_MESSAGE = "Contract addresses are not supported for native ETH withdrawals or borrowing";
3353
+ var RESERVED_DESTINATION_MESSAGE = "Ethereum outflow destination must not be the zero address or a precompile";
3354
+ async function guardEthereumOutflowDestination({
3355
+ address,
3356
+ apiClient,
3357
+ asset,
3358
+ chain,
3359
+ evmReadClient
3360
+ }) {
3361
+ if (chain !== Chain.ETH) {
3362
+ return;
3363
+ }
3364
+ const normalizedAddress = getAddress(address);
3365
+ if (BigInt(normalizedAddress) <= LAST_ETHEREUM_MAINNET_PRECOMPILE_ADDRESS) {
3366
+ throw new LiquidiumError(
3367
+ LiquidiumErrorCode.INVALID_ADDRESS,
3368
+ RESERVED_DESTINATION_MESSAGE
3369
+ );
3370
+ }
3371
+ if (asset !== Asset.ETH) {
3372
+ return;
3373
+ }
3374
+ if (evmReadClient?.chain?.id === mainnet.id && evmReadClient.getCode) {
3375
+ let bytecode;
3376
+ try {
3377
+ bytecode = await evmReadClient.getCode({
3378
+ address: normalizedAddress
3379
+ });
3380
+ } catch {
3381
+ return;
3382
+ }
3383
+ if (bytecode === void 0 || bytecode === EMPTY_EVM_BYTECODE) {
3384
+ return;
3385
+ }
3386
+ if (typeof bytecode !== "string" || !/^0x(?:[0-9a-fA-F]{2})*$/.test(bytecode)) {
3387
+ return;
3388
+ }
3389
+ throw new LiquidiumError(
3390
+ LiquidiumErrorCode.CONTRACT_DESTINATION_UNSUPPORTED,
3391
+ CONTRACT_DESTINATION_UNSUPPORTED_MESSAGE
3392
+ );
3393
+ }
3394
+ if (!apiClient) {
3395
+ return;
3396
+ }
3397
+ let response;
3398
+ try {
3399
+ response = await apiClient.get(
3400
+ buildEthereumAddressBytecodePath({ address: normalizedAddress })
3401
+ );
3402
+ } catch {
3403
+ return;
3404
+ }
3405
+ if (!response || typeof response !== "object" || !("hasDeployedBytecode" in response) || typeof response.hasDeployedBytecode !== "boolean") {
3406
+ return;
3407
+ }
3408
+ if (response.hasDeployedBytecode) {
3409
+ throw new LiquidiumError(
3410
+ LiquidiumErrorCode.CONTRACT_DESTINATION_UNSUPPORTED,
3411
+ CONTRACT_DESTINATION_UNSUPPORTED_MESSAGE
3412
+ );
3413
+ }
3414
+ }
3246
3415
 
3247
3416
  // src/core/pool-ledger-assets.ts
3248
3417
  function getPoolLedgerAssetRoute(params) {
@@ -4625,6 +4794,13 @@ var LendingModule = class {
4625
4794
  poolChain: selectedPool.chain,
4626
4795
  destinationChain: request.chain
4627
4796
  });
4797
+ await guardEthereumOutflowDestination({
4798
+ address: receiver.address,
4799
+ apiClient: this.apiClient,
4800
+ asset: selectedAsset,
4801
+ chain: request.chain,
4802
+ evmReadClient: this.evmReadClient
4803
+ });
4628
4804
  const lendingActor = createLendingActor(this.canisterContext);
4629
4805
  try {
4630
4806
  const expiryTimestamp = computeExpiryTimestampFromNow();
@@ -4643,7 +4819,9 @@ var LendingModule = class {
4643
4819
  };
4644
4820
  const withdrawSubmissionData = {
4645
4821
  ...withdrawActionData,
4646
- receiverAccount: receiver.canisterAccount
4822
+ asset: selectedAsset,
4823
+ receiverAccount: receiver.canisterAccount,
4824
+ receiverAddress: receiver.address
4647
4825
  };
4648
4826
  return {
4649
4827
  kind: WalletActionKind.createWithdraw,
@@ -4675,6 +4853,13 @@ var LendingModule = class {
4675
4853
  }
4676
4854
  }
4677
4855
  async submitWithdraw(request, signatureInfo) {
4856
+ await guardEthereumOutflowDestination({
4857
+ address: request.receiverAddress,
4858
+ apiClient: this.apiClient,
4859
+ asset: request.asset,
4860
+ chain: request.chain,
4861
+ evmReadClient: this.evmReadClient
4862
+ });
4678
4863
  try {
4679
4864
  const result = await createLendingActor(this.canisterContext).withdraw(
4680
4865
  Principal.fromText(request.profileId),
@@ -4779,6 +4964,13 @@ var LendingModule = class {
4779
4964
  poolChain: selectedPool.chain,
4780
4965
  destinationChain: request.chain
4781
4966
  });
4967
+ await guardEthereumOutflowDestination({
4968
+ address: receiver.address,
4969
+ apiClient: this.apiClient,
4970
+ asset: selectedAsset,
4971
+ chain: request.chain,
4972
+ evmReadClient: this.evmReadClient
4973
+ });
4782
4974
  await this.guardBorrowSameAssetPolicy({
4783
4975
  profileId: request.profileId,
4784
4976
  pool: selectedPool
@@ -4801,7 +4993,9 @@ var LendingModule = class {
4801
4993
  };
4802
4994
  const borrowSubmissionData = {
4803
4995
  ...borrowActionData,
4804
- receiverAccount: receiver.canisterAccount
4996
+ asset: selectedAsset,
4997
+ receiverAccount: receiver.canisterAccount,
4998
+ receiverAddress: receiver.address
4805
4999
  };
4806
5000
  return {
4807
5001
  kind: WalletActionKind.createBorrow,
@@ -4830,6 +5024,13 @@ var LendingModule = class {
4830
5024
  }
4831
5025
  }
4832
5026
  async submitBorrow(request, signatureInfo) {
5027
+ await guardEthereumOutflowDestination({
5028
+ address: request.receiverAddress,
5029
+ apiClient: this.apiClient,
5030
+ asset: request.asset,
5031
+ chain: request.chain,
5032
+ evmReadClient: this.evmReadClient
5033
+ });
4833
5034
  try {
4834
5035
  const result = await createLendingActor(
4835
5036
  this.canisterContext
@@ -5149,9 +5350,78 @@ function mapOutflowTypeToStatusOperation(outflowType) {
5149
5350
  return outflowType;
5150
5351
  }
5151
5352
 
5353
+ // src/core/asset-metadata.ts
5354
+ var ASSET_METADATA = {
5355
+ [Asset.BTC]: {
5356
+ symbol: Asset.BTC,
5357
+ displayName: "Bitcoin"
5358
+ },
5359
+ [Asset.ETH]: {
5360
+ symbol: Asset.ETH,
5361
+ displayName: "Ethereum"
5362
+ },
5363
+ [Asset.ICP]: {
5364
+ symbol: Asset.ICP,
5365
+ displayName: "Internet Computer"
5366
+ },
5367
+ [Asset.USDC]: {
5368
+ symbol: Asset.USDC,
5369
+ displayName: "USD Coin"
5370
+ },
5371
+ [Asset.USDT]: {
5372
+ symbol: Asset.USDT,
5373
+ displayName: "Tether USD"
5374
+ }
5375
+ };
5376
+ function getAssetMetadata(asset) {
5377
+ return ASSET_METADATA[asset];
5378
+ }
5379
+
5152
5380
  // src/core/rates.ts
5153
5381
  var RATE_SCALE = 1000000000000000000000000000n;
5154
5382
  var RATE_DECIMALS = BigInt(RATE_SCALE.toString().length - 1);
5383
+ var INTEREST_YEAR_365_DAYS_SECONDS = 31536000n;
5384
+ var SUPPLY_COMPOUNDING_INTERVAL_15_SECONDS = 15n;
5385
+ function estimateBorrowApy(borrowApr) {
5386
+ return estimateCompoundedApy(borrowApr, 1n);
5387
+ }
5388
+ function estimateSupplyApy(supplyApr) {
5389
+ return estimateCompoundedApy(
5390
+ supplyApr,
5391
+ SUPPLY_COMPOUNDING_INTERVAL_15_SECONDS
5392
+ );
5393
+ }
5394
+ function estimateCompoundedApy(apr, compoundingIntervalSeconds) {
5395
+ if (apr < 0n) {
5396
+ throw new RangeError("APR cannot be negative");
5397
+ }
5398
+ if (apr === 0n) {
5399
+ return 0n;
5400
+ }
5401
+ const periodsPerYear = INTEREST_YEAR_365_DAYS_SECONDS / compoundingIntervalSeconds;
5402
+ const ratePerPeriod = apr * compoundingIntervalSeconds / INTEREST_YEAR_365_DAYS_SECONDS;
5403
+ const annualGrowth = fixedPointPow(
5404
+ RATE_SCALE + ratePerPeriod,
5405
+ periodsPerYear
5406
+ );
5407
+ return annualGrowth - RATE_SCALE;
5408
+ }
5409
+ function fixedPointPow(base, exponent) {
5410
+ let remainingExponent = exponent;
5411
+ let currentBase = base;
5412
+ let result = RATE_SCALE;
5413
+ while (remainingExponent > 0n) {
5414
+ if (remainingExponent % 2n === 1n) {
5415
+ result = fixedPointMultiply(result, currentBase);
5416
+ }
5417
+ currentBase = fixedPointMultiply(currentBase, currentBase);
5418
+ remainingExponent /= 2n;
5419
+ }
5420
+ return result;
5421
+ }
5422
+ function fixedPointMultiply(left, right) {
5423
+ return (left * right + RATE_SCALE / 2n) / RATE_SCALE;
5424
+ }
5155
5425
 
5156
5426
  // src/core/utils/asset-decimals.ts
5157
5427
  var ASSET_NATIVE_DECIMALS = {
@@ -5178,12 +5448,14 @@ var DECIMAL_BASE = 10;
5178
5448
  var PAIR_SEPARATOR = "_";
5179
5449
  var USDT_SYMBOL = "USDT";
5180
5450
  function mapDecodedPoolToPool(pool, rate) {
5451
+ const assetMetadata = getAssetMetadata(pool.asset);
5181
5452
  const totalSupply = pool.total_supply_at_last_sync * pool.lending_index / RATE_SCALE;
5182
5453
  const totalDebt = pool.total_debt_at_last_sync * pool.borrow_index / RATE_SCALE;
5183
5454
  const availableLiquidity = totalSupply > totalDebt ? totalSupply - totalDebt : 0n;
5184
5455
  return {
5185
5456
  id: pool.principal.toString(),
5186
5457
  asset: pool.asset,
5458
+ displayName: assetMetadata.displayName,
5187
5459
  chain: pool.chain,
5188
5460
  decimals: getAssetNativeDecimals(pool.asset),
5189
5461
  frozen: pool.frozen,
@@ -5199,7 +5471,9 @@ function mapDecodedPoolToPool(pool, rate) {
5199
5471
  reserveFactor: pool.reserve_factor,
5200
5472
  rateDecimals: RATE_DECIMALS,
5201
5473
  lendingRate: rate[1],
5474
+ estimatedLendingApy: estimateSupplyApy(rate[1]),
5202
5475
  borrowingRate: rate[0],
5476
+ estimatedBorrowingApy: estimateBorrowApy(rate[0]),
5203
5477
  utilizationRate: rate[2],
5204
5478
  baseRate: pool.base_rate,
5205
5479
  optimalUtilizationRate: pool.optimal_utilization_rate,
@@ -5233,7 +5507,9 @@ function mapGetPoolRateResponseToPoolRate(rate) {
5233
5507
  return {
5234
5508
  rateDecimals: RATE_DECIMALS,
5235
5509
  borrowRate: rate[0],
5510
+ estimatedBorrowApy: estimateBorrowApy(rate[0]),
5236
5511
  lendRate: rate[1],
5512
+ estimatedLendApy: estimateSupplyApy(rate[1]),
5237
5513
  utilizationRate: rate[2]
5238
5514
  };
5239
5515
  }
@@ -5287,15 +5563,32 @@ var MarketModule = class {
5287
5563
  }
5288
5564
  }
5289
5565
  /**
5290
- * Returns the latest asset prices reported by the protocol.
5566
+ * Returns the current cached asset prices reported by the protocol.
5291
5567
  *
5292
- * @returns The latest protocol price map keyed by market asset symbol.
5568
+ * @returns The current protocol price map keyed by market asset symbol.
5293
5569
  */
5294
5570
  async getAssetPrices() {
5571
+ return (await this.getAssetPriceSnapshot()).prices;
5572
+ }
5573
+ /**
5574
+ * Returns protocol prices with the time at which the SDK completed the fetch.
5575
+ *
5576
+ * `fetchedAt` is an SDK retrieval time, not an oracle observation timestamp.
5577
+ * The current lending canister price response does not expose the underlying
5578
+ * oracle timestamp.
5579
+ *
5580
+ * @returns Protocol prices and their SDK fetch timestamp.
5581
+ */
5582
+ async getAssetPriceSnapshot() {
5295
5583
  try {
5296
- return mapGetPricesResponseToAssetPrices(
5584
+ const prices = mapGetPricesResponseToAssetPrices(
5297
5585
  await createLendingActor(this.canisterContext).get_prices()
5298
5586
  );
5587
+ const fetchedAtUnixSeconds = getCurrentUnixTimestampSeconds();
5588
+ return {
5589
+ prices,
5590
+ fetchedAt: fetchedAtUnixSeconds
5591
+ };
5299
5592
  } catch (error) {
5300
5593
  if (error instanceof LiquidiumError) {
5301
5594
  throw error;
@@ -5397,6 +5690,10 @@ function decodeSupportedFlexiblePools(rawPools) {
5397
5690
  return decodedPools;
5398
5691
  }
5399
5692
 
5693
+ // src/modules/positions/health-factor.ts
5694
+ var HEALTH_FACTOR_SCALE = 1000n;
5695
+ var HEALTH_FACTOR_DECIMALS = 3n;
5696
+
5400
5697
  // src/modules/positions/mappers.ts
5401
5698
  var USD_VALUE_SCALE_DECIMALS = 27n;
5402
5699
  function mapDecodedPositionViewToPosition(view) {
@@ -5525,11 +5822,13 @@ var PositionsModule = class {
5525
5822
  const [healthFactor, userStatsRecord] = await createFlexibleLendingActor(
5526
5823
  this.canisterContext
5527
5824
  ).get_health_factor(Principal.fromText(profileId));
5825
+ const userStats = mapDecodedUserStatsToUserStats(
5826
+ decodeFlexibleUserStats(userStatsRecord)
5827
+ );
5528
5828
  return {
5529
- healthFactor,
5530
- userStats: mapDecodedUserStatsToUserStats(
5531
- decodeFlexibleUserStats(userStatsRecord)
5532
- )
5829
+ healthFactor: userStats.debt === 0n ? null : healthFactor,
5830
+ healthFactorDecimals: HEALTH_FACTOR_DECIMALS,
5831
+ userStats
5533
5832
  };
5534
5833
  } catch (error) {
5535
5834
  if (error instanceof LiquidiumError) {
@@ -5569,7 +5868,7 @@ var PositionsModule = class {
5569
5868
  * @returns Derived position summary for the requested profile.
5570
5869
  */
5571
5870
  async getUserPositionSummary(profileId) {
5572
- const { healthFactor, userStats } = await this.getHealthFactor(profileId);
5871
+ const { healthFactor, healthFactorDecimals, userStats } = await this.getHealthFactor(profileId);
5573
5872
  const collateral = userStats.collateral;
5574
5873
  const debt = userStats.debt;
5575
5874
  const maxBorrowableUsd = userStats.borrowingPower.maxBorrowableUsd;
@@ -5585,7 +5884,8 @@ var PositionsModule = class {
5585
5884
  currentLtvBps,
5586
5885
  weightedMaxLtvBps: userStats.borrowingPower.weightedMaxLtv,
5587
5886
  weightedLiquidationThresholdBps: userStats.weightedLiquidationThreshold,
5588
- healthFactor
5887
+ healthFactor,
5888
+ healthFactorDecimals
5589
5889
  };
5590
5890
  }
5591
5891
  /**
@@ -6869,9 +7169,9 @@ function isSimpleLoanDepositExpired(input) {
6869
7169
  if (input.expiryTimestamp === null) {
6870
7170
  return false;
6871
7171
  }
6872
- return input.expiryTimestamp <= getCurrentUnixTimestampSeconds();
7172
+ return input.expiryTimestamp <= getCurrentUnixTimestampSeconds2();
6873
7173
  }
6874
- function getCurrentUnixTimestampSeconds() {
7174
+ function getCurrentUnixTimestampSeconds2() {
6875
7175
  return BigInt(Math.floor(Date.now() / MILLISECONDS_PER_SECOND3));
6876
7176
  }
6877
7177
  function deriveDepositExpiryTimestamp(input) {
@@ -7472,6 +7772,6 @@ function resolveEvmReadClient(config) {
7472
7772
  });
7473
7773
  }
7474
7774
 
7475
- export { AccountsModule, ActivitiesModule, ActivityFilter, Asset, CK_ETH_DEPOSIT_CONTRACT_ADDRESS, Chain, Environment, EvmSupplyApprovalStrategy, HistoryModule, LendingModule, LiquidiumAccountType, LiquidiumClient, LiquidiumError, LiquidiumErrorCode, MIN_BORROW_AMOUNTS_BY_ASSET, MIN_DEPOSIT_AMOUNTS_BY_ASSET, MIN_WITHDRAW_AMOUNTS_BY_ASSET, MarketModule, OutflowType, PositionsModule, QuoteModule, QuoteValidationErrorCode, QuoteWarningCode, RATE_DECIMALS, RATE_SCALE, SimpleLoanCreatedError, SimpleLoansModule, SupplyAction, SupplyPlanType, USDC_CONTRACT_ADDRESS, USDT_CONTRACT_ADDRESS, WalletActionKind, WalletExecutionKind, createTransferErc20Transaction, executeWith, getMinimumBorrowAmount, getMinimumDepositAmount, getMinimumWithdrawAmount, intFromPublicId, isAssetIdentifier, publicIdFromInt };
7775
+ export { ASSET_METADATA, AccountsModule, ActivitiesModule, ActivityFilter, Asset, CK_ETH_DEPOSIT_CONTRACT_ADDRESS, Chain, Environment, EvmSupplyApprovalStrategy, HEALTH_FACTOR_DECIMALS, HEALTH_FACTOR_SCALE, HistoryModule, INTEREST_YEAR_365_DAYS_SECONDS, LendingModule, LiquidiumAccountType, LiquidiumClient, LiquidiumError, LiquidiumErrorCode, MIN_BORROW_AMOUNTS_BY_ASSET, MIN_DEPOSIT_AMOUNTS_BY_ASSET, MIN_WITHDRAW_AMOUNTS_BY_ASSET, MarketModule, OutflowType, PositionsModule, QuoteModule, QuoteValidationErrorCode, QuoteWarningCode, RATE_DECIMALS, RATE_SCALE, SUPPLY_COMPOUNDING_INTERVAL_15_SECONDS, SimpleLoanCreatedError, SimpleLoansModule, SupplyAction, SupplyPlanType, USDC_CONTRACT_ADDRESS, USDT_CONTRACT_ADDRESS, WalletActionKind, WalletExecutionKind, createTransferErc20Transaction, estimateBorrowApy, estimateSupplyApy, executeWith, getAssetMetadata, getMinimumBorrowAmount, getMinimumDepositAmount, getMinimumWithdrawAmount, intFromPublicId, isAssetIdentifier, publicIdFromInt };
7476
7776
  //# sourceMappingURL=index.js.map
7477
7777
  //# sourceMappingURL=index.js.map