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