@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/README.md +20 -3
- package/dist/index.cjs +1114 -1206
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +92 -277
- package/dist/index.d.ts +92 -277
- package/dist/index.js +1113 -1200
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30,11 +30,7 @@ var SupplyAction = {
|
|
|
30
30
|
var OutflowType = {
|
|
31
31
|
borrow: "borrow",
|
|
32
32
|
feeClaim: "feeClaim",
|
|
33
|
-
|
|
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", "
|
|
1372
|
-
var KNOWN_CHAIN_TAGS = ["BTC", "ETH"
|
|
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
|
|
1686
|
-
|
|
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
|
-
|
|
1699
|
-
|
|
1700
|
-
to: "to",
|
|
1701
|
-
types: "types"
|
|
1692
|
+
states: "states",
|
|
1693
|
+
to: "to"
|
|
1702
1694
|
};
|
|
1703
|
-
var ACTIVITIES =
|
|
1704
|
-
var
|
|
1705
|
-
var
|
|
1706
|
-
var
|
|
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.
|
|
1743
|
-
query.set(SdkApiQueryParam.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
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
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
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
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
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
|
|
1980
|
+
`Invalid activity operation: ${wire.status.operation}`
|
|
2049
1981
|
);
|
|
2050
1982
|
}
|
|
2051
|
-
function
|
|
2052
|
-
|
|
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
|
|
2061
|
-
|
|
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
|
|
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
|
|
2219
|
-
* @param
|
|
2220
|
-
* @returns Paginated
|
|
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
|
|
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 (
|
|
2244
|
-
query.set(SdkApiQueryParam.cursor,
|
|
2029
|
+
if (filters.cursor) {
|
|
2030
|
+
query.set(SdkApiQueryParam.cursor, filters.cursor);
|
|
2245
2031
|
}
|
|
2246
|
-
if (
|
|
2247
|
-
query.set(SdkApiQueryParam.market,
|
|
2032
|
+
if (filters.market) {
|
|
2033
|
+
query.set(SdkApiQueryParam.market, filters.market);
|
|
2248
2034
|
}
|
|
2249
|
-
if (
|
|
2250
|
-
query.set(SdkApiQueryParam.poolId,
|
|
2035
|
+
if (filters.poolId) {
|
|
2036
|
+
query.set(SdkApiQueryParam.poolId, filters.poolId);
|
|
2251
2037
|
}
|
|
2252
|
-
if (
|
|
2253
|
-
query.set(SdkApiQueryParam.
|
|
2038
|
+
if (filters.operations?.length) {
|
|
2039
|
+
query.set(SdkApiQueryParam.operations, filters.operations.join(","));
|
|
2254
2040
|
}
|
|
2255
|
-
if (
|
|
2041
|
+
if (filters.states?.length) {
|
|
2256
2042
|
query.set(
|
|
2257
|
-
SdkApiQueryParam.
|
|
2258
|
-
|
|
2043
|
+
SdkApiQueryParam.states,
|
|
2044
|
+
createHistoryStateFilterParam(filters.states)
|
|
2259
2045
|
);
|
|
2260
2046
|
}
|
|
2261
|
-
if (
|
|
2262
|
-
query.set(SdkApiQueryParam.from,
|
|
2047
|
+
if (filters.from) {
|
|
2048
|
+
query.set(SdkApiQueryParam.from, filters.from);
|
|
2263
2049
|
}
|
|
2264
|
-
if (
|
|
2265
|
-
query.set(SdkApiQueryParam.to,
|
|
2050
|
+
if (filters.to) {
|
|
2051
|
+
query.set(SdkApiQueryParam.to, filters.to);
|
|
2266
2052
|
}
|
|
2267
|
-
if (
|
|
2268
|
-
query.set(SdkApiQueryParam.limit, String(
|
|
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
|
-
|
|
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 (
|
|
2285
|
-
query.set(SdkApiQueryParam.cursor,
|
|
2073
|
+
if (filters.cursor) {
|
|
2074
|
+
query.set(SdkApiQueryParam.cursor, filters.cursor);
|
|
2286
2075
|
}
|
|
2287
|
-
if (
|
|
2288
|
-
query.set(SdkApiQueryParam.market,
|
|
2076
|
+
if (filters.market) {
|
|
2077
|
+
query.set(SdkApiQueryParam.market, filters.market);
|
|
2289
2078
|
}
|
|
2290
|
-
if (
|
|
2291
|
-
query.set(SdkApiQueryParam.poolId,
|
|
2079
|
+
if (filters.poolId) {
|
|
2080
|
+
query.set(SdkApiQueryParam.poolId, filters.poolId);
|
|
2292
2081
|
}
|
|
2293
|
-
if (
|
|
2294
|
-
query.set(SdkApiQueryParam.from,
|
|
2082
|
+
if (filters.from) {
|
|
2083
|
+
query.set(SdkApiQueryParam.from, filters.from);
|
|
2295
2084
|
}
|
|
2296
|
-
if (
|
|
2297
|
-
query.set(SdkApiQueryParam.to,
|
|
2085
|
+
if (filters.to) {
|
|
2086
|
+
query.set(SdkApiQueryParam.to, filters.to);
|
|
2298
2087
|
}
|
|
2299
|
-
if (
|
|
2300
|
-
query.set(SdkApiQueryParam.limit, String(
|
|
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:
|
|
2105
|
+
status: item.status,
|
|
2318
2106
|
txids: item.txids
|
|
2319
2107
|
};
|
|
2320
2108
|
}
|
|
2321
2109
|
function mapUserLiquidationHistoryEntry(item) {
|
|
2322
|
-
if (item.
|
|
2110
|
+
if (item.status.operation !== "liquidation" || item.status.state !== "completed") {
|
|
2323
2111
|
throw new LiquidiumError(
|
|
2324
2112
|
LiquidiumErrorCode.INTERNAL,
|
|
2325
|
-
`Invalid liquidation history
|
|
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:
|
|
2121
|
+
status: item.status,
|
|
2335
2122
|
txids: item.txids
|
|
2336
2123
|
};
|
|
2337
2124
|
}
|
|
2338
|
-
function
|
|
2339
|
-
|
|
2340
|
-
|
|
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
|
|
2129
|
+
return [...new Set(states)].join(",");
|
|
2366
2130
|
}
|
|
2367
|
-
function
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
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
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
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
|
|
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
|
-
|
|
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 /
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
4982
|
-
var
|
|
4983
|
-
var
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
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
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4847
|
+
params;
|
|
4848
|
+
async create(request) {
|
|
4849
|
+
const target = await resolveSupplyTarget(
|
|
4850
|
+
this.params.canisterContext,
|
|
4851
|
+
request
|
|
4992
4852
|
);
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
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
|
-
|
|
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:
|
|
5023
|
-
|
|
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
|
-
|
|
5027
|
-
const
|
|
5028
|
-
return {
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
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
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
4926
|
+
"EVM supply context requires a positive amount"
|
|
5105
4927
|
);
|
|
5106
4928
|
}
|
|
5107
|
-
if (!
|
|
4929
|
+
if (!isEthStablecoin(asset, chain)) {
|
|
5108
4930
|
throw new LiquidiumError(
|
|
5109
4931
|
LiquidiumErrorCode.VALIDATION_ERROR,
|
|
5110
|
-
|
|
4932
|
+
`EVM supply context only supports ETH stablecoin pools, received ${asset} on ${chain}`
|
|
5111
4933
|
);
|
|
5112
4934
|
}
|
|
5113
|
-
const
|
|
5114
|
-
|
|
5115
|
-
|
|
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
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
4976
|
+
"Wallet-executed supply requires a native-address target"
|
|
5227
4977
|
);
|
|
5228
4978
|
}
|
|
5229
|
-
if (!
|
|
4979
|
+
if (!request.walletAdapter) {
|
|
5230
4980
|
throw new LiquidiumError(
|
|
5231
4981
|
LiquidiumErrorCode.VALIDATION_ERROR,
|
|
5232
|
-
"
|
|
4982
|
+
"Wallet-executed supply requires a wallet adapter"
|
|
5233
4983
|
);
|
|
5234
4984
|
}
|
|
5235
|
-
|
|
4985
|
+
const account = request.account?.trim();
|
|
4986
|
+
if (!account) {
|
|
5236
4987
|
throw new LiquidiumError(
|
|
5237
4988
|
LiquidiumErrorCode.VALIDATION_ERROR,
|
|
5238
|
-
"
|
|
4989
|
+
"Wallet-executed transfer supply requires an account"
|
|
5239
4990
|
);
|
|
5240
4991
|
}
|
|
5241
|
-
|
|
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
|
-
|
|
4995
|
+
"Wallet-executed supply requires a positive amount"
|
|
5251
4996
|
);
|
|
5252
4997
|
}
|
|
5253
|
-
const
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
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
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
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
|
-
|
|
5320
|
-
|
|
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
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
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
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
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
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
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
|
-
|
|
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:
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
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
|
-
*
|
|
5416
|
+
* Prepares a withdraw action that can be signed and submitted later.
|
|
5420
5417
|
*
|
|
5421
|
-
*
|
|
5422
|
-
* contract-interaction `supply`.
|
|
5418
|
+
* Use this when you need explicit control over signing and submission.
|
|
5423
5419
|
*
|
|
5424
|
-
* @param request - Profile, pool,
|
|
5425
|
-
* @returns
|
|
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
|
|
5428
|
-
const
|
|
5429
|
-
|
|
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 (
|
|
5428
|
+
if (!destinationAccount) {
|
|
5445
5429
|
throw new LiquidiumError(
|
|
5446
5430
|
LiquidiumErrorCode.VALIDATION_ERROR,
|
|
5447
|
-
"
|
|
5431
|
+
"Withdraw requires a custom outflow account"
|
|
5448
5432
|
);
|
|
5449
5433
|
}
|
|
5450
|
-
if (!
|
|
5434
|
+
if (!signerAccount) {
|
|
5451
5435
|
throw new LiquidiumError(
|
|
5452
5436
|
LiquidiumErrorCode.VALIDATION_ERROR,
|
|
5453
|
-
|
|
5437
|
+
"Withdraw requires a signer account"
|
|
5454
5438
|
);
|
|
5455
5439
|
}
|
|
5456
|
-
const
|
|
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
|
-
|
|
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
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
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
|
-
|
|
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
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
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
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
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
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
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
|
-
"
|
|
5553
|
+
"Borrow requires a custom outflow account"
|
|
5623
5554
|
);
|
|
5624
5555
|
}
|
|
5625
|
-
|
|
5626
|
-
if (!walletAddressInput) {
|
|
5556
|
+
if (!signerAccount) {
|
|
5627
5557
|
throw new LiquidiumError(
|
|
5628
5558
|
LiquidiumErrorCode.VALIDATION_ERROR,
|
|
5629
|
-
"
|
|
5559
|
+
"Borrow requires a signer account"
|
|
5630
5560
|
);
|
|
5631
5561
|
}
|
|
5632
|
-
|
|
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
|
-
"
|
|
5565
|
+
"Borrow amount must be greater than 0"
|
|
5640
5566
|
);
|
|
5641
5567
|
}
|
|
5642
|
-
const
|
|
5643
|
-
|
|
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
|
-
|
|
5577
|
+
minimumBorrowAmountError.message
|
|
5647
5578
|
);
|
|
5648
5579
|
}
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
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
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
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
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
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
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
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
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
}
|
|
5721
|
-
|
|
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
|
-
|
|
5724
|
-
|
|
5725
|
-
} catch {
|
|
5739
|
+
if ("Err" in result) {
|
|
5740
|
+
throw mapDepositAccountErrorToLiquidiumError(result.Err);
|
|
5726
5741
|
}
|
|
5727
|
-
return
|
|
5742
|
+
return result.Ok;
|
|
5728
5743
|
}
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
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
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
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
|
|
5786
|
-
await
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
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
|
|
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
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
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
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
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
|
-
|
|
5977
|
-
|
|
5871
|
+
...details,
|
|
5872
|
+
status: createLiquidiumStatus({
|
|
5873
|
+
operation: mapOutflowTypeToStatusOperation(expectedOutflowType),
|
|
5874
|
+
state: details.txid ? "confirming" : "processing"
|
|
5875
|
+
})
|
|
5978
5876
|
};
|
|
5979
5877
|
}
|
|
5980
|
-
function
|
|
5981
|
-
|
|
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
|
|
5964
|
+
* Lists SDK-supported pools with their current rates.
|
|
6066
5965
|
*
|
|
6067
|
-
*
|
|
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
|
|
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,
|
|
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
|