@liquidium/client 0.3.4 → 0.4.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.js CHANGED
@@ -30,11 +30,7 @@ var SupplyAction = {
30
30
  var OutflowType = {
31
31
  borrow: "borrow",
32
32
  feeClaim: "feeClaim",
33
- withdraw: "withdraw"
34
- };
35
- var InflowSubmitType = {
36
- DEPOSIT: "DEPOSIT",
37
- REPAY: "REPAY"
33
+ withdrawal: "withdrawal"
38
34
  };
39
35
 
40
36
  // src/core/config.ts
@@ -1368,8 +1364,8 @@ function mapCanisterWalletToWallet(canisterWallet) {
1368
1364
  );
1369
1365
  }
1370
1366
  }
1371
- var KNOWN_ASSET_TAGS = ["BTC", "SOL", "USDC", "USDT"];
1372
- var KNOWN_CHAIN_TAGS = ["BTC", "ETH", "SOL"];
1367
+ var KNOWN_ASSET_TAGS = ["BTC", "USDC", "USDT"];
1368
+ var KNOWN_CHAIN_TAGS = ["BTC", "ETH"];
1373
1369
  function extractVariantTag(variant, knownTags) {
1374
1370
  const [key] = Object.keys(variant);
1375
1371
  if (!key) {
@@ -1682,49 +1678,24 @@ function decodeLoanCreatedEvent(payload) {
1682
1678
  }
1683
1679
 
1684
1680
  // src/core/sdk-api-paths.ts
1685
- var SDK_API_VERSION = {
1686
- activities: "v1",
1687
- history: "v1",
1688
- inflow: "v1",
1689
- instantLoans: "v1"
1690
- };
1681
+ var SDK_API_V1_PATH = "/v1";
1682
+ var SDK_API_V2_PATH = "/v2";
1691
1683
  var SdkApiQueryParam = {
1692
1684
  cursor: "cursor",
1693
1685
  from: "from",
1686
+ filter: "filter",
1694
1687
  limit: "limit",
1695
1688
  market: "market",
1689
+ operations: "operations",
1696
1690
  poolId: "poolId",
1697
1691
  profileId: "profileId",
1698
- state: "state",
1699
- statuses: "statuses",
1700
- to: "to",
1701
- types: "types"
1692
+ states: "states",
1693
+ to: "to"
1702
1694
  };
1703
- var ACTIVITIES = `/${SDK_API_VERSION.activities}/activities`;
1704
- var HISTORY_POOL = `/${SDK_API_VERSION.history}/history/pool`;
1705
- var HISTORY_POOL_CONFIG = `/${SDK_API_VERSION.history}/history/pool-config`;
1706
- var HISTORY_RATES = `/${SDK_API_VERSION.history}/history/rates`;
1707
- var HISTORY_USERS = `/${SDK_API_VERSION.history}/history/users`;
1708
- var INFLOW = `/${SDK_API_VERSION.inflow}/inflow`;
1709
- var INSTANT_LOANS = `/${SDK_API_VERSION.instantLoans}/instant-loans`;
1710
- function buildHistoryPoolPath(poolId, query) {
1711
- const base = `${HISTORY_POOL}/${encodeURIComponent(poolId)}`;
1712
- const qs = query.toString();
1713
- return qs ? `${base}?${qs}` : base;
1714
- }
1715
- function buildHistoryPoolConfigPath(poolId, cursor) {
1716
- const base = `${HISTORY_POOL_CONFIG}/${encodeURIComponent(poolId)}`;
1717
- if (!cursor) {
1718
- return base;
1719
- }
1720
- const query = new URLSearchParams({ [SdkApiQueryParam.cursor]: cursor });
1721
- return `${base}?${query.toString()}`;
1722
- }
1723
- function buildHistoryRatesPath(poolId, query) {
1724
- const base = `${HISTORY_RATES}/${encodeURIComponent(poolId)}`;
1725
- const qs = query.toString();
1726
- return qs ? `${base}?${qs}` : base;
1727
- }
1695
+ var ACTIVITIES = `${SDK_API_V2_PATH}/activities`;
1696
+ var HISTORY_USERS = `${SDK_API_V2_PATH}/history/users`;
1697
+ var INFLOW = `${SDK_API_V2_PATH}/inflow`;
1698
+ var INSTANT_LOANS = `${SDK_API_V1_PATH}/instant-loans`;
1728
1699
  function buildHistoryUserTransactionsPath(user, query) {
1729
1700
  const base = `${HISTORY_USERS}/${encodeURIComponent(user)}/transactions`;
1730
1701
  const qs = query.toString();
@@ -1739,8 +1710,8 @@ function buildActivitiesPath(request) {
1739
1710
  const query = new URLSearchParams({
1740
1711
  [SdkApiQueryParam.profileId]: request.profileId
1741
1712
  });
1742
- if (request.state) {
1743
- query.set(SdkApiQueryParam.state, request.state);
1713
+ if (request.filter) {
1714
+ query.set(SdkApiQueryParam.filter, request.filter);
1744
1715
  }
1745
1716
  return `${ACTIVITIES}?${query.toString()}`;
1746
1717
  }
@@ -1798,12 +1769,6 @@ function parseBigInt(value, label) {
1798
1769
  );
1799
1770
  }
1800
1771
  }
1801
- function parseOptionalBigInt(value, label) {
1802
- if (value === void 0) {
1803
- return void 0;
1804
- }
1805
- return parseBigInt(value, label);
1806
- }
1807
1772
 
1808
1773
  // src/modules/instant-loans/ref-code.ts
1809
1774
  var REF_LENGTH = 6;
@@ -1874,28 +1839,8 @@ var ActivityFilter = {
1874
1839
  completed: "completed",
1875
1840
  all: "all"
1876
1841
  };
1877
- var ActivityDirection = {
1878
- inflow: "inflow",
1879
- outflow: "outflow"
1880
- };
1881
- var ActivityKind = {
1882
- deposit: "deposit",
1883
- repayment: "repayment",
1884
- borrow: "borrow",
1885
- withdraw: "withdraw"
1886
- };
1887
- var ActivityStatus = {
1888
- requested: "requested",
1889
- pending: "pending",
1890
- detected: "detected",
1891
- processing: "processing",
1892
- sent: "sent",
1893
- confirmed: "confirmed",
1894
- failed: "failed"
1895
- };
1896
1842
 
1897
1843
  // src/modules/activities/activities.ts
1898
- var PRE_TERMINAL_ETH_ACTIVITY_ID_PREFIX = "pre_terminal_eth_";
1899
1844
  var ActivitiesModule = class {
1900
1845
  constructor(apiClient, canisterContext) {
1901
1846
  this.apiClient = apiClient;
@@ -1904,11 +1849,11 @@ var ActivitiesModule = class {
1904
1849
  apiClient;
1905
1850
  canisterContext;
1906
1851
  /**
1907
- * Lists profile activities. Defaults to all activities.
1852
+ * Lists profile activities. Defaults to active activities.
1908
1853
  *
1909
1854
  * Uses the Liquidium SDK API.
1910
1855
  *
1911
- * @param request - Profile id or instant-loan short reference plus optional state filter.
1856
+ * @param request - Profile id or instant-loan short reference plus optional lifecycle filter.
1912
1857
  * @returns Activities owned by the resolved profile.
1913
1858
  */
1914
1859
  async list(request) {
@@ -1917,7 +1862,7 @@ var ActivitiesModule = class {
1917
1862
  const response = await apiClient.get(
1918
1863
  buildActivitiesPath({
1919
1864
  profileId,
1920
- state: request.filter ?? ActivityFilter.all
1865
+ filter: request.filter ?? ActivityFilter.active
1921
1866
  })
1922
1867
  );
1923
1868
  return response.activities.map(mapActivity);
@@ -1998,76 +1943,48 @@ function mapInstantLoanLookupError(error) {
1998
1943
  return new LiquidiumError(LiquidiumErrorCode.INTERNAL, key);
1999
1944
  }
2000
1945
  function mapActivity(wire) {
2001
- const { amount, stage, status, topUp: topUpWire, ...activity } = wire;
2002
- const effectiveStage = stage ?? deriveActivityStage(wire);
2003
- const topUp = activity.direction === ActivityDirection.inflow && topUpWire ? mapActivityTopUp(topUpWire) : void 0;
2004
- if (activity.direction === ActivityDirection.inflow) {
2005
- return {
2006
- ...activity,
2007
- direction: ActivityDirection.inflow,
2008
- kind: mapInflowActivityKind(activity.kind),
2009
- status: mapInflowActivityStatus(
2010
- mapActivityStatus(status, effectiveStage)
2011
- ),
2012
- ...topUp ? { topUp } : {},
2013
- amount: parseBigInt(amount, "activity amount")
2014
- };
2015
- }
2016
- return {
2017
- ...activity,
2018
- direction: ActivityDirection.outflow,
2019
- kind: mapOutflowActivityKind(activity.kind),
2020
- status: mapOutflowActivityStatus(mapActivityStatus(status, effectiveStage)),
2021
- amount: parseBigInt(amount, "activity amount")
1946
+ const amount = parseBigInt(wire.amount, "activity amount");
1947
+ const activityBase = {
1948
+ id: wire.id,
1949
+ poolId: wire.poolId,
1950
+ asset: wire.asset,
1951
+ chain: wire.chain,
1952
+ amount,
1953
+ timestampMs: wire.timestampMs
2022
1954
  };
2023
- }
2024
- function mapInflowActivityKind(kind) {
2025
- if (kind === "deposit" || kind === "repayment") {
2026
- return kind;
2027
- }
2028
- throw new LiquidiumError(
2029
- LiquidiumErrorCode.INTERNAL,
2030
- `Invalid inflow activity kind: ${kind}`
2031
- );
2032
- }
2033
- function mapOutflowActivityKind(kind) {
2034
- if (kind === "borrow" || kind === "withdraw") {
2035
- return kind;
1955
+ if (isInflowOperation(wire.status.operation)) {
1956
+ const activity = {
1957
+ ...activityBase,
1958
+ status: wire.status
1959
+ };
1960
+ if (wire.txids) {
1961
+ activity.txids = wire.txids;
1962
+ }
1963
+ if (wire.topUp) {
1964
+ activity.topUp = mapActivityTopUp(wire.topUp);
1965
+ }
1966
+ return activity;
2036
1967
  }
2037
- throw new LiquidiumError(
2038
- LiquidiumErrorCode.INTERNAL,
2039
- `Invalid outflow activity kind: ${kind}`
2040
- );
2041
- }
2042
- function mapInflowActivityStatus(status) {
2043
- if (status !== "sent") {
2044
- return status;
1968
+ if (isOutflowOperation(wire.status.operation)) {
1969
+ const activity = {
1970
+ ...activityBase,
1971
+ status: wire.status
1972
+ };
1973
+ if (wire.txids) {
1974
+ activity.txids = wire.txids;
1975
+ }
1976
+ return activity;
2045
1977
  }
2046
1978
  throw new LiquidiumError(
2047
1979
  LiquidiumErrorCode.INTERNAL,
2048
- `Invalid inflow activity status: ${status}`
1980
+ `Invalid activity operation: ${wire.status.operation}`
2049
1981
  );
2050
1982
  }
2051
- function mapOutflowActivityStatus(status) {
2052
- if (status !== "detected" && status !== "processing") {
2053
- return status;
2054
- }
2055
- throw new LiquidiumError(
2056
- LiquidiumErrorCode.INTERNAL,
2057
- `Invalid outflow activity status: ${status}`
2058
- );
1983
+ function isInflowOperation(operation) {
1984
+ return operation === "deposit" || operation === "repayment";
2059
1985
  }
2060
- function mapActivityStatus(status, stage) {
2061
- if (status !== "pending") {
2062
- return status;
2063
- }
2064
- if (stage === "deposited") {
2065
- return "detected";
2066
- }
2067
- if (stage === "confirmed" || stage === "finalising" || stage === "pending") {
2068
- return "processing";
2069
- }
2070
- return "pending";
1986
+ function isOutflowOperation(operation) {
1987
+ return operation === "borrow" || operation === "withdrawal";
2071
1988
  }
2072
1989
  function mapActivityTopUp(wire) {
2073
1990
  return {
@@ -2083,24 +2000,6 @@ function mapActivityTopUp(wire) {
2083
2000
  )
2084
2001
  };
2085
2002
  }
2086
- function deriveActivityStage(wire) {
2087
- if (wire.id.startsWith(PRE_TERMINAL_ETH_ACTIVITY_ID_PREFIX) && wire.direction === ActivityDirection.inflow && wire.chain === Chain.ETH) {
2088
- return "deposited";
2089
- }
2090
- return void 0;
2091
- }
2092
-
2093
- // src/core/rates.ts
2094
- var RATE_SCALE = 1000000000000000000000000000n;
2095
- var RATE_DECIMALS = BigInt(RATE_SCALE.toString().length - 1);
2096
-
2097
- // src/modules/history/types.ts
2098
- var UserHistoryStatus = {
2099
- requested: "requested",
2100
- pending: "pending",
2101
- confirmed: "confirmed",
2102
- failed: "failed"
2103
- };
2104
2003
 
2105
2004
  // src/modules/history/history.ts
2106
2005
  var HistoryModule = class {
@@ -2118,154 +2017,41 @@ var HistoryModule = class {
2118
2017
  return this.apiClient;
2119
2018
  }
2120
2019
  /**
2121
- * Returns paginated rate and utilization history for a pool.
2122
- *
2123
- * @param poolId - The pool principal text.
2124
- * @param window - Optional time window with from/to timestamps and limit.
2125
- * @returns A page of pool rate history entries and the next cursor when more results are available.
2126
- */
2127
- async getPoolHistory(poolId, window = {}) {
2128
- const apiClient = this.requireApi();
2129
- const query = createHistoryWindowQuery(window);
2130
- const requestPath = buildHistoryPoolPath(poolId, query);
2131
- const response = await apiClient.get(requestPath);
2132
- return {
2133
- items: response.items.map((item) => ({
2134
- date: item.date,
2135
- rateDecimals: RATE_DECIMALS,
2136
- avgBorrowRate: parseBigInt(item.avgBorrowRate, "pool borrow rate"),
2137
- avgLendRate: parseBigInt(item.avgLendRate, "pool lend rate"),
2138
- avgUtilizationRate: parseBigInt(
2139
- item.avgUtilizationRate,
2140
- "pool utilization rate"
2141
- )
2142
- })),
2143
- nextCursor: response.nextCursor
2144
- };
2145
- }
2146
- /**
2147
- * Returns paginated configuration change history for a pool.
2148
- *
2149
- * @param poolId - The pool principal text.
2150
- * @param cursor - An optional pagination cursor from a previous response.
2151
- * @returns A page of pool configuration changes and the next cursor when more results are available.
2152
- */
2153
- async getPoolConfigHistory(poolId, cursor) {
2154
- const apiClient = this.requireApi();
2155
- const requestPath = buildHistoryPoolConfigPath(poolId, cursor);
2156
- const response = await apiClient.get(requestPath);
2157
- return {
2158
- items: response.items.map((item) => ({
2159
- type: item.type,
2160
- poolId: item.poolId,
2161
- asset: item.asset,
2162
- chain: item.chain,
2163
- timestamp: item.timestamp,
2164
- totalSupply: parseBigInt(item.totalSupply, "pool history totalSupply"),
2165
- totalDebt: parseBigInt(item.totalDebt, "pool history totalDebt"),
2166
- supplyCap: parseOptionalBigInt(
2167
- item.supplyCap,
2168
- "pool history supplyCap"
2169
- ),
2170
- borrowCap: parseOptionalBigInt(
2171
- item.borrowCap,
2172
- "pool history borrowCap"
2173
- ),
2174
- maxLtv: parseBigInt(item.maxLtv, "pool history maxLtv"),
2175
- liquidationThreshold: parseBigInt(
2176
- item.liquidationThreshold,
2177
- "pool history liquidationThreshold"
2178
- ),
2179
- liquidationBonus: parseBigInt(
2180
- item.liquidationBonus,
2181
- "pool history liquidationBonus"
2182
- ),
2183
- protocolLiquidationFee: parseBigInt(
2184
- item.protocolLiquidationFee,
2185
- "pool history protocolLiquidationFee"
2186
- ),
2187
- reserveFactor: parseBigInt(
2188
- item.reserveFactor,
2189
- "pool history reserveFactor"
2190
- ),
2191
- baseRate: parseBigInt(item.baseRate, "pool history baseRate"),
2192
- optimalUtilizationRate: parseBigInt(
2193
- item.optimalUtilizationRate,
2194
- "pool history optimalUtilizationRate"
2195
- ),
2196
- rateSlopeBefore: parseBigInt(
2197
- item.rateSlopeBefore,
2198
- "pool history rateSlopeBefore"
2199
- ),
2200
- rateSlopeAfter: parseBigInt(
2201
- item.rateSlopeAfter,
2202
- "pool history rateSlopeAfter"
2203
- ),
2204
- lendingIndex: parseBigInt(
2205
- item.lendingIndex,
2206
- "pool history lendingIndex"
2207
- ),
2208
- borrowIndex: parseBigInt(item.borrowIndex, "pool history borrowIndex"),
2209
- sameAssetBorrowing: item.sameAssetBorrowing,
2210
- frozen: item.frozen
2211
- })),
2212
- nextCursor: response.nextCursor
2213
- };
2214
- }
2215
- /**
2216
- * Returns borrow rate history for a pool.
2020
+ * Returns transaction history for a user.
2217
2021
  *
2218
- * @param poolId - The pool principal text.
2219
- * @param window - Optional time window with from/to timestamps and limit.
2220
- * @returns Paginated APY samples.
2022
+ * @param user - The Liquidium profile principal text.
2023
+ * @param filters - Optional pool, operation, state, time range, and pagination filters.
2024
+ * @returns Paginated user history entries.
2221
2025
  */
2222
- async getBorrowRateHistory(poolId, window = {}) {
2223
- const apiClient = this.requireApi();
2224
- const query = createHistoryWindowQuery(window);
2225
- const requestPath = buildHistoryRatesPath(poolId, query);
2226
- const response = await apiClient.get(requestPath);
2227
- return {
2228
- items: response.items.map((item) => ({
2229
- date: item.date,
2230
- rateDecimals: RATE_DECIMALS,
2231
- avgRate: parseBigInt(item.avgRate, "borrow rate")
2232
- })),
2233
- nextCursor: response.nextCursor
2234
- };
2235
- }
2236
- async getUserTransactionHistory(user, marketOrFilters, filters = {}) {
2026
+ async getUserTransactionHistory(user, filters = {}) {
2237
2027
  const apiClient = this.requireApi();
2238
- const normalizedFilters = normalizeTransactionHistoryFilters(
2239
- marketOrFilters,
2240
- filters
2241
- );
2242
2028
  const query = new URLSearchParams();
2243
- if (normalizedFilters.cursor) {
2244
- query.set(SdkApiQueryParam.cursor, normalizedFilters.cursor);
2029
+ if (filters.cursor) {
2030
+ query.set(SdkApiQueryParam.cursor, filters.cursor);
2245
2031
  }
2246
- if (normalizedFilters.market) {
2247
- query.set(SdkApiQueryParam.market, normalizedFilters.market);
2032
+ if (filters.market) {
2033
+ query.set(SdkApiQueryParam.market, filters.market);
2248
2034
  }
2249
- if (normalizedFilters.poolId) {
2250
- query.set(SdkApiQueryParam.poolId, normalizedFilters.poolId);
2035
+ if (filters.poolId) {
2036
+ query.set(SdkApiQueryParam.poolId, filters.poolId);
2251
2037
  }
2252
- if (normalizedFilters.types?.length) {
2253
- query.set(SdkApiQueryParam.types, normalizedFilters.types.join(","));
2038
+ if (filters.operations?.length) {
2039
+ query.set(SdkApiQueryParam.operations, filters.operations.join(","));
2254
2040
  }
2255
- if (normalizedFilters.statuses?.length) {
2041
+ if (filters.states?.length) {
2256
2042
  query.set(
2257
- SdkApiQueryParam.statuses,
2258
- normalizedFilters.statuses.map(mapHistoryStatusToApi).join(",")
2043
+ SdkApiQueryParam.states,
2044
+ createHistoryStateFilterParam(filters.states)
2259
2045
  );
2260
2046
  }
2261
- if (normalizedFilters.from) {
2262
- query.set(SdkApiQueryParam.from, normalizedFilters.from);
2047
+ if (filters.from) {
2048
+ query.set(SdkApiQueryParam.from, filters.from);
2263
2049
  }
2264
- if (normalizedFilters.to) {
2265
- query.set(SdkApiQueryParam.to, normalizedFilters.to);
2050
+ if (filters.to) {
2051
+ query.set(SdkApiQueryParam.to, filters.to);
2266
2052
  }
2267
- if (normalizedFilters.limit !== void 0) {
2268
- query.set(SdkApiQueryParam.limit, String(normalizedFilters.limit));
2053
+ if (filters.limit !== void 0) {
2054
+ query.set(SdkApiQueryParam.limit, String(filters.limit));
2269
2055
  }
2270
2056
  const requestPath = buildHistoryUserTransactionsPath(user, query);
2271
2057
  const response = await apiClient.get(requestPath);
@@ -2274,30 +2060,33 @@ var HistoryModule = class {
2274
2060
  nextCursor: response.nextCursor
2275
2061
  };
2276
2062
  }
2277
- async getLiquidationHistory(user, marketOrFilters, filters = {}) {
2063
+ /**
2064
+ * Returns liquidation history for a user.
2065
+ *
2066
+ * @param user - The Liquidium profile principal text.
2067
+ * @param filters - Optional pool, time range, and pagination filters.
2068
+ * @returns Paginated liquidation history entries.
2069
+ */
2070
+ async getLiquidationHistory(user, filters = {}) {
2278
2071
  const apiClient = this.requireApi();
2279
- const normalizedFilters = normalizeLiquidationHistoryFilters(
2280
- marketOrFilters,
2281
- filters
2282
- );
2283
2072
  const query = new URLSearchParams();
2284
- if (normalizedFilters.cursor) {
2285
- query.set(SdkApiQueryParam.cursor, normalizedFilters.cursor);
2073
+ if (filters.cursor) {
2074
+ query.set(SdkApiQueryParam.cursor, filters.cursor);
2286
2075
  }
2287
- if (normalizedFilters.market) {
2288
- query.set(SdkApiQueryParam.market, normalizedFilters.market);
2076
+ if (filters.market) {
2077
+ query.set(SdkApiQueryParam.market, filters.market);
2289
2078
  }
2290
- if (normalizedFilters.poolId) {
2291
- query.set(SdkApiQueryParam.poolId, normalizedFilters.poolId);
2079
+ if (filters.poolId) {
2080
+ query.set(SdkApiQueryParam.poolId, filters.poolId);
2292
2081
  }
2293
- if (normalizedFilters.from) {
2294
- query.set(SdkApiQueryParam.from, normalizedFilters.from);
2082
+ if (filters.from) {
2083
+ query.set(SdkApiQueryParam.from, filters.from);
2295
2084
  }
2296
- if (normalizedFilters.to) {
2297
- query.set(SdkApiQueryParam.to, normalizedFilters.to);
2085
+ if (filters.to) {
2086
+ query.set(SdkApiQueryParam.to, filters.to);
2298
2087
  }
2299
- if (normalizedFilters.limit !== void 0) {
2300
- query.set(SdkApiQueryParam.limit, String(normalizedFilters.limit));
2088
+ if (filters.limit !== void 0) {
2089
+ query.set(SdkApiQueryParam.limit, String(filters.limit));
2301
2090
  }
2302
2091
  const requestPath = buildHistoryUserLiquidationsPath(user, query);
2303
2092
  const response = await apiClient.get(requestPath);
@@ -2310,77 +2099,59 @@ var HistoryModule = class {
2310
2099
  function mapUserTransactionHistoryEntry(item) {
2311
2100
  return {
2312
2101
  id: item.id,
2313
- type: mapUserTransactionHistoryType(item.type),
2314
2102
  amount: parseBigInt(item.amount, "history user amount"),
2315
2103
  poolId: item.poolId,
2316
2104
  timestamp: item.timestamp,
2317
- status: mapHistoryStatusFromApi(item.status),
2105
+ status: item.status,
2318
2106
  txids: item.txids
2319
2107
  };
2320
2108
  }
2321
2109
  function mapUserLiquidationHistoryEntry(item) {
2322
- if (item.type !== "liquidation") {
2110
+ if (item.status.operation !== "liquidation" || item.status.state !== "completed") {
2323
2111
  throw new LiquidiumError(
2324
2112
  LiquidiumErrorCode.INTERNAL,
2325
- `Invalid liquidation history type: ${item.type}`
2113
+ `Invalid liquidation history status: ${item.status.state}`
2326
2114
  );
2327
2115
  }
2328
2116
  return {
2329
2117
  id: item.id,
2330
- type: "liquidation",
2331
2118
  amount: parseBigInt(item.amount, "history user amount"),
2332
2119
  poolId: item.poolId,
2333
2120
  timestamp: item.timestamp,
2334
- status: mapLiquidationHistoryStatus(item.status),
2121
+ status: item.status,
2335
2122
  txids: item.txids
2336
2123
  };
2337
2124
  }
2338
- function mapUserTransactionHistoryType(type) {
2339
- if (type !== "liquidation") {
2340
- return type;
2341
- }
2342
- throw new LiquidiumError(
2343
- LiquidiumErrorCode.INTERNAL,
2344
- "Transaction history response included a liquidation entry"
2345
- );
2346
- }
2347
- function mapLiquidationHistoryStatus(status) {
2348
- const mappedStatus = mapHistoryStatusFromApi(status);
2349
- if (mappedStatus === UserHistoryStatus.confirmed) {
2350
- return mappedStatus;
2351
- }
2352
- throw new LiquidiumError(
2353
- LiquidiumErrorCode.INTERNAL,
2354
- `Invalid liquidation history status: ${status}`
2355
- );
2356
- }
2357
- function createHistoryWindowQuery(window) {
2358
- const query = new URLSearchParams();
2359
- if (window.cursor) query.set(SdkApiQueryParam.cursor, window.cursor);
2360
- if (window.from) query.set(SdkApiQueryParam.from, window.from);
2361
- if (window.to) query.set(SdkApiQueryParam.to, window.to);
2362
- if (window.limit !== void 0) {
2363
- query.set(SdkApiQueryParam.limit, String(window.limit));
2125
+ function createHistoryStateFilterParam(states) {
2126
+ for (const state of states) {
2127
+ validateHistoryStateFilter(state);
2364
2128
  }
2365
- return query;
2129
+ return [...new Set(states)].join(",");
2366
2130
  }
2367
- function normalizeTransactionHistoryFilters(marketOrFilters, filters) {
2368
- if (typeof marketOrFilters === "string") {
2369
- return { ...filters, market: marketOrFilters };
2370
- }
2371
- return { ...marketOrFilters ?? {}, ...filters };
2372
- }
2373
- function normalizeLiquidationHistoryFilters(marketOrFilters, filters) {
2374
- if (typeof marketOrFilters === "string") {
2375
- return { ...filters, market: marketOrFilters };
2131
+ function validateHistoryStateFilter(state) {
2132
+ switch (state) {
2133
+ case "action_required":
2134
+ case "confirming":
2135
+ case "processing":
2136
+ case "completed":
2137
+ case "failed":
2138
+ return;
2139
+ default:
2140
+ throw new LiquidiumError(
2141
+ LiquidiumErrorCode.VALIDATION_ERROR,
2142
+ `History state filter is not supported: ${state}`
2143
+ );
2376
2144
  }
2377
- return { ...marketOrFilters ?? {}, ...filters };
2378
2145
  }
2379
- function mapHistoryStatusFromApi(status) {
2380
- return status.toLowerCase();
2381
- }
2382
- function mapHistoryStatusToApi(status) {
2383
- return status.toUpperCase();
2146
+
2147
+ // src/core/status.ts
2148
+ function createLiquidiumStatus(params) {
2149
+ return {
2150
+ operation: params.operation,
2151
+ state: params.state,
2152
+ confirmations: params.confirmations ?? null,
2153
+ requiredConfirmations: params.requiredConfirmations ?? null
2154
+ };
2384
2155
  }
2385
2156
 
2386
2157
  // src/core/utils/api-response-parsers.ts
@@ -3977,20 +3748,11 @@ function getChainForInstantLoanAsset(asset) {
3977
3748
  return asset;
3978
3749
  }
3979
3750
 
3980
- // src/modules/instant-loans/types.ts
3981
- var InstantLoanStatus = {
3982
- awaitingDeposit: "awaiting_deposit",
3983
- depositDetected: "deposit_detected",
3984
- active: "active",
3985
- settling: "settling",
3986
- closed: "closed",
3987
- expired: "expired"
3988
- };
3989
-
3990
3751
  // src/modules/instant-loans/instant-loans.ts
3991
3752
  var REPAYMENT_BUFFER_SECONDS = 86400n;
3992
- var RATE_SCALE2 = 10n ** 27n;
3753
+ var RATE_SCALE = 10n ** 27n;
3993
3754
  var SECONDS_PER_YEAR = 31536000n;
3755
+ var MILLISECONDS_PER_SECOND3 = 1e3;
3994
3756
  var ETH_STABLECOIN_INFLOW_FEE_FALLBACK = 1500000n;
3995
3757
  var INSTANT_LOAN_MIN_SLIPPAGE_BUFFER_BPS = 200n;
3996
3758
  var INSTANT_LOAN_FIND_QUERY_MAX_LENGTH = 256;
@@ -4002,14 +3764,16 @@ var INSTANT_LOAN_ASSETS = [
4002
3764
  Asset.USDT
4003
3765
  ];
4004
3766
  var InstantLoansModule = class {
4005
- constructor(canisterContext, apiClient, lending, positions) {
3767
+ constructor(canisterContext, apiClient, activities, lending, positions) {
4006
3768
  this.canisterContext = canisterContext;
4007
3769
  this.apiClient = apiClient;
3770
+ this.activities = activities;
4008
3771
  this.lending = lending;
4009
3772
  this.positions = positions;
4010
3773
  }
4011
3774
  canisterContext;
4012
3775
  apiClient;
3776
+ activities;
4013
3777
  lending;
4014
3778
  positions;
4015
3779
  /**
@@ -4209,7 +3973,8 @@ var InstantLoansModule = class {
4209
3973
  const response = await apiClient.get(
4210
3974
  buildInstantLoanFindPath({ query })
4211
3975
  );
4212
- return (response.candidates ?? response.loans ?? []).map(mapCandidateWire);
3976
+ const candidates = "candidates" in response ? response.candidates : response.loans;
3977
+ return candidates.map(mapCandidateWire);
4213
3978
  }
4214
3979
  async getLoanRecord(loanId) {
4215
3980
  try {
@@ -4248,6 +4013,7 @@ var InstantLoansModule = class {
4248
4013
  borrowAmount: record.borrow_amount,
4249
4014
  borrowDestination: accountFromCanister(record.borrow_destination),
4250
4015
  refundDestination: accountFromCanister(record.refund_destination),
4016
+ started: record.started,
4251
4017
  depositDetectedTimestamp: record.deposit_detected_ts[0] ?? null,
4252
4018
  expiryTimestamp: record.expires_at[0] ?? deriveDepositExpiryTimestamp({
4253
4019
  depositDetectedTimestamp: record.deposit_detected_ts[0] ?? null,
@@ -4276,7 +4042,8 @@ var InstantLoansModule = class {
4276
4042
  repayTarget,
4277
4043
  collateralPosition,
4278
4044
  borrowPosition,
4279
- borrowPoolRate
4045
+ borrowPoolRate,
4046
+ activeActivities
4280
4047
  ] = await Promise.all([
4281
4048
  resolveSupplyTarget(this.canisterContext, {
4282
4049
  profileId,
@@ -4292,7 +4059,8 @@ var InstantLoansModule = class {
4292
4059
  }),
4293
4060
  this.positions.getPosition(profileId, collateralPoolId),
4294
4061
  this.positions.getPosition(profileId, borrowPoolId),
4295
- this.positions.market.getPoolRate(borrowPoolId)
4062
+ this.positions.market.getPoolRate(borrowPoolId),
4063
+ this.activities.list({ profileId, filter: ActivityFilter.active })
4296
4064
  ]);
4297
4065
  const totalDebtAmount = calculateTotalDebtAmount(borrowPosition);
4298
4066
  const interestBufferAmount = calculateInterestBufferAmount(
@@ -4309,8 +4077,12 @@ var InstantLoansModule = class {
4309
4077
  const borrowedDecimals = borrowPosition?.borrowedDecimals ?? getAssetNativeDecimals(borrowAsset);
4310
4078
  const debtInterestAmount = borrowPosition?.debtInterest ?? 0n;
4311
4079
  const status = deriveInstantLoanStatus({
4080
+ started: input.started,
4081
+ depositDetectedTimestamp: input.depositDetectedTimestamp,
4082
+ expiryTimestamp: input.expiryTimestamp,
4312
4083
  collateralAmount: currentCollateralAmount,
4313
- totalDebtAmount
4084
+ totalDebtAmount,
4085
+ activeActivities
4314
4086
  });
4315
4087
  const initialDeposit = await this.createInitialDepositQuote({
4316
4088
  collateralAmount,
@@ -4462,7 +4234,7 @@ function calculateInterestBufferAmount(borrowPosition, annualBorrowRate) {
4462
4234
  if (totalDebtAmount <= 0n || annualBorrowRate <= 0n) {
4463
4235
  return 0n;
4464
4236
  }
4465
- const interestBuffer = totalDebtAmount * annualBorrowRate * REPAYMENT_BUFFER_SECONDS / RATE_SCALE2 / SECONDS_PER_YEAR;
4237
+ const interestBuffer = totalDebtAmount * annualBorrowRate * REPAYMENT_BUFFER_SECONDS / RATE_SCALE / SECONDS_PER_YEAR;
4466
4238
  return interestBuffer > 1n ? interestBuffer : 1n;
4467
4239
  }
4468
4240
  function calculateTotalDebtAmount(borrowPosition) {
@@ -4472,13 +4244,75 @@ function calculateTotalDebtAmount(borrowPosition) {
4472
4244
  return borrowPosition.borrowed + borrowPosition.debtInterest;
4473
4245
  }
4474
4246
  function deriveInstantLoanStatus(input) {
4247
+ const activeActivityStatus = deriveActiveInstantLoanActivityStatus(input);
4248
+ if (activeActivityStatus) {
4249
+ return activeActivityStatus;
4250
+ }
4475
4251
  if (input.totalDebtAmount > 0n) {
4476
- return InstantLoanStatus.active;
4252
+ return createLiquidiumStatus({
4253
+ operation: "repayment",
4254
+ state: "active"
4255
+ });
4256
+ }
4257
+ if (input.started) {
4258
+ return createLiquidiumStatus({
4259
+ operation: "repayment",
4260
+ state: "completed"
4261
+ });
4262
+ }
4263
+ if (isInstantLoanDepositExpired(input)) {
4264
+ return createLiquidiumStatus({
4265
+ operation: "deposit",
4266
+ state: "expired"
4267
+ });
4477
4268
  }
4478
4269
  if (input.collateralAmount > 0n) {
4479
- return InstantLoanStatus.depositDetected;
4270
+ return createLiquidiumStatus({
4271
+ operation: "deposit",
4272
+ state: "processing"
4273
+ });
4274
+ }
4275
+ if (input.depositDetectedTimestamp !== null) {
4276
+ return createLiquidiumStatus({
4277
+ operation: "deposit",
4278
+ state: "confirming"
4279
+ });
4280
+ }
4281
+ return createLiquidiumStatus({
4282
+ operation: "deposit",
4283
+ state: "action_required"
4284
+ });
4285
+ }
4286
+ function deriveActiveInstantLoanActivityStatus(input) {
4287
+ if (!input.started) {
4288
+ return findActiveActivityStatus(input.activeActivities, "deposit");
4289
+ }
4290
+ const borrowStatus = findActiveActivityStatus(
4291
+ input.activeActivities,
4292
+ "borrow"
4293
+ );
4294
+ if (borrowStatus) {
4295
+ return borrowStatus;
4296
+ }
4297
+ return findActiveActivityStatus(input.activeActivities, "repayment");
4298
+ }
4299
+ function findActiveActivityStatus(activities, operation) {
4300
+ const activity = activities.find(
4301
+ (candidate) => candidate.status.operation === operation
4302
+ );
4303
+ return activity?.status ?? null;
4304
+ }
4305
+ function isInstantLoanDepositExpired(input) {
4306
+ if (input.started || input.depositDetectedTimestamp === null) {
4307
+ return false;
4308
+ }
4309
+ if (input.expiryTimestamp === null) {
4310
+ return false;
4480
4311
  }
4481
- return InstantLoanStatus.awaitingDeposit;
4312
+ return input.expiryTimestamp <= getCurrentUnixTimestampSeconds();
4313
+ }
4314
+ function getCurrentUnixTimestampSeconds() {
4315
+ return BigInt(Math.floor(Date.now() / MILLISECONDS_PER_SECOND3));
4482
4316
  }
4483
4317
  function deriveDepositExpiryTimestamp(input) {
4484
4318
  if (input.depositDetectedTimestamp === null) {
@@ -4927,6 +4761,28 @@ function accountTypeToString(accountType) {
4927
4761
  }
4928
4762
  }
4929
4763
 
4764
+ // src/modules/lending/_internal/inflow-fee-rounding.ts
4765
+ var ETH_STABLECOIN_INFLOW_FEE_ROUND_UP_TO_NEAREST_10_CENTS = 100000n;
4766
+ var BTC_INFLOW_FEE_ROUND_UP_TO_NEAREST_SATS = 500n;
4767
+ function roundInflowFeeEstimate(request, totalFee) {
4768
+ if (totalFee <= 0n) {
4769
+ return 0n;
4770
+ }
4771
+ if (isEthStablecoin(request.asset, request.chain)) {
4772
+ return roundUpToNearest(
4773
+ totalFee,
4774
+ ETH_STABLECOIN_INFLOW_FEE_ROUND_UP_TO_NEAREST_10_CENTS
4775
+ );
4776
+ }
4777
+ if (request.asset === Asset.BTC && request.chain === Chain.BTC) {
4778
+ return roundUpToNearest(totalFee, BTC_INFLOW_FEE_ROUND_UP_TO_NEAREST_SATS);
4779
+ }
4780
+ return totalFee;
4781
+ }
4782
+ function roundUpToNearest(amount, roundingUnit) {
4783
+ return ceilDivBigint(amount, roundingUnit) * roundingUnit;
4784
+ }
4785
+
4930
4786
  // src/core/utils/retry.ts
4931
4787
  var MIN_ATTEMPTS = 1;
4932
4788
  var MIN_INITIAL_RETRY_DELAY_MS = 0;
@@ -4978,835 +4834,966 @@ function assertValidRetryOptions(options) {
4978
4834
  }
4979
4835
  }
4980
4836
 
4981
- // src/modules/lending/_internal/inflow-fee-rounding.ts
4982
- var ETH_STABLECOIN_INFLOW_FEE_ROUND_UP_TO_NEAREST_10_CENTS = 100000n;
4983
- var BTC_INFLOW_FEE_ROUND_UP_TO_NEAREST_SATS = 500n;
4984
- function roundInflowFeeEstimate(request, totalFee) {
4985
- if (totalFee <= 0n) {
4986
- return 0n;
4837
+ // src/modules/lending/_internal/supply-flow.ts
4838
+ var SUBMIT_INFLOW_MAX_ATTEMPTS = 4;
4839
+ var SUBMIT_INFLOW_INITIAL_RETRY_DELAY_MS = 1500;
4840
+ var SUBMIT_INFLOW_RETRY_BACKOFF_MULTIPLIER = 2;
4841
+ var ETH_APPROVAL_POLL_INTERVAL_MS = 2e3;
4842
+ var ETH_APPROVAL_MAX_POLLS = 30;
4843
+ var SupplyFlowExecutor = class {
4844
+ constructor(params) {
4845
+ this.params = params;
4987
4846
  }
4988
- if (isEthStablecoin(request.asset, request.chain)) {
4989
- return roundUpToNearest(
4990
- totalFee,
4991
- ETH_STABLECOIN_INFLOW_FEE_ROUND_UP_TO_NEAREST_10_CENTS
4847
+ params;
4848
+ async create(request) {
4849
+ const target = await resolveSupplyTarget(
4850
+ this.params.canisterContext,
4851
+ request
4992
4852
  );
4993
- }
4994
- if (request.asset === Asset.BTC && request.chain === Chain.BTC) {
4995
- return roundUpToNearest(totalFee, BTC_INFLOW_FEE_ROUND_UP_TO_NEAREST_SATS);
4996
- }
4997
- return totalFee;
4998
- }
4999
- function roundUpToNearest(amount, roundingUnit) {
5000
- return ceilDivBigint(amount, roundingUnit) * roundingUnit;
5001
- }
5002
- function mapCanisterOutflowDetails(outflow) {
5003
- const rawOutflowType = getVariantKey(outflow.outflow_type);
5004
- return {
5005
- id: outflow.id,
5006
- outflowType: normalizeOutflowType(rawOutflowType),
5007
- outflowRef: outflow.outflow_ref[0],
5008
- txid: outflow.txid[0],
5009
- amount: outflow.amount,
5010
- receiver: mapCanisterAccountType(outflow.receiver)
5011
- };
5012
- }
5013
- function mapCanisterAccountType(receiver) {
5014
- if ("Native" in receiver) {
5015
- return {
5016
- type: "Native",
5017
- account: receiver.Native.toText()
4853
+ const instruction = {
4854
+ poolId: request.poolId,
4855
+ asset: target.asset,
4856
+ chain: target.chain,
4857
+ action: request.action,
4858
+ target
5018
4859
  };
5019
- }
5020
- if ("AccountIdentifier" in receiver) {
4860
+ const mechanism = resolveSupplyMechanism({
4861
+ asset: instruction.asset,
4862
+ chain: instruction.chain,
4863
+ requestedMechanism: request.mechanism
4864
+ });
4865
+ const defaultSubmitInflowRequest = getDefaultSubmitInflowRequest({
4866
+ action: request.action,
4867
+ chain: mechanism === SupplyPlanType.contractInteraction ? Chain.ETH : instruction.chain
4868
+ });
4869
+ let txid;
4870
+ switch (mechanism) {
4871
+ case SupplyPlanType.transfer:
4872
+ if (request.walletAdapter) {
4873
+ txid = await this.sendAndSubmitNativeSupplyInflow({
4874
+ request,
4875
+ instruction,
4876
+ defaultSubmitInflowRequest
4877
+ });
4878
+ }
4879
+ break;
4880
+ case SupplyPlanType.contractInteraction:
4881
+ txid = await this.executeContractSupply({
4882
+ request,
4883
+ instruction,
4884
+ defaultSubmitInflowRequest
4885
+ });
4886
+ break;
4887
+ }
5021
4888
  return {
5022
- type: "AccountIdentifier",
5023
- account: receiver.AccountIdentifier
4889
+ type: mechanism,
4890
+ target: instruction.target,
4891
+ txid,
4892
+ status: createLiquidiumStatus({
4893
+ operation: mapSupplyActionToStatusOperation(request.action),
4894
+ state: txid ? "confirming" : "action_required"
4895
+ }),
4896
+ submit: async (submitRequest) => {
4897
+ return await this.submitSupplyFlowInflow({
4898
+ instruction,
4899
+ mechanism,
4900
+ defaultSubmitInflowRequest,
4901
+ submitRequest
4902
+ });
4903
+ }
5024
4904
  };
5025
4905
  }
5026
- if ("Icrc" in receiver) {
5027
- const subaccount = normalizeOptionalSubaccount2(receiver.Icrc.subaccount[0]);
5028
- return {
5029
- type: "Icrc",
5030
- owner: receiver.Icrc.owner.toText(),
5031
- subaccount,
5032
- account: encodeIcrcAccount({
5033
- owner: receiver.Icrc.owner,
5034
- subaccount
5035
- })
5036
- };
5037
- }
5038
- return {
5039
- type: "External",
5040
- account: receiver.External
5041
- };
5042
- }
5043
- function normalizeOptionalSubaccount2(subaccount) {
5044
- if (!subaccount) {
5045
- return void 0;
5046
- }
5047
- return subaccount instanceof Uint8Array ? subaccount : Uint8Array.from(subaccount);
5048
- }
5049
- function normalizeOutflowType(rawOutflowType) {
5050
- switch (rawOutflowType) {
5051
- case "Withdraw":
5052
- return OutflowType.withdraw;
5053
- case "Borrow":
5054
- return OutflowType.borrow;
5055
- case "FeeClaim":
5056
- return OutflowType.feeClaim;
5057
- default:
5058
- throw new LiquidiumError(
5059
- LiquidiumErrorCode.INTERNAL,
5060
- `Unsupported outflow type: ${rawOutflowType}`
5061
- );
5062
- }
5063
- }
5064
- function mapWalletChainToLendingChain(chain) {
5065
- switch (chain) {
5066
- case Chain.BTC:
5067
- return { BTC: null };
5068
- case Chain.ETH:
5069
- return { ETH: null };
5070
- }
5071
- }
5072
-
5073
- // src/modules/lending/lending.ts
5074
- var SUBMIT_INFLOW_MAX_ATTEMPTS = 4;
5075
- var SUBMIT_INFLOW_INITIAL_RETRY_DELAY_MS = 1500;
5076
- var SUBMIT_INFLOW_RETRY_BACKOFF_MULTIPLIER = 2;
5077
- var ETH_APPROVAL_POLL_INTERVAL_MS = 2e3;
5078
- var ETH_APPROVAL_MAX_POLLS = 30;
5079
- var LendingModule = class {
5080
- constructor(canisterContext, apiClient, evmReadClient) {
5081
- this.canisterContext = canisterContext;
5082
- this.apiClient = apiClient;
5083
- this.evmReadClient = evmReadClient;
4906
+ async getEvmSupplyContext(request) {
4907
+ const selectedPool = await this.params.getPoolById(request.poolId);
4908
+ return await this.getEvmSupplyContextForPool({
4909
+ request,
4910
+ asset: selectedPool.asset,
4911
+ chain: selectedPool.chain
4912
+ });
5084
4913
  }
5085
- canisterContext;
5086
- apiClient;
5087
- evmReadClient;
5088
- /**
5089
- * Prepares a withdraw action that can be signed and submitted later.
5090
- *
5091
- * Use this when you need explicit control over signing and submission.
5092
- *
5093
- * @param request - Profile, pool, amount (pool asset base units), outflow address, and signer wallet.
5094
- * @returns A signable {@link WithdrawAction} with `submit` wired to the canister.
5095
- */
5096
- async prepareWithdraw(request) {
5097
- const destinationAccount = request.receiverAddress.trim();
5098
- const signerAccount = normalizeEvmAddress(
5099
- request.signerWalletAddress.trim()
4914
+ async getEvmSupplyContextForPool(params) {
4915
+ const { request, asset, chain } = params;
4916
+ const evmReadClient = this.requireEvmReadClient(
4917
+ "EVM supply context requires an EVM RPC URL or public client"
5100
4918
  );
5101
- if (!destinationAccount) {
4919
+ const walletAddress = normalizeAndValidateEvmAddress(
4920
+ request.walletAddress,
4921
+ "Invalid EVM wallet address"
4922
+ );
4923
+ if (request.amount <= 0n) {
5102
4924
  throw new LiquidiumError(
5103
4925
  LiquidiumErrorCode.VALIDATION_ERROR,
5104
- "Withdraw requires a custom outflow account"
4926
+ "EVM supply context requires a positive amount"
5105
4927
  );
5106
4928
  }
5107
- if (!signerAccount) {
4929
+ if (!isEthStablecoin(asset, chain)) {
5108
4930
  throw new LiquidiumError(
5109
4931
  LiquidiumErrorCode.VALIDATION_ERROR,
5110
- "Withdraw requires a signer account"
4932
+ `EVM supply context only supports ETH stablecoin pools, received ${asset} on ${chain}`
5111
4933
  );
5112
4934
  }
5113
- const receiverAddress = await this.normalizeOutflowReceiverAddress({
5114
- poolId: request.poolId,
5115
- receiverAddress: destinationAccount
4935
+ const tokenAddress = getEthStablecoinContractAddress(asset);
4936
+ const spenderAddress = CK_ETH_DEPOSIT_CONTRACT_ADDRESS;
4937
+ const [allowance, balance] = await Promise.all([
4938
+ readErc20Allowance({
4939
+ evmReadClient,
4940
+ tokenAddress,
4941
+ ownerAddress: walletAddress,
4942
+ spenderAddress
4943
+ }),
4944
+ readErc20Balance({
4945
+ evmReadClient,
4946
+ tokenAddress,
4947
+ ownerAddress: walletAddress
4948
+ })
4949
+ ]);
4950
+ const approvalStrategy = getApprovalStrategy({
4951
+ allowance,
4952
+ amount: request.amount
5116
4953
  });
5117
- const lendingActor = createLendingActor(this.canisterContext);
5118
- try {
5119
- const expiryTimestamp = computeExpiryTimestampFromNow();
5120
- const nonce = await lendingActor.get_nonce(signerAccount);
5121
- const withdrawRequestData = {
5122
- profileId: request.profileId,
5123
- poolId: request.poolId,
5124
- amount: request.amount,
5125
- receiverAddress,
5126
- signerWalletAddress: signerAccount,
5127
- expiryTimestamp
5128
- };
5129
- return {
5130
- kind: WalletActionKind.createWithdraw,
5131
- executionKind: WalletExecutionKind.signMessage,
5132
- actionType: WalletActionKind.createWithdraw,
5133
- transferMode: TransferMode.native,
5134
- account: signerAccount,
5135
- message: createWithdrawAssetMessage(
5136
- {
5137
- pool_id: request.poolId,
5138
- amount: request.amount.toString(),
5139
- account: { type: "External", data: receiverAddress },
5140
- expiry_timestamp: expiryTimestamp
5141
- },
5142
- nonce
5143
- ),
5144
- data: withdrawRequestData,
5145
- submit: async (signatureInfo) => {
5146
- return await this.submitWithdraw(withdrawRequestData, signatureInfo);
5147
- }
5148
- };
5149
- } catch (error) {
5150
- if (error instanceof LiquidiumError) {
5151
- throw error;
5152
- }
5153
- throw mapCanisterCallErrorToLiquidiumError("get_nonce", error);
5154
- }
5155
- }
5156
- async submitWithdraw(request, signatureInfo) {
5157
- try {
5158
- const result = await createLendingActor(this.canisterContext).withdraw(
5159
- Principal.fromText(request.profileId),
5160
- {
5161
- data: {
5162
- expiry_timestamp: request.expiryTimestamp,
5163
- account: { External: request.receiverAddress },
5164
- pool_id: Principal.fromText(request.poolId),
5165
- amount: request.amount
5166
- },
5167
- signature_info: {
5168
- Wallet: {
5169
- signature: normalizeWalletSignature(
5170
- signatureInfo.signature,
5171
- signatureInfo.chain
5172
- ),
5173
- chain: mapWalletChainToLendingChain(signatureInfo.chain),
5174
- account: request.signerWalletAddress
5175
- }
5176
- }
5177
- }
5178
- );
5179
- if ("Err" in result) {
5180
- throw mapLendingProtocolErrorToLiquidiumError(result.Err);
5181
- }
5182
- return mapExpectedOutflowDetails(
5183
- mapCanisterOutflowDetails(result.Ok),
5184
- OutflowType.withdraw,
5185
- "withdraw"
5186
- );
5187
- } catch (error) {
5188
- if (error instanceof LiquidiumError) {
5189
- throw error;
5190
- }
5191
- throw mapCanisterCallErrorToLiquidiumError("withdraw", error);
5192
- }
5193
- }
5194
- /**
5195
- * Creates a withdraw outflow using the provided wallet adapter.
5196
- *
5197
- * This is the convenience form of `prepareWithdraw(...)` plus execution.
5198
- *
5199
- * @param params - Withdraw request fields plus `signerChain` and `signerWalletAdapter`.
5200
- * @returns The canister {@link OutflowDetails} for the completed withdraw.
5201
- */
5202
- async withdraw(params) {
5203
- const action = await this.prepareWithdraw(params);
5204
- return await executeWith({
5205
- walletAdapter: params.signerWalletAdapter,
5206
- chain: params.signerChain,
5207
- account: action.account
5208
- })(action);
4954
+ return {
4955
+ profileId: request.profileId,
4956
+ poolId: request.poolId,
4957
+ walletAddress,
4958
+ action: request.action,
4959
+ asset,
4960
+ chain: Chain.ETH,
4961
+ amount: request.amount.toString(),
4962
+ tokenAddress,
4963
+ spenderAddress,
4964
+ depositContractAddress: CK_ETH_DEPOSIT_CONTRACT_ADDRESS,
4965
+ balance: balance.toString(),
4966
+ allowance: allowance.toString(),
4967
+ requiresApproval: approvalStrategy !== EvmSupplyApprovalStrategy.none,
4968
+ approvalStrategy
4969
+ };
5209
4970
  }
5210
- /**
5211
- * Prepares a borrow action that can be signed and submitted later.
5212
- *
5213
- * Use this when you need explicit control over signing and submission.
5214
- *
5215
- * @param request - Profile, pool, amount (borrow asset base units), outflow address, and signer wallet.
5216
- * @returns A signable {@link BorrowAction} with `submit` wired to the canister.
5217
- */
5218
- async prepareBorrow(request) {
5219
- const destinationAccount = request.receiverAddress.trim();
5220
- const signerAccount = normalizeEvmAddress(
5221
- request.signerWalletAddress.trim()
5222
- );
5223
- if (!destinationAccount) {
4971
+ async sendAndSubmitNativeSupplyInflow(params) {
4972
+ const { request, instruction, defaultSubmitInflowRequest } = params;
4973
+ if (instruction.target.type !== "nativeAddress") {
5224
4974
  throw new LiquidiumError(
5225
4975
  LiquidiumErrorCode.VALIDATION_ERROR,
5226
- "Borrow requires a custom outflow account"
4976
+ "Wallet-executed supply requires a native-address target"
5227
4977
  );
5228
4978
  }
5229
- if (!signerAccount) {
4979
+ if (!request.walletAdapter) {
5230
4980
  throw new LiquidiumError(
5231
4981
  LiquidiumErrorCode.VALIDATION_ERROR,
5232
- "Borrow requires a signer account"
4982
+ "Wallet-executed supply requires a wallet adapter"
5233
4983
  );
5234
4984
  }
5235
- if (request.amount <= 0n) {
4985
+ const account = request.account?.trim();
4986
+ if (!account) {
5236
4987
  throw new LiquidiumError(
5237
4988
  LiquidiumErrorCode.VALIDATION_ERROR,
5238
- "Borrow amount must be greater than 0"
4989
+ "Wallet-executed transfer supply requires an account"
5239
4990
  );
5240
4991
  }
5241
- const selectedPool = await this.getPoolById(request.poolId);
5242
- const selectedAsset = selectedPool.asset;
5243
- const minimumBorrowAmountError = getBorrowAmountMinimumValidationError({
5244
- amount: request.amount,
5245
- asset: selectedAsset
5246
- });
5247
- if (minimumBorrowAmountError) {
4992
+ if (!request.amount || request.amount <= 0n) {
5248
4993
  throw new LiquidiumError(
5249
4994
  LiquidiumErrorCode.VALIDATION_ERROR,
5250
- minimumBorrowAmountError.message
4995
+ "Wallet-executed supply requires a positive amount"
5251
4996
  );
5252
4997
  }
5253
- const receiverAddress = normalizeExternalAddress({
5254
- address: destinationAccount,
5255
- asset: selectedAsset,
5256
- chain: selectedPool.chain
4998
+ const txid = await this.sendNativeSupplyTransaction({
4999
+ walletAdapter: request.walletAdapter,
5000
+ chain: instruction.chain,
5001
+ toAddress: instruction.target.address,
5002
+ amount: request.amount,
5003
+ senderAccount: account,
5004
+ asset: instruction.asset,
5005
+ action: request.action
5257
5006
  });
5258
- const lendingActor = createLendingActor(this.canisterContext);
5259
- try {
5260
- const expiryTimestamp = computeExpiryTimestampFromNow();
5261
- const nonce = await lendingActor.get_nonce(signerAccount);
5262
- const borrowRequestData = {
5263
- profileId: request.profileId,
5264
- poolId: request.poolId,
5265
- amount: request.amount,
5266
- receiverAddress,
5267
- signerWalletAddress: signerAccount,
5268
- expiryTimestamp
5269
- };
5007
+ if (shouldSubmitInflow({ instruction, mechanism: SupplyPlanType.transfer })) {
5008
+ try {
5009
+ await this.submitInflowWithRetry(txid, defaultSubmitInflowRequest);
5010
+ } catch {
5011
+ }
5012
+ }
5013
+ return txid;
5014
+ }
5015
+ async submitSupplyFlowInflow(params) {
5016
+ if (!shouldSubmitInflow(params)) {
5270
5017
  return {
5271
- kind: WalletActionKind.createBorrow,
5272
- executionKind: WalletExecutionKind.signMessage,
5273
- actionType: WalletActionKind.createBorrow,
5274
- transferMode: TransferMode.native,
5275
- account: signerAccount,
5276
- message: createBorrowAssetMessage(
5277
- {
5278
- pool_id: request.poolId,
5279
- amount: request.amount.toString(),
5280
- account: { type: "External", data: receiverAddress },
5281
- expiry_timestamp: expiryTimestamp
5282
- },
5283
- nonce
5284
- ),
5285
- data: borrowRequestData,
5286
- submit: async (signatureInfo) => {
5287
- return await this.submitBorrow(borrowRequestData, signatureInfo);
5288
- }
5018
+ txid: params.submitRequest.txid
5289
5019
  };
5290
- } catch (error) {
5291
- if (error instanceof LiquidiumError) {
5292
- throw error;
5293
- }
5294
- throw mapCanisterCallErrorToLiquidiumError("get_nonce", error);
5295
5020
  }
5021
+ const defaultSubmitInflowRequest = params.defaultSubmitInflowRequest;
5022
+ if (!defaultSubmitInflowRequest) {
5023
+ throw new LiquidiumError(
5024
+ LiquidiumErrorCode.VALIDATION_ERROR,
5025
+ "Supply flow submit requires an inflow operation"
5026
+ );
5027
+ }
5028
+ return await this.params.submitInflow({
5029
+ ...defaultSubmitInflowRequest,
5030
+ ...params.submitRequest
5031
+ });
5296
5032
  }
5297
- async submitBorrow(request, signatureInfo) {
5033
+ async executeContractSupply(params) {
5034
+ const { request, instruction, defaultSubmitInflowRequest } = params;
5035
+ if (instruction.target.type !== "icrcAccount" || instruction.chain !== Chain.ETH) {
5036
+ throw new LiquidiumError(
5037
+ LiquidiumErrorCode.VALIDATION_ERROR,
5038
+ "Contract-interaction supply is only supported for ETH ICRC-account targets"
5039
+ );
5040
+ }
5041
+ const walletAddressInput = request.account?.trim();
5042
+ if (!walletAddressInput) {
5043
+ throw new LiquidiumError(
5044
+ LiquidiumErrorCode.VALIDATION_ERROR,
5045
+ "Contract-interaction supply requires an account"
5046
+ );
5047
+ }
5048
+ const walletAddress = normalizeAndValidateEvmAddress(
5049
+ walletAddressInput,
5050
+ "Invalid EVM wallet address"
5051
+ );
5052
+ if (!request.amount || request.amount <= 0n) {
5053
+ throw new LiquidiumError(
5054
+ LiquidiumErrorCode.VALIDATION_ERROR,
5055
+ "Contract-interaction supply requires a positive amount"
5056
+ );
5057
+ }
5058
+ const walletAdapter = request.walletAdapter;
5059
+ if (!walletAdapter?.sendEthTransaction) {
5060
+ throw new LiquidiumError(
5061
+ LiquidiumErrorCode.VALIDATION_ERROR,
5062
+ "Contract-interaction supply requires an ETH wallet adapter"
5063
+ );
5064
+ }
5065
+ this.params.requireApi();
5066
+ this.requireEvmReadClient(
5067
+ "Contract-interaction supply requires an EVM RPC URL or public client"
5068
+ );
5069
+ const evmSupplyContext = await this.getEvmSupplyContextForPool({
5070
+ request: {
5071
+ profileId: request.profileId,
5072
+ poolId: request.poolId,
5073
+ walletAddress,
5074
+ amount: request.amount,
5075
+ action: request.action
5076
+ },
5077
+ asset: instruction.asset,
5078
+ chain: instruction.chain
5079
+ });
5080
+ const supplyAmount = BigInt(evmSupplyContext.amount);
5081
+ if (BigInt(evmSupplyContext.balance) < supplyAmount) {
5082
+ throw new LiquidiumError(
5083
+ LiquidiumErrorCode.INSUFFICIENT_FUNDS,
5084
+ `Insufficient ${evmSupplyContext.asset} balance for ${request.action}`
5085
+ );
5086
+ }
5087
+ if (evmSupplyContext.approvalStrategy === EvmSupplyApprovalStrategy.resetThenApproveMax) {
5088
+ await this.sendEthContractTransaction(
5089
+ walletAdapter,
5090
+ walletAddress,
5091
+ createApproveTransaction({
5092
+ tokenAddress: evmSupplyContext.tokenAddress,
5093
+ spenderAddress: evmSupplyContext.spenderAddress,
5094
+ amount: 0n
5095
+ }),
5096
+ `supply-${request.action}-approve-reset`
5097
+ );
5098
+ await this.waitForExpectedAllowance({
5099
+ walletAddress,
5100
+ tokenAddress: evmSupplyContext.tokenAddress,
5101
+ spenderAddress: evmSupplyContext.spenderAddress,
5102
+ amount: supplyAmount,
5103
+ expectation: "zero"
5104
+ });
5105
+ }
5106
+ if (evmSupplyContext.approvalStrategy !== EvmSupplyApprovalStrategy.none) {
5107
+ await this.sendEthContractTransaction(
5108
+ walletAdapter,
5109
+ walletAddress,
5110
+ createApproveTransaction({
5111
+ tokenAddress: evmSupplyContext.tokenAddress,
5112
+ spenderAddress: evmSupplyContext.spenderAddress,
5113
+ amount: MAX_UINT256
5114
+ }),
5115
+ `supply-${request.action}-approve-max`
5116
+ );
5117
+ await this.waitForExpectedAllowance({
5118
+ walletAddress,
5119
+ tokenAddress: evmSupplyContext.tokenAddress,
5120
+ spenderAddress: evmSupplyContext.spenderAddress,
5121
+ amount: supplyAmount,
5122
+ expectation: "sufficient"
5123
+ });
5124
+ }
5125
+ const depositTxid = await this.sendEthContractTransaction(
5126
+ walletAdapter,
5127
+ walletAddress,
5128
+ createDepositErc20Transaction({
5129
+ depositContractAddress: evmSupplyContext.depositContractAddress,
5130
+ tokenAddress: evmSupplyContext.tokenAddress,
5131
+ amount: supplyAmount,
5132
+ poolId: request.poolId,
5133
+ profileId: request.profileId,
5134
+ destinationAccount: instruction.target.account,
5135
+ action: request.action
5136
+ }),
5137
+ `supply-${request.action}-deposit-erc20`
5138
+ );
5298
5139
  try {
5299
- const result = await createLendingActor(
5300
- this.canisterContext
5301
- ).borrow_assets(Principal.fromText(request.profileId), {
5302
- data: {
5303
- expiry_timestamp: request.expiryTimestamp,
5304
- account: { External: request.receiverAddress },
5305
- pool_id: Principal.fromText(request.poolId),
5306
- amount: request.amount
5307
- },
5308
- signature_info: {
5309
- Wallet: {
5310
- signature: normalizeWalletSignature(
5311
- signatureInfo.signature,
5312
- signatureInfo.chain
5313
- ),
5314
- chain: mapWalletChainToLendingChain(signatureInfo.chain),
5315
- account: request.signerWalletAddress
5316
- }
5140
+ await this.submitInflowWithRetry(depositTxid, defaultSubmitInflowRequest);
5141
+ } catch {
5142
+ }
5143
+ return depositTxid;
5144
+ }
5145
+ async sendNativeSupplyTransaction(params) {
5146
+ switch (params.chain) {
5147
+ case Chain.BTC: {
5148
+ if (!params.walletAdapter.sendBtcTransaction) {
5149
+ throw new LiquidiumError(
5150
+ LiquidiumErrorCode.VALIDATION_ERROR,
5151
+ "BTC wallet adapter does not support transaction sending"
5152
+ );
5317
5153
  }
5318
- });
5319
- if ("Err" in result) {
5320
- throw mapLendingProtocolErrorToLiquidiumError(result.Err);
5154
+ return await params.walletAdapter.sendBtcTransaction({
5155
+ chain: Chain.BTC,
5156
+ toAddress: params.toAddress,
5157
+ amountSats: params.amount,
5158
+ account: params.senderAccount,
5159
+ actionType: `supply-${params.action}`,
5160
+ transferMode: TransferMode.native
5161
+ });
5321
5162
  }
5322
- return mapExpectedOutflowDetails(
5323
- mapCanisterOutflowDetails(result.Ok),
5324
- OutflowType.borrow,
5325
- "borrow_assets"
5326
- );
5327
- } catch (error) {
5328
- if (error instanceof LiquidiumError) {
5329
- throw error;
5163
+ case Chain.ETH: {
5164
+ if (!params.walletAdapter.sendEthTransaction) {
5165
+ throw new LiquidiumError(
5166
+ LiquidiumErrorCode.VALIDATION_ERROR,
5167
+ "ETH wallet adapter does not support transaction sending"
5168
+ );
5169
+ }
5170
+ if (isEthStablecoin(params.asset, params.chain)) {
5171
+ return await params.walletAdapter.sendEthTransaction({
5172
+ chain: Chain.ETH,
5173
+ account: params.senderAccount,
5174
+ actionType: `supply-${params.action}`,
5175
+ transferMode: TransferMode.native,
5176
+ transaction: createTransferErc20Transaction({
5177
+ tokenAddress: getEthStablecoinContractAddress(params.asset),
5178
+ recipientAddress: params.toAddress,
5179
+ amount: params.amount
5180
+ })
5181
+ });
5182
+ }
5183
+ return await params.walletAdapter.sendEthTransaction({
5184
+ chain: Chain.ETH,
5185
+ account: params.senderAccount,
5186
+ actionType: `supply-${params.action}`,
5187
+ transferMode: TransferMode.native,
5188
+ transaction: {
5189
+ to: params.toAddress,
5190
+ value: params.amount.toString()
5191
+ }
5192
+ });
5330
5193
  }
5331
- throw mapCanisterCallErrorToLiquidiumError("borrow_assets", error);
5194
+ default:
5195
+ throw new LiquidiumError(
5196
+ LiquidiumErrorCode.VALIDATION_ERROR,
5197
+ `Native-address wallet execution is not supported for ${params.chain}`
5198
+ );
5332
5199
  }
5333
5200
  }
5334
- /**
5335
- * Creates a borrow outflow using the provided wallet adapter.
5336
- *
5337
- * This is the convenience form of `prepareBorrow(...)` plus execution.
5338
- *
5339
- * @param params - Borrow request fields plus `signerChain` and `signerWalletAdapter`.
5340
- * @returns The lending canister receipt as {@link OutflowDetails}.
5341
- *
5342
- * @remarks
5343
- * `id` is always present. `txid` may be missing on the first response; the SDK does not
5344
- * poll for it. Use history or app-level polling if you need the chain transaction id.
5345
- */
5346
- async borrow(params) {
5347
- const action = await this.prepareBorrow(params);
5348
- return await executeWith({
5349
- walletAdapter: params.signerWalletAdapter,
5350
- chain: params.signerChain,
5351
- account: action.account
5352
- })(action);
5353
- }
5354
- /**
5355
- * Resolves a supply target for a deposit or repayment and optionally broadcasts it.
5356
- *
5357
- * Transfer mode can return manual broadcast instructions when wallet fields are
5358
- * omitted. Contract-interaction mode always requires `walletAdapter`, `account`,
5359
- * and `amount` because it must prepare and submit approval/deposit calls.
5360
- *
5361
- * The SDK does not poll for inflow status. When a `txid` is returned, it is the
5362
- * caller's responsibility to track confirmation state using their own polling.
5363
- *
5364
- * @returns A {@link SupplyFlow} receipt with `type`, `target`, `submit`, and
5365
- * an optional `txid` present when the SDK broadcast for you.
5366
- */
5367
- async supply(request) {
5368
- const target = await resolveSupplyTarget(this.canisterContext, request);
5369
- const instruction = {
5370
- poolId: request.poolId,
5371
- asset: target.asset,
5372
- chain: target.chain,
5373
- action: request.action,
5374
- target
5375
- };
5376
- const mechanism = resolveSupplyMechanism({
5377
- asset: instruction.asset,
5378
- chain: instruction.chain,
5379
- requestedMechanism: request.mechanism
5201
+ async submitInflowWithRetry(txid, extraRequest) {
5202
+ if (!extraRequest) {
5203
+ throw new LiquidiumError(
5204
+ LiquidiumErrorCode.VALIDATION_ERROR,
5205
+ "Inflow submission requires an operation"
5206
+ );
5207
+ }
5208
+ await retryWithBackoff({
5209
+ execute: async () => {
5210
+ await this.params.submitInflow({ txid, ...extraRequest });
5211
+ },
5212
+ maxAttempts: SUBMIT_INFLOW_MAX_ATTEMPTS,
5213
+ initialRetryDelayMs: SUBMIT_INFLOW_INITIAL_RETRY_DELAY_MS,
5214
+ backoffMultiplier: SUBMIT_INFLOW_RETRY_BACKOFF_MULTIPLIER,
5215
+ shouldRetryError: isRetriableInflowSubmitError
5380
5216
  });
5381
- const defaultSubmitInflowRequest = getDefaultSubmitInflowRequest({
5382
- action: request.action,
5383
- chain: mechanism === SupplyPlanType.contractInteraction ? Chain.ETH : instruction.chain
5217
+ }
5218
+ async sendEthContractTransaction(walletAdapter, walletAddress, request, actionType) {
5219
+ if (!walletAdapter.sendEthTransaction) {
5220
+ throw new LiquidiumError(
5221
+ LiquidiumErrorCode.VALIDATION_ERROR,
5222
+ "ETH wallet adapter does not support transaction sending"
5223
+ );
5224
+ }
5225
+ return await walletAdapter.sendEthTransaction({
5226
+ chain: Chain.ETH,
5227
+ account: walletAddress,
5228
+ actionType,
5229
+ transferMode: TransferMode.native,
5230
+ transaction: request
5384
5231
  });
5385
- let txid;
5386
- switch (mechanism) {
5387
- case SupplyPlanType.transfer:
5388
- if (request.walletAdapter) {
5389
- txid = await this.sendAndSubmitNativeSupplyInflow({
5390
- request,
5391
- instruction,
5392
- defaultSubmitInflowRequest
5393
- });
5394
- }
5395
- break;
5396
- case SupplyPlanType.contractInteraction:
5397
- txid = await this.executeContractSupply({
5398
- request,
5399
- instruction,
5400
- defaultSubmitInflowRequest
5232
+ }
5233
+ async waitForExpectedAllowance(params) {
5234
+ const evmReadClient = this.requireEvmReadClient(
5235
+ "Contract-interaction supply requires an EVM RPC URL or public client"
5236
+ );
5237
+ let lastPollingError;
5238
+ for (let pollIndex = 0; pollIndex < ETH_APPROVAL_MAX_POLLS; pollIndex += 1) {
5239
+ try {
5240
+ const allowance = await readErc20Allowance({
5241
+ evmReadClient,
5242
+ tokenAddress: params.tokenAddress,
5243
+ ownerAddress: params.walletAddress,
5244
+ spenderAddress: params.spenderAddress
5401
5245
  });
5402
- break;
5246
+ const matchesExpectation = params.expectation === "zero" ? allowance === 0n : allowance >= params.amount;
5247
+ if (matchesExpectation) {
5248
+ return;
5249
+ }
5250
+ } catch (error) {
5251
+ lastPollingError = error;
5252
+ }
5253
+ await delay(ETH_APPROVAL_POLL_INTERVAL_MS);
5403
5254
  }
5255
+ const lastPollingErrorMessage = getUnknownErrorMessage(lastPollingError);
5256
+ throw new LiquidiumError(
5257
+ LiquidiumErrorCode.SERVICE_UNAVAILABLE,
5258
+ lastPollingErrorMessage ? `Timed out waiting for ${params.expectation} ETH allowance update. Last polling error: ${lastPollingErrorMessage}` : `Timed out waiting for ${params.expectation} ETH allowance update`
5259
+ );
5260
+ }
5261
+ requireEvmReadClient(message) {
5262
+ if (!this.params.evmReadClient) {
5263
+ throw new LiquidiumError(LiquidiumErrorCode.VALIDATION_ERROR, message);
5264
+ }
5265
+ return this.params.evmReadClient;
5266
+ }
5267
+ };
5268
+ async function readErc20Allowance(params) {
5269
+ const allowance = await params.evmReadClient.readContract({
5270
+ address: params.tokenAddress,
5271
+ abi: ERC20_ABI,
5272
+ functionName: "allowance",
5273
+ args: [
5274
+ params.ownerAddress,
5275
+ params.spenderAddress
5276
+ ]
5277
+ });
5278
+ return BigInt(allowance);
5279
+ }
5280
+ async function readErc20Balance(params) {
5281
+ const balance = await params.evmReadClient.readContract({
5282
+ address: params.tokenAddress,
5283
+ abi: ERC20_ABI,
5284
+ functionName: "balanceOf",
5285
+ args: [params.ownerAddress]
5286
+ });
5287
+ return BigInt(balance);
5288
+ }
5289
+ function getApprovalStrategy(params) {
5290
+ if (params.allowance >= params.amount) {
5291
+ return EvmSupplyApprovalStrategy.none;
5292
+ }
5293
+ if (params.allowance === 0n) {
5294
+ return EvmSupplyApprovalStrategy.approveMax;
5295
+ }
5296
+ return EvmSupplyApprovalStrategy.resetThenApproveMax;
5297
+ }
5298
+ async function delay(timeoutMs) {
5299
+ await new Promise((resolve) => setTimeout(resolve, timeoutMs));
5300
+ }
5301
+ function isRetriableInflowSubmitError(error) {
5302
+ if (!(error instanceof LiquidiumError)) {
5303
+ return false;
5304
+ }
5305
+ return error.code === LiquidiumErrorCode.SERVICE_UNAVAILABLE;
5306
+ }
5307
+ function getUnknownErrorMessage(error) {
5308
+ if (error instanceof Error) {
5309
+ return error.message;
5310
+ }
5311
+ if (typeof error === "string") {
5312
+ return error;
5313
+ }
5314
+ return null;
5315
+ }
5316
+ function getDefaultSubmitInflowRequest(params) {
5317
+ if (params.chain !== Chain.BTC && params.chain !== Chain.ETH) {
5318
+ return void 0;
5319
+ }
5320
+ return {
5321
+ chain: params.chain,
5322
+ operation: mapSupplyActionToStatusOperation(params.action)
5323
+ };
5324
+ }
5325
+ function mapSupplyActionToStatusOperation(action) {
5326
+ return action === SupplyAction.repayment ? "repayment" : "deposit";
5327
+ }
5328
+ function shouldSubmitInflow(params) {
5329
+ if (params.mechanism !== SupplyPlanType.transfer) {
5330
+ return true;
5331
+ }
5332
+ return !isEthStablecoin(params.instruction.asset, params.instruction.chain);
5333
+ }
5334
+ function mapCanisterOutflowDetails(outflow) {
5335
+ const rawOutflowType = getVariantKey(outflow.outflow_type);
5336
+ return {
5337
+ id: outflow.id,
5338
+ outflowType: normalizeOutflowType(rawOutflowType),
5339
+ outflowRef: outflow.outflow_ref[0],
5340
+ txid: outflow.txid[0],
5341
+ amount: outflow.amount,
5342
+ receiver: mapCanisterAccountType(outflow.receiver)
5343
+ };
5344
+ }
5345
+ function mapCanisterAccountType(receiver) {
5346
+ if ("Native" in receiver) {
5404
5347
  return {
5405
- type: mechanism,
5406
- target: instruction.target,
5407
- txid,
5408
- submit: async (submitRequest) => {
5409
- return await this.submitSupplyFlowInflow({
5410
- instruction,
5411
- mechanism,
5412
- defaultSubmitInflowRequest,
5413
- submitRequest
5414
- });
5415
- }
5348
+ type: "Native",
5349
+ account: receiver.Native.toText()
5350
+ };
5351
+ }
5352
+ if ("AccountIdentifier" in receiver) {
5353
+ return {
5354
+ type: "AccountIdentifier",
5355
+ account: receiver.AccountIdentifier
5416
5356
  };
5417
5357
  }
5358
+ if ("Icrc" in receiver) {
5359
+ const subaccount = normalizeOptionalSubaccount2(receiver.Icrc.subaccount[0]);
5360
+ return {
5361
+ type: "Icrc",
5362
+ owner: receiver.Icrc.owner.toText(),
5363
+ subaccount,
5364
+ account: encodeIcrcAccount({
5365
+ owner: receiver.Icrc.owner,
5366
+ subaccount
5367
+ })
5368
+ };
5369
+ }
5370
+ return {
5371
+ type: "External",
5372
+ account: receiver.External
5373
+ };
5374
+ }
5375
+ function normalizeOptionalSubaccount2(subaccount) {
5376
+ if (!subaccount) {
5377
+ return void 0;
5378
+ }
5379
+ return subaccount instanceof Uint8Array ? subaccount : Uint8Array.from(subaccount);
5380
+ }
5381
+ function normalizeOutflowType(rawOutflowType) {
5382
+ switch (rawOutflowType) {
5383
+ case "Withdraw":
5384
+ return OutflowType.withdrawal;
5385
+ case "Borrow":
5386
+ return OutflowType.borrow;
5387
+ case "FeeClaim":
5388
+ return OutflowType.feeClaim;
5389
+ default:
5390
+ throw new LiquidiumError(
5391
+ LiquidiumErrorCode.INTERNAL,
5392
+ `Unsupported outflow type: ${rawOutflowType}`
5393
+ );
5394
+ }
5395
+ }
5396
+ function mapWalletChainToLendingChain(chain) {
5397
+ switch (chain) {
5398
+ case Chain.BTC:
5399
+ return { BTC: null };
5400
+ case Chain.ETH:
5401
+ return { ETH: null };
5402
+ }
5403
+ }
5404
+
5405
+ // src/modules/lending/lending.ts
5406
+ var LendingModule = class {
5407
+ constructor(canisterContext, apiClient, evmReadClient) {
5408
+ this.canisterContext = canisterContext;
5409
+ this.apiClient = apiClient;
5410
+ this.evmReadClient = evmReadClient;
5411
+ }
5412
+ canisterContext;
5413
+ apiClient;
5414
+ evmReadClient;
5418
5415
  /**
5419
- * Fetches ERC-20 supply planning data with the configured EVM read client.
5416
+ * Prepares a withdraw action that can be signed and submitted later.
5420
5417
  *
5421
- * Requires `evmRpcUrl` or `evmPublicClient` on the client. Used internally by
5422
- * contract-interaction `supply`.
5418
+ * Use this when you need explicit control over signing and submission.
5423
5419
  *
5424
- * @param request - Profile, pool, wallet, amount (token base units), and action.
5425
- * @returns Locally computed {@link EvmSupplyContext} for approvals and deposit.
5420
+ * @param request - Profile, pool, amount (pool asset base units), outflow address, and signer wallet.
5421
+ * @returns A signable {@link WithdrawAction} with `submit` wired to the canister.
5426
5422
  */
5427
- async getEvmSupplyContext(request) {
5428
- const selectedPool = await this.getPoolById(request.poolId);
5429
- return await this.getEvmSupplyContextForPool({
5430
- request,
5431
- asset: selectedPool.asset,
5432
- chain: selectedPool.chain
5433
- });
5434
- }
5435
- async getEvmSupplyContextForPool(params) {
5436
- const { request, asset, chain } = params;
5437
- const evmReadClient = this.requireEvmReadClient(
5438
- "EVM supply context requires an EVM RPC URL or public client"
5439
- );
5440
- const walletAddress = normalizeAndValidateEvmAddress(
5441
- request.walletAddress,
5442
- "Invalid EVM wallet address"
5423
+ async prepareWithdraw(request) {
5424
+ const destinationAccount = request.receiverAddress.trim();
5425
+ const signerAccount = normalizeEvmAddress(
5426
+ request.signerWalletAddress.trim()
5443
5427
  );
5444
- if (request.amount <= 0n) {
5428
+ if (!destinationAccount) {
5445
5429
  throw new LiquidiumError(
5446
5430
  LiquidiumErrorCode.VALIDATION_ERROR,
5447
- "EVM supply context requires a positive amount"
5431
+ "Withdraw requires a custom outflow account"
5448
5432
  );
5449
5433
  }
5450
- if (!isEthStablecoin(asset, chain)) {
5434
+ if (!signerAccount) {
5451
5435
  throw new LiquidiumError(
5452
5436
  LiquidiumErrorCode.VALIDATION_ERROR,
5453
- `EVM supply context only supports ETH stablecoin pools, received ${asset} on ${chain}`
5437
+ "Withdraw requires a signer account"
5454
5438
  );
5455
5439
  }
5456
- const tokenAddress = getEthStablecoinContractAddress(asset);
5457
- const spenderAddress = CK_ETH_DEPOSIT_CONTRACT_ADDRESS;
5458
- const [allowance, balance] = await Promise.all([
5459
- readErc20Allowance({
5460
- evmReadClient,
5461
- tokenAddress,
5462
- ownerAddress: walletAddress,
5463
- spenderAddress
5464
- }),
5465
- readErc20Balance({
5466
- evmReadClient,
5467
- tokenAddress,
5468
- ownerAddress: walletAddress
5469
- })
5470
- ]);
5471
- const approvalStrategy = getApprovalStrategy({
5472
- allowance,
5473
- amount: request.amount
5474
- });
5475
- return {
5476
- success: true,
5477
- profileId: request.profileId,
5440
+ const receiverAddress = await this.normalizeOutflowReceiverAddress({
5478
5441
  poolId: request.poolId,
5479
- walletAddress,
5480
- action: request.action,
5481
- asset,
5482
- chain: Chain.ETH,
5483
- amount: request.amount.toString(),
5484
- tokenAddress,
5485
- spenderAddress,
5486
- depositContractAddress: CK_ETH_DEPOSIT_CONTRACT_ADDRESS,
5487
- balance: balance.toString(),
5488
- allowance: allowance.toString(),
5489
- requiresApproval: approvalStrategy !== EvmSupplyApprovalStrategy.none,
5490
- approvalStrategy
5491
- };
5492
- }
5493
- /**
5494
- * Returns the read-only deposit address for an ETH stablecoin inflow target.
5495
- *
5496
- * This is a query call that does not create or mutate state. Use it when you
5497
- * need the deposit address without hitting the authorization-gated update path.
5498
- *
5499
- * @param request - Profile, pool, asset, and supply action.
5500
- * @returns The EVM deposit address for the derived account.
5501
- */
5502
- async getDepositAddress(request) {
5503
- if (!isEthStablecoin(request.asset, Chain.ETH)) {
5504
- throw new LiquidiumError(
5505
- LiquidiumErrorCode.VALIDATION_ERROR,
5506
- "getDepositAddress is only supported for ETH stablecoins"
5507
- );
5508
- }
5509
- const tokenAddress = getEthStablecoinContractAddress(request.asset);
5510
- const subaccount = encodeInflowSubaccount({
5511
- action: request.action,
5512
- principal: Principal.fromText(request.profileId)
5442
+ receiverAddress: destinationAccount
5513
5443
  });
5514
- const result = await createDepositAccountsActor(
5515
- this.canisterContext
5516
- ).get_deposit_address(
5517
- {
5518
- owner: Principal.fromText(request.poolId),
5519
- subaccount: [subaccount]
5520
- },
5521
- [tokenAddress]
5522
- );
5523
- if ("Err" in result) {
5524
- throw mapDepositAccountErrorToLiquidiumError(result.Err);
5525
- }
5526
- return result.Ok;
5527
- }
5528
- /**
5529
- * Estimates the network/deposit fee for an inflow target.
5530
- *
5531
- * ETH stablecoin deposit-address estimates are served by the deposit-address
5532
- * canister. BTC estimates include the ckBTC minter deposit fee and ledger fee.
5533
- *
5534
- * @param request - Asset and chain pair to estimate for.
5535
- * @returns Total fee estimate rounded up in the asset's base units.
5536
- */
5537
- async estimateInflowFee(request) {
5538
- if (isEthStablecoin(request.asset, request.chain)) {
5539
- const result = await createDepositAccountsActor(
5540
- this.canisterContext
5541
- ).estimate_deposit_fee([getEthStablecoinContractAddress(request.asset)]);
5542
- if ("Err" in result) {
5543
- throw mapDepositAccountErrorToLiquidiumError(result.Err);
5544
- }
5545
- return {
5546
- totalFee: roundInflowFeeEstimate(request, result.Ok)
5444
+ const lendingActor = createLendingActor(this.canisterContext);
5445
+ try {
5446
+ const expiryTimestamp = computeExpiryTimestampFromNow();
5447
+ const nonce = await lendingActor.get_nonce(signerAccount);
5448
+ const withdrawRequestData = {
5449
+ profileId: request.profileId,
5450
+ poolId: request.poolId,
5451
+ amount: request.amount,
5452
+ receiverAddress,
5453
+ signerWalletAddress: signerAccount,
5454
+ expiryTimestamp
5547
5455
  };
5548
- }
5549
- if (request.asset === Asset.BTC && request.chain === Chain.BTC) {
5550
- const estimate = await this.estimateBtcInflowFee();
5551
5456
  return {
5552
- totalFee: roundInflowFeeEstimate(request, estimate.totalFee)
5457
+ kind: WalletActionKind.createWithdraw,
5458
+ executionKind: WalletExecutionKind.signMessage,
5459
+ actionType: WalletActionKind.createWithdraw,
5460
+ transferMode: TransferMode.native,
5461
+ account: signerAccount,
5462
+ message: createWithdrawAssetMessage(
5463
+ {
5464
+ pool_id: request.poolId,
5465
+ amount: request.amount.toString(),
5466
+ account: { type: "External", data: receiverAddress },
5467
+ expiry_timestamp: expiryTimestamp
5468
+ },
5469
+ nonce
5470
+ ),
5471
+ data: withdrawRequestData,
5472
+ submit: async (signatureInfo) => {
5473
+ return await this.submitWithdraw(withdrawRequestData, signatureInfo);
5474
+ }
5553
5475
  };
5476
+ } catch (error) {
5477
+ if (error instanceof LiquidiumError) {
5478
+ throw error;
5479
+ }
5480
+ throw mapCanisterCallErrorToLiquidiumError("get_nonce", error);
5554
5481
  }
5555
- throw new LiquidiumError(
5556
- LiquidiumErrorCode.VALIDATION_ERROR,
5557
- `Inflow fee estimates are not supported for ${request.asset} on ${request.chain}`
5558
- );
5559
- }
5560
- async estimateBtcInflowFee() {
5561
- const [minterFee, ledgerFee] = await Promise.all([
5562
- createCkBtcMinterActor(this.canisterContext).get_deposit_fee(),
5563
- createCkBtcLedgerActor(this.canisterContext).icrc1_fee()
5564
- ]);
5565
- return { totalFee: minterFee + ledgerFee };
5566
5482
  }
5567
- async sendAndSubmitNativeSupplyInflow(params) {
5568
- const { request, instruction, defaultSubmitInflowRequest } = params;
5569
- if (instruction.target.type !== "nativeAddress") {
5570
- throw new LiquidiumError(
5571
- LiquidiumErrorCode.VALIDATION_ERROR,
5572
- "Wallet-executed supply requires a native-address target"
5573
- );
5574
- }
5575
- if (!request.walletAdapter) {
5576
- throw new LiquidiumError(
5577
- LiquidiumErrorCode.VALIDATION_ERROR,
5578
- "Wallet-executed supply requires a wallet adapter"
5579
- );
5580
- }
5581
- const account = request.account?.trim();
5582
- if (!account) {
5583
- throw new LiquidiumError(
5584
- LiquidiumErrorCode.VALIDATION_ERROR,
5585
- "Wallet-executed transfer supply requires an account"
5586
- );
5587
- }
5588
- if (!request.amount || request.amount <= 0n) {
5589
- throw new LiquidiumError(
5590
- LiquidiumErrorCode.VALIDATION_ERROR,
5591
- "Wallet-executed supply requires a positive amount"
5483
+ async submitWithdraw(request, signatureInfo) {
5484
+ try {
5485
+ const result = await createLendingActor(this.canisterContext).withdraw(
5486
+ Principal.fromText(request.profileId),
5487
+ {
5488
+ data: {
5489
+ expiry_timestamp: request.expiryTimestamp,
5490
+ account: { External: request.receiverAddress },
5491
+ pool_id: Principal.fromText(request.poolId),
5492
+ amount: request.amount
5493
+ },
5494
+ signature_info: {
5495
+ Wallet: {
5496
+ signature: normalizeWalletSignature(
5497
+ signatureInfo.signature,
5498
+ signatureInfo.chain
5499
+ ),
5500
+ chain: mapWalletChainToLendingChain(signatureInfo.chain),
5501
+ account: request.signerWalletAddress
5502
+ }
5503
+ }
5504
+ }
5592
5505
  );
5506
+ if ("Err" in result) {
5507
+ throw mapLendingProtocolErrorToLiquidiumError(result.Err);
5508
+ }
5509
+ return mapExpectedOutflowDetails(
5510
+ mapCanisterOutflowDetails(result.Ok),
5511
+ OutflowType.withdrawal,
5512
+ "withdraw"
5513
+ );
5514
+ } catch (error) {
5515
+ if (error instanceof LiquidiumError) {
5516
+ throw error;
5517
+ }
5518
+ throw mapCanisterCallErrorToLiquidiumError("withdraw", error);
5593
5519
  }
5594
- const txid = await this.sendNativeSupplyTransaction({
5595
- walletAdapter: request.walletAdapter,
5596
- chain: instruction.chain,
5597
- toAddress: instruction.target.address,
5598
- amount: request.amount,
5599
- senderAccount: account,
5600
- asset: instruction.asset,
5601
- action: request.action
5602
- });
5603
- if (shouldSubmitInflow({ instruction, mechanism: SupplyPlanType.transfer })) {
5604
- await this.submitInflowWithRetry(txid, defaultSubmitInflowRequest);
5605
- }
5606
- return txid;
5607
5520
  }
5608
- async submitSupplyFlowInflow(params) {
5609
- if (!shouldSubmitInflow(params)) {
5610
- return { success: true, txid: params.submitRequest.txid };
5611
- }
5612
- return await this.submitInflow({
5613
- ...params.defaultSubmitInflowRequest,
5614
- ...params.submitRequest
5615
- });
5521
+ /**
5522
+ * Creates a withdraw outflow using the provided wallet adapter.
5523
+ *
5524
+ * This is the convenience form of `prepareWithdraw(...)` plus execution.
5525
+ *
5526
+ * @param params - Withdraw request fields plus `signerChain` and `signerWalletAdapter`.
5527
+ * @returns The canister {@link OutflowDetails} for the completed withdraw.
5528
+ */
5529
+ async withdraw(params) {
5530
+ const action = await this.prepareWithdraw(params);
5531
+ return await executeWith({
5532
+ walletAdapter: params.signerWalletAdapter,
5533
+ chain: params.signerChain,
5534
+ account: action.account
5535
+ })(action);
5616
5536
  }
5617
- async executeContractSupply(params) {
5618
- const { request, instruction, defaultSubmitInflowRequest } = params;
5619
- if (instruction.target.type !== "icrcAccount" || instruction.chain !== Chain.ETH) {
5537
+ /**
5538
+ * Prepares a borrow action that can be signed and submitted later.
5539
+ *
5540
+ * Use this when you need explicit control over signing and submission.
5541
+ *
5542
+ * @param request - Profile, pool, amount (borrow asset base units), outflow address, and signer wallet.
5543
+ * @returns A signable {@link BorrowAction} with `submit` wired to the canister.
5544
+ */
5545
+ async prepareBorrow(request) {
5546
+ const destinationAccount = request.receiverAddress.trim();
5547
+ const signerAccount = normalizeEvmAddress(
5548
+ request.signerWalletAddress.trim()
5549
+ );
5550
+ if (!destinationAccount) {
5620
5551
  throw new LiquidiumError(
5621
5552
  LiquidiumErrorCode.VALIDATION_ERROR,
5622
- "Contract-interaction supply is only supported for ETH ICRC-account targets"
5553
+ "Borrow requires a custom outflow account"
5623
5554
  );
5624
5555
  }
5625
- const walletAddressInput = request.account?.trim();
5626
- if (!walletAddressInput) {
5556
+ if (!signerAccount) {
5627
5557
  throw new LiquidiumError(
5628
5558
  LiquidiumErrorCode.VALIDATION_ERROR,
5629
- "Contract-interaction supply requires an account"
5559
+ "Borrow requires a signer account"
5630
5560
  );
5631
5561
  }
5632
- const walletAddress = normalizeAndValidateEvmAddress(
5633
- walletAddressInput,
5634
- "Invalid EVM wallet address"
5635
- );
5636
- if (!request.amount || request.amount <= 0n) {
5562
+ if (request.amount <= 0n) {
5637
5563
  throw new LiquidiumError(
5638
5564
  LiquidiumErrorCode.VALIDATION_ERROR,
5639
- "Contract-interaction supply requires a positive amount"
5565
+ "Borrow amount must be greater than 0"
5640
5566
  );
5641
5567
  }
5642
- const walletAdapter = request.walletAdapter;
5643
- if (!walletAdapter?.sendEthTransaction) {
5568
+ const selectedPool = await this.getPoolById(request.poolId);
5569
+ const selectedAsset = selectedPool.asset;
5570
+ const minimumBorrowAmountError = getBorrowAmountMinimumValidationError({
5571
+ amount: request.amount,
5572
+ asset: selectedAsset
5573
+ });
5574
+ if (minimumBorrowAmountError) {
5644
5575
  throw new LiquidiumError(
5645
5576
  LiquidiumErrorCode.VALIDATION_ERROR,
5646
- "Contract-interaction supply requires an ETH wallet adapter"
5577
+ minimumBorrowAmountError.message
5647
5578
  );
5648
5579
  }
5649
- this.requireApi();
5650
- this.requireEvmReadClient(
5651
- "Contract-interaction supply requires an EVM RPC URL or public client"
5652
- );
5653
- const evmSupplyContext = await this.getEvmSupplyContextForPool({
5654
- request: {
5580
+ const receiverAddress = normalizeExternalAddress({
5581
+ address: destinationAccount,
5582
+ asset: selectedAsset,
5583
+ chain: selectedPool.chain
5584
+ });
5585
+ const lendingActor = createLendingActor(this.canisterContext);
5586
+ try {
5587
+ const expiryTimestamp = computeExpiryTimestampFromNow();
5588
+ const nonce = await lendingActor.get_nonce(signerAccount);
5589
+ const borrowRequestData = {
5655
5590
  profileId: request.profileId,
5656
5591
  poolId: request.poolId,
5657
- walletAddress,
5658
5592
  amount: request.amount,
5659
- action: request.action
5660
- },
5661
- asset: instruction.asset,
5662
- chain: instruction.chain
5663
- });
5664
- const supplyAmount = BigInt(evmSupplyContext.amount);
5665
- if (BigInt(evmSupplyContext.balance) < supplyAmount) {
5666
- throw new LiquidiumError(
5667
- LiquidiumErrorCode.INSUFFICIENT_FUNDS,
5668
- `Insufficient ${evmSupplyContext.asset} balance for ${request.action}`
5669
- );
5593
+ receiverAddress,
5594
+ signerWalletAddress: signerAccount,
5595
+ expiryTimestamp
5596
+ };
5597
+ return {
5598
+ kind: WalletActionKind.createBorrow,
5599
+ executionKind: WalletExecutionKind.signMessage,
5600
+ actionType: WalletActionKind.createBorrow,
5601
+ transferMode: TransferMode.native,
5602
+ account: signerAccount,
5603
+ message: createBorrowAssetMessage(
5604
+ {
5605
+ pool_id: request.poolId,
5606
+ amount: request.amount.toString(),
5607
+ account: { type: "External", data: receiverAddress },
5608
+ expiry_timestamp: expiryTimestamp
5609
+ },
5610
+ nonce
5611
+ ),
5612
+ data: borrowRequestData,
5613
+ submit: async (signatureInfo) => {
5614
+ return await this.submitBorrow(borrowRequestData, signatureInfo);
5615
+ }
5616
+ };
5617
+ } catch (error) {
5618
+ if (error instanceof LiquidiumError) {
5619
+ throw error;
5620
+ }
5621
+ throw mapCanisterCallErrorToLiquidiumError("get_nonce", error);
5670
5622
  }
5671
- if (evmSupplyContext.approvalStrategy === EvmSupplyApprovalStrategy.resetThenApproveMax) {
5672
- await this.sendEthContractTransaction(
5673
- walletAdapter,
5674
- walletAddress,
5675
- createApproveTransaction({
5676
- tokenAddress: evmSupplyContext.tokenAddress,
5677
- spenderAddress: evmSupplyContext.spenderAddress,
5678
- amount: 0n
5679
- }),
5680
- `supply-${request.action}-approve-reset`
5681
- );
5682
- await this.waitForExpectedAllowance({
5683
- walletAddress,
5684
- tokenAddress: evmSupplyContext.tokenAddress,
5685
- spenderAddress: evmSupplyContext.spenderAddress,
5686
- amount: supplyAmount,
5687
- expectation: "zero"
5623
+ }
5624
+ async submitBorrow(request, signatureInfo) {
5625
+ try {
5626
+ const result = await createLendingActor(
5627
+ this.canisterContext
5628
+ ).borrow_assets(Principal.fromText(request.profileId), {
5629
+ data: {
5630
+ expiry_timestamp: request.expiryTimestamp,
5631
+ account: { External: request.receiverAddress },
5632
+ pool_id: Principal.fromText(request.poolId),
5633
+ amount: request.amount
5634
+ },
5635
+ signature_info: {
5636
+ Wallet: {
5637
+ signature: normalizeWalletSignature(
5638
+ signatureInfo.signature,
5639
+ signatureInfo.chain
5640
+ ),
5641
+ chain: mapWalletChainToLendingChain(signatureInfo.chain),
5642
+ account: request.signerWalletAddress
5643
+ }
5644
+ }
5688
5645
  });
5689
- }
5690
- if (evmSupplyContext.approvalStrategy !== EvmSupplyApprovalStrategy.none) {
5691
- await this.sendEthContractTransaction(
5692
- walletAdapter,
5693
- walletAddress,
5694
- createApproveTransaction({
5695
- tokenAddress: evmSupplyContext.tokenAddress,
5696
- spenderAddress: evmSupplyContext.spenderAddress,
5697
- amount: MAX_UINT256
5698
- }),
5699
- `supply-${request.action}-approve-max`
5646
+ if ("Err" in result) {
5647
+ throw mapLendingProtocolErrorToLiquidiumError(result.Err);
5648
+ }
5649
+ return mapExpectedOutflowDetails(
5650
+ mapCanisterOutflowDetails(result.Ok),
5651
+ OutflowType.borrow,
5652
+ "borrow_assets"
5653
+ );
5654
+ } catch (error) {
5655
+ if (error instanceof LiquidiumError) {
5656
+ throw error;
5657
+ }
5658
+ throw mapCanisterCallErrorToLiquidiumError("borrow_assets", error);
5659
+ }
5660
+ }
5661
+ /**
5662
+ * Creates a borrow outflow using the provided wallet adapter.
5663
+ *
5664
+ * This is the convenience form of `prepareBorrow(...)` plus execution.
5665
+ *
5666
+ * @param params - Borrow request fields plus `signerChain` and `signerWalletAdapter`.
5667
+ * @returns The lending canister receipt as {@link OutflowDetails}.
5668
+ *
5669
+ * @remarks
5670
+ * `id` is always present. `txid` may be missing on the first response; the SDK does not
5671
+ * poll for it. Use history or app-level polling if you need the chain transaction id.
5672
+ */
5673
+ async borrow(params) {
5674
+ const action = await this.prepareBorrow(params);
5675
+ return await executeWith({
5676
+ walletAdapter: params.signerWalletAdapter,
5677
+ chain: params.signerChain,
5678
+ account: action.account
5679
+ })(action);
5680
+ }
5681
+ /**
5682
+ * Resolves a supply target for a deposit or repayment and optionally broadcasts it.
5683
+ *
5684
+ * Transfer mode can return manual broadcast instructions when wallet fields are
5685
+ * omitted. Contract-interaction mode always requires `walletAdapter`, `account`,
5686
+ * and `amount` because it must prepare and submit approval/deposit calls.
5687
+ *
5688
+ * The SDK does not poll for inflow status. When a `txid` is returned, it is the
5689
+ * caller's responsibility to track confirmation state using their own polling.
5690
+ *
5691
+ * @returns A {@link SupplyFlow} receipt with `type`, `target`, `submit`, and
5692
+ * an optional `txid` present when the SDK broadcast for you.
5693
+ */
5694
+ async supply(request) {
5695
+ return await this.createSupplyFlowExecutor().create(request);
5696
+ }
5697
+ /**
5698
+ * Fetches ERC-20 supply planning data with the configured EVM read client.
5699
+ *
5700
+ * Requires `evmRpcUrl` or `evmPublicClient` on the client. Used internally by
5701
+ * contract-interaction `supply`.
5702
+ *
5703
+ * @param request - Profile, pool, wallet, amount (token base units), and action.
5704
+ * @returns Locally computed {@link EvmSupplyContext} for approvals and deposit.
5705
+ */
5706
+ async getEvmSupplyContext(request) {
5707
+ return await this.createSupplyFlowExecutor().getEvmSupplyContext(request);
5708
+ }
5709
+ /**
5710
+ * Returns the read-only deposit address for an ETH stablecoin inflow target.
5711
+ *
5712
+ * This is a query call that does not create or mutate state. Use it when you
5713
+ * need the deposit address without hitting the authorization-gated update path.
5714
+ *
5715
+ * @param request - Profile, pool, asset, and supply action.
5716
+ * @returns The EVM deposit address for the derived account.
5717
+ */
5718
+ async getDepositAddress(request) {
5719
+ if (!isEthStablecoin(request.asset, Chain.ETH)) {
5720
+ throw new LiquidiumError(
5721
+ LiquidiumErrorCode.VALIDATION_ERROR,
5722
+ "getDepositAddress is only supported for ETH stablecoins"
5700
5723
  );
5701
- await this.waitForExpectedAllowance({
5702
- walletAddress,
5703
- tokenAddress: evmSupplyContext.tokenAddress,
5704
- spenderAddress: evmSupplyContext.spenderAddress,
5705
- amount: supplyAmount,
5706
- expectation: "sufficient"
5707
- });
5708
5724
  }
5709
- const depositTxid = await this.sendEthContractTransaction(
5710
- walletAdapter,
5711
- walletAddress,
5712
- createDepositErc20Transaction({
5713
- depositContractAddress: evmSupplyContext.depositContractAddress,
5714
- tokenAddress: evmSupplyContext.tokenAddress,
5715
- amount: supplyAmount,
5716
- poolId: request.poolId,
5717
- profileId: request.profileId,
5718
- destinationAccount: instruction.target.account,
5719
- action: request.action
5720
- }),
5721
- `supply-${request.action}-deposit-erc20`
5725
+ const tokenAddress = getEthStablecoinContractAddress(request.asset);
5726
+ const subaccount = encodeInflowSubaccount({
5727
+ action: request.action,
5728
+ principal: Principal.fromText(request.profileId)
5729
+ });
5730
+ const result = await createDepositAccountsActor(
5731
+ this.canisterContext
5732
+ ).get_deposit_address(
5733
+ {
5734
+ owner: Principal.fromText(request.poolId),
5735
+ subaccount: [subaccount]
5736
+ },
5737
+ [tokenAddress]
5722
5738
  );
5723
- try {
5724
- await this.submitInflowWithRetry(depositTxid, defaultSubmitInflowRequest);
5725
- } catch {
5739
+ if ("Err" in result) {
5740
+ throw mapDepositAccountErrorToLiquidiumError(result.Err);
5726
5741
  }
5727
- return depositTxid;
5742
+ return result.Ok;
5728
5743
  }
5729
- async sendNativeSupplyTransaction(params) {
5730
- switch (params.chain) {
5731
- case Chain.BTC: {
5732
- if (!params.walletAdapter.sendBtcTransaction) {
5733
- throw new LiquidiumError(
5734
- LiquidiumErrorCode.VALIDATION_ERROR,
5735
- "BTC wallet adapter does not support transaction sending"
5736
- );
5737
- }
5738
- return await params.walletAdapter.sendBtcTransaction({
5739
- chain: Chain.BTC,
5740
- toAddress: params.toAddress,
5741
- amountSats: params.amount,
5742
- account: params.senderAccount,
5743
- actionType: `supply-${params.action}`,
5744
- transferMode: TransferMode.native
5745
- });
5746
- }
5747
- case Chain.ETH: {
5748
- if (!params.walletAdapter.sendEthTransaction) {
5749
- throw new LiquidiumError(
5750
- LiquidiumErrorCode.VALIDATION_ERROR,
5751
- "ETH wallet adapter does not support transaction sending"
5752
- );
5753
- }
5754
- if (isEthStablecoin(params.asset, params.chain)) {
5755
- return await params.walletAdapter.sendEthTransaction({
5756
- chain: Chain.ETH,
5757
- account: params.senderAccount,
5758
- actionType: `supply-${params.action}`,
5759
- transferMode: TransferMode.native,
5760
- transaction: createTransferErc20Transaction({
5761
- tokenAddress: getEthStablecoinContractAddress(params.asset),
5762
- recipientAddress: params.toAddress,
5763
- amount: params.amount
5764
- })
5765
- });
5766
- }
5767
- return await params.walletAdapter.sendEthTransaction({
5768
- chain: Chain.ETH,
5769
- account: params.senderAccount,
5770
- actionType: `supply-${params.action}`,
5771
- transferMode: TransferMode.native,
5772
- transaction: {
5773
- to: params.toAddress,
5774
- value: params.amount.toString()
5775
- }
5776
- });
5744
+ /**
5745
+ * Estimates the network/deposit fee for an inflow target.
5746
+ *
5747
+ * ETH stablecoin deposit-address estimates are served by the deposit-address
5748
+ * canister. BTC estimates include the ckBTC minter deposit fee and ledger fee.
5749
+ *
5750
+ * @param request - Asset and chain pair to estimate for.
5751
+ * @returns Total fee estimate rounded up in the asset's base units.
5752
+ */
5753
+ async estimateInflowFee(request) {
5754
+ if (isEthStablecoin(request.asset, request.chain)) {
5755
+ const result = await createDepositAccountsActor(
5756
+ this.canisterContext
5757
+ ).estimate_deposit_fee([getEthStablecoinContractAddress(request.asset)]);
5758
+ if ("Err" in result) {
5759
+ throw mapDepositAccountErrorToLiquidiumError(result.Err);
5777
5760
  }
5778
- default:
5779
- throw new LiquidiumError(
5780
- LiquidiumErrorCode.VALIDATION_ERROR,
5781
- `Native-address wallet execution is not supported for ${params.chain}`
5782
- );
5761
+ return {
5762
+ totalFee: roundInflowFeeEstimate(request, result.Ok)
5763
+ };
5764
+ }
5765
+ if (request.asset === Asset.BTC && request.chain === Chain.BTC) {
5766
+ const estimate = await this.estimateBtcInflowFee();
5767
+ return {
5768
+ totalFee: roundInflowFeeEstimate(request, estimate.totalFee)
5769
+ };
5783
5770
  }
5771
+ throw new LiquidiumError(
5772
+ LiquidiumErrorCode.VALIDATION_ERROR,
5773
+ `Inflow fee estimates are not supported for ${request.asset} on ${request.chain}`
5774
+ );
5784
5775
  }
5785
- async submitInflowWithRetry(txid, extraRequest) {
5786
- await retryWithBackoff({
5787
- execute: async () => {
5788
- await this.submitInflow({ txid, ...extraRequest });
5789
- },
5790
- maxAttempts: SUBMIT_INFLOW_MAX_ATTEMPTS,
5791
- initialRetryDelayMs: SUBMIT_INFLOW_INITIAL_RETRY_DELAY_MS,
5792
- backoffMultiplier: SUBMIT_INFLOW_RETRY_BACKOFF_MULTIPLIER,
5793
- shouldRetryError: isRetriableInflowSubmitError
5794
- });
5776
+ async estimateBtcInflowFee() {
5777
+ const [minterFee, ledgerFee] = await Promise.all([
5778
+ createCkBtcMinterActor(this.canisterContext).get_deposit_fee(),
5779
+ createCkBtcLedgerActor(this.canisterContext).icrc1_fee()
5780
+ ]);
5781
+ return { totalFee: minterFee + ledgerFee };
5795
5782
  }
5796
5783
  /**
5797
5784
  * Submits an inflow transaction id for faster indexing.
5798
5785
  *
5799
5786
  * Uses the Liquidium SDK API.
5800
5787
  *
5801
- * @param request - Broadcast `txid` plus optional `chain` and inflow `type`.
5788
+ * @param request - Broadcast `txid` plus inflow `operation` and optional `chain`.
5802
5789
  * @returns Acknowledgement including the submitted `txid`.
5803
5790
  */
5804
5791
  async submitInflow(request) {
5805
5792
  const apiClient = this.requireApi();
5806
- return await apiClient.post(
5807
- SdkApiPath.inflow,
5808
- request
5809
- );
5793
+ const response = await apiClient.post(SdkApiPath.inflow, request);
5794
+ return {
5795
+ txid: response.txid
5796
+ };
5810
5797
  }
5811
5798
  /**
5812
5799
  * Returns whether borrowing is currently disabled by the protocol.
@@ -5837,11 +5824,16 @@ var LendingModule = class {
5837
5824
  }
5838
5825
  return this.apiClient;
5839
5826
  }
5840
- requireEvmReadClient(message) {
5841
- if (!this.evmReadClient) {
5842
- throw new LiquidiumError(LiquidiumErrorCode.VALIDATION_ERROR, message);
5843
- }
5844
- return this.evmReadClient;
5827
+ createSupplyFlowExecutor() {
5828
+ return new SupplyFlowExecutor({
5829
+ canisterContext: this.canisterContext,
5830
+ evmReadClient: this.evmReadClient,
5831
+ getPoolById: async (poolId) => await this.getPoolById(poolId),
5832
+ requireApi: () => {
5833
+ this.requireApi();
5834
+ },
5835
+ submitInflow: async (request) => await this.submitInflow(request)
5836
+ });
5845
5837
  }
5846
5838
  async getPoolById(poolId) {
5847
5839
  const pools = await createFlexibleLendingActor(
@@ -5867,80 +5859,7 @@ var LendingModule = class {
5867
5859
  chain: selectedPool.chain
5868
5860
  });
5869
5861
  }
5870
- async sendEthContractTransaction(walletAdapter, walletAddress, request, actionType) {
5871
- if (!walletAdapter.sendEthTransaction) {
5872
- throw new LiquidiumError(
5873
- LiquidiumErrorCode.VALIDATION_ERROR,
5874
- "ETH wallet adapter does not support transaction sending"
5875
- );
5876
- }
5877
- return await walletAdapter.sendEthTransaction({
5878
- chain: Chain.ETH,
5879
- account: walletAddress,
5880
- actionType,
5881
- transferMode: TransferMode.native,
5882
- transaction: request
5883
- });
5884
- }
5885
- async waitForExpectedAllowance(params) {
5886
- const evmReadClient = this.requireEvmReadClient(
5887
- "Contract-interaction supply requires an EVM RPC URL or public client"
5888
- );
5889
- let lastPollingError;
5890
- for (let pollIndex = 0; pollIndex < ETH_APPROVAL_MAX_POLLS; pollIndex += 1) {
5891
- try {
5892
- const allowance = await readErc20Allowance({
5893
- evmReadClient,
5894
- tokenAddress: params.tokenAddress,
5895
- ownerAddress: params.walletAddress,
5896
- spenderAddress: params.spenderAddress
5897
- });
5898
- const matchesExpectation = params.expectation === "zero" ? allowance === 0n : allowance >= params.amount;
5899
- if (matchesExpectation) {
5900
- return;
5901
- }
5902
- } catch (error) {
5903
- lastPollingError = error;
5904
- }
5905
- await delay(ETH_APPROVAL_POLL_INTERVAL_MS);
5906
- }
5907
- const lastPollingErrorMessage = getUnknownErrorMessage(lastPollingError);
5908
- throw new LiquidiumError(
5909
- LiquidiumErrorCode.SERVICE_UNAVAILABLE,
5910
- lastPollingErrorMessage ? `Timed out waiting for ${params.expectation} ETH allowance update. Last polling error: ${lastPollingErrorMessage}` : `Timed out waiting for ${params.expectation} ETH allowance update`
5911
- );
5912
- }
5913
5862
  };
5914
- async function readErc20Allowance(params) {
5915
- const allowance = await params.evmReadClient.readContract({
5916
- address: params.tokenAddress,
5917
- abi: ERC20_ABI,
5918
- functionName: "allowance",
5919
- args: [
5920
- params.ownerAddress,
5921
- params.spenderAddress
5922
- ]
5923
- });
5924
- return BigInt(allowance);
5925
- }
5926
- async function readErc20Balance(params) {
5927
- const balance = await params.evmReadClient.readContract({
5928
- address: params.tokenAddress,
5929
- abi: ERC20_ABI,
5930
- functionName: "balanceOf",
5931
- args: [params.ownerAddress]
5932
- });
5933
- return BigInt(balance);
5934
- }
5935
- function getApprovalStrategy(params) {
5936
- if (params.allowance >= params.amount) {
5937
- return EvmSupplyApprovalStrategy.none;
5938
- }
5939
- if (params.allowance === 0n) {
5940
- return EvmSupplyApprovalStrategy.approveMax;
5941
- }
5942
- return EvmSupplyApprovalStrategy.resetThenApproveMax;
5943
- }
5944
5863
  function mapExpectedOutflowDetails(details, expectedOutflowType, operation) {
5945
5864
  if (details.outflowType !== expectedOutflowType) {
5946
5865
  throw new LiquidiumError(
@@ -5948,42 +5867,22 @@ function mapExpectedOutflowDetails(details, expectedOutflowType, operation) {
5948
5867
  `${operation} returned unexpected outflow type ${details.outflowType}`
5949
5868
  );
5950
5869
  }
5951
- return details;
5952
- }
5953
- async function delay(timeoutMs) {
5954
- await new Promise((resolve) => setTimeout(resolve, timeoutMs));
5955
- }
5956
- function isRetriableInflowSubmitError(error) {
5957
- if (!(error instanceof LiquidiumError)) {
5958
- return false;
5959
- }
5960
- return error.code === LiquidiumErrorCode.SERVICE_UNAVAILABLE;
5961
- }
5962
- function getUnknownErrorMessage(error) {
5963
- if (error instanceof Error) {
5964
- return error.message;
5965
- }
5966
- if (typeof error === "string") {
5967
- return error;
5968
- }
5969
- return null;
5970
- }
5971
- function getDefaultSubmitInflowRequest(params) {
5972
- if (params.chain !== Chain.BTC && params.chain !== Chain.ETH) {
5973
- return void 0;
5974
- }
5975
5870
  return {
5976
- chain: params.chain,
5977
- type: params.action === SupplyAction.repayment ? InflowSubmitType.REPAY : InflowSubmitType.DEPOSIT
5871
+ ...details,
5872
+ status: createLiquidiumStatus({
5873
+ operation: mapOutflowTypeToStatusOperation(expectedOutflowType),
5874
+ state: details.txid ? "confirming" : "processing"
5875
+ })
5978
5876
  };
5979
5877
  }
5980
- function shouldSubmitInflow(params) {
5981
- if (params.mechanism !== SupplyPlanType.transfer) {
5982
- return true;
5983
- }
5984
- return !isEthStablecoin(params.instruction.asset, params.instruction.chain);
5878
+ function mapOutflowTypeToStatusOperation(outflowType) {
5879
+ return outflowType;
5985
5880
  }
5986
5881
 
5882
+ // src/core/rates.ts
5883
+ var RATE_SCALE2 = 1000000000000000000000000000n;
5884
+ var RATE_DECIMALS = BigInt(RATE_SCALE2.toString().length - 1);
5885
+
5987
5886
  // src/modules/market/mappers.ts
5988
5887
  var DECIMAL_BASE = 10;
5989
5888
  var PAIR_SEPARATOR = "_";
@@ -6062,16 +5961,18 @@ var MarketModule = class {
6062
5961
  canisterContext;
6063
5962
  apiClient;
6064
5963
  /**
6065
- * Lists all pools with their current rates.
5964
+ * Lists SDK-supported pools with their current rates.
6066
5965
  *
6067
- * @returns All configured lending pools enriched with their current rate data.
5966
+ * Unsupported asset or chain variants returned by the canister are omitted.
5967
+ *
5968
+ * @returns Supported lending pools enriched with their current rate data.
6068
5969
  */
6069
5970
  async listPools() {
6070
5971
  void this.apiClient;
6071
5972
  try {
6072
5973
  const flexibleActor = createFlexibleLendingActor(this.canisterContext);
6073
5974
  const rawPools = await flexibleActor.list_pools();
6074
- const decodedPools = rawPools.map(decodeFlexiblePool).filter((pool) => pool !== null);
5975
+ const decodedPools = decodeSupportedFlexiblePools(rawPools);
6075
5976
  return await Promise.all(
6076
5977
  decodedPools.map(async (pool) => {
6077
5978
  const poolRate = await flexibleActor.get_pool_rate(pool.principal);
@@ -6178,6 +6079,17 @@ var MarketModule = class {
6178
6079
  }
6179
6080
  }
6180
6081
  };
6082
+ function decodeSupportedFlexiblePools(rawPools) {
6083
+ const decodedPools = [];
6084
+ for (const rawPool of rawPools) {
6085
+ const decodedPool = decodeFlexiblePool(rawPool);
6086
+ if (!decodedPool) {
6087
+ continue;
6088
+ }
6089
+ decodedPools.push(decodedPool);
6090
+ }
6091
+ return decodedPools;
6092
+ }
6181
6093
 
6182
6094
  // src/modules/positions/mappers.ts
6183
6095
  var USD_VALUE_SCALE_DECIMALS = 27n;
@@ -6508,6 +6420,7 @@ var LiquidiumClient = class {
6508
6420
  this.instantLoans = new InstantLoansModule(
6509
6421
  this.canisterContext,
6510
6422
  this.apiClient,
6423
+ this.activities,
6511
6424
  this.lending,
6512
6425
  this.positions
6513
6426
  );
@@ -6529,6 +6442,6 @@ function resolveEvmReadClient(config) {
6529
6442
  });
6530
6443
  }
6531
6444
 
6532
- export { AccountsModule, ActivitiesModule, ActivityDirection, ActivityFilter, ActivityKind, ActivityStatus, Asset, CK_ETH_DEPOSIT_CONTRACT_ADDRESS, Chain, Environment, EvmSupplyApprovalStrategy, HistoryModule, InflowSubmitType, InstantLoanStatus, InstantLoansModule, LendingModule, LiquidiumClient, LiquidiumError, LiquidiumErrorCode, MIN_BORROW_AMOUNTS_BY_ASSET, MarketModule, OutflowType, PositionsModule, QuoteModule, QuoteValidationErrorCode, QuoteWarningCode, RATE_DECIMALS, RATE_SCALE, SupplyAction, SupplyPlanType, TransferMode, USDC_CONTRACT_ADDRESS, USDT_CONTRACT_ADDRESS, UserHistoryStatus, WalletExecutionKind, createTransferErc20Transaction, executeWith, getMinimumBorrowAmount, intFromPublicId, publicIdFromInt };
6445
+ export { AccountsModule, ActivitiesModule, ActivityFilter, Asset, CK_ETH_DEPOSIT_CONTRACT_ADDRESS, Chain, Environment, EvmSupplyApprovalStrategy, HistoryModule, InstantLoansModule, LendingModule, LiquidiumClient, LiquidiumError, LiquidiumErrorCode, MIN_BORROW_AMOUNTS_BY_ASSET, MarketModule, OutflowType, PositionsModule, QuoteModule, QuoteValidationErrorCode, QuoteWarningCode, RATE_DECIMALS, RATE_SCALE2 as RATE_SCALE, SupplyAction, SupplyPlanType, TransferMode, USDC_CONTRACT_ADDRESS, USDT_CONTRACT_ADDRESS, WalletActionKind, WalletExecutionKind, createTransferErc20Transaction, executeWith, getMinimumBorrowAmount, intFromPublicId, publicIdFromInt };
6533
6446
  //# sourceMappingURL=index.js.map
6534
6447
  //# sourceMappingURL=index.js.map