@liquidium/client 0.4.0 → 0.5.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6,60 +6,12 @@ var agent = require('@icp-sdk/core/agent');
6
6
  var principal = require('@icp-sdk/core/principal');
7
7
  var base = require('@scure/base');
8
8
  var candid = require('@icp-sdk/core/candid');
9
+ var icp = require('@icp-sdk/canisters/ledger/icp');
9
10
  var icrc = require('@icp-sdk/canisters/ledger/icrc');
10
11
  var bitcoinAddressValidation = require('bitcoin-address-validation');
11
12
 
12
13
  // src/client.ts
13
14
 
14
- // src/core/types.ts
15
- var Environment = {
16
- mainnet: "mainnet"
17
- };
18
- var Asset = {
19
- BTC: "BTC",
20
- SOL: "SOL",
21
- USDC: "USDC",
22
- USDT: "USDT"
23
- };
24
- var Chain = {
25
- BTC: "BTC",
26
- ETH: "ETH"
27
- };
28
- var SupplyAction = {
29
- deposit: "deposit",
30
- repayment: "repayment"
31
- };
32
- var OutflowType = {
33
- borrow: "borrow",
34
- feeClaim: "feeClaim",
35
- withdrawal: "withdrawal"
36
- };
37
-
38
- // src/core/config.ts
39
- var MAINNET_HOST = "https://icp-api.io";
40
- var DEFAULT_API_BASE_URL = "https://app.liquidium.fi/api/sdk";
41
- var DEFAULT_ENVIRONMENT = Environment.mainnet;
42
- var MAINNET_CANISTER_IDS = {
43
- lending: "hyk4r-jqaaa-aaaar-qb4ca-cai",
44
- btcPool: "hkmli-faaaa-aaaar-qb4ba-cai",
45
- ercPool: "hnnn4-iyaaa-aaaar-qb4bq-cai",
46
- ethDeposit: "z5jz7-nyaaa-aaaar-qb6pq-cai",
47
- instantLoans: "u5rm3-niaaa-aaaar-qb7eq-cai"
48
- };
49
- var CANISTER_IDS_BY_ENVIRONMENT = {
50
- mainnet: MAINNET_CANISTER_IDS
51
- };
52
- function resolveHost(override) {
53
- return override ?? MAINNET_HOST;
54
- }
55
- function resolveCanisterIds(environment = DEFAULT_ENVIRONMENT, overrides) {
56
- return { ...CANISTER_IDS_BY_ENVIRONMENT[environment], ...overrides };
57
- }
58
- var DEFAULT_TIMEOUT_MS = 3e4;
59
- var CK_CANISTER_IDS = {
60
- btcMinter: "mqygn-kiaaa-aaaar-qaadq-cai",
61
- btcLedger: "mxzaz-hqaaa-aaaar-qaada-cai"};
62
-
63
15
  // src/core/errors.ts
64
16
  var LiquidiumErrorCode = {
65
17
  // Protocol errors
@@ -130,6 +82,127 @@ var LiquidiumError = class extends Error {
130
82
  }
131
83
  };
132
84
 
85
+ // src/core/types.ts
86
+ var Environment = {
87
+ mainnet: "mainnet"
88
+ };
89
+ var Asset = {
90
+ BTC: "BTC",
91
+ ICP: "ICP",
92
+ USDC: "USDC",
93
+ USDT: "USDT"
94
+ };
95
+ var Chain = {
96
+ BTC: "BTC",
97
+ ETH: "ETH",
98
+ ICP: "ICP"
99
+ };
100
+ function isAssetIdentifier(identifier) {
101
+ switch (identifier.chain) {
102
+ case Chain.BTC:
103
+ return identifier.asset === Asset.BTC;
104
+ case Chain.ETH:
105
+ return identifier.asset === Asset.USDC || identifier.asset === Asset.USDT;
106
+ case Chain.ICP:
107
+ return identifier.asset === Asset.BTC || identifier.asset === Asset.ICP || identifier.asset === Asset.USDC || identifier.asset === Asset.USDT;
108
+ default:
109
+ return false;
110
+ }
111
+ }
112
+ var SupplyAction = {
113
+ deposit: "deposit",
114
+ repayment: "repayment"
115
+ };
116
+ var OutflowType = {
117
+ borrow: "borrow",
118
+ feeClaim: "feeClaim",
119
+ withdrawal: "withdrawal"
120
+ };
121
+
122
+ // src/core/config.ts
123
+ var MAINNET_HOST = "https://icp-api.io";
124
+ var DEFAULT_API_BASE_URL = "https://app.liquidium.fi/api/sdk";
125
+ var DEFAULT_ENVIRONMENT = Environment.mainnet;
126
+ var MAINNET_CANISTER_IDS = {
127
+ lending: "hyk4r-jqaaa-aaaar-qb4ca-cai",
128
+ pools: {
129
+ btc: "hkmli-faaaa-aaaar-qb4ba-cai",
130
+ usdt: "hnnn4-iyaaa-aaaar-qb4bq-cai",
131
+ usdc: "6sna2-oiaaa-aaaar-qb66q-cai",
132
+ icp: "r2pk3-4yaaa-aaaar-qb7zq-cai"
133
+ },
134
+ ethDeposit: "z5jz7-nyaaa-aaaar-qb6pq-cai",
135
+ instantLoans: "u5rm3-niaaa-aaaar-qb7eq-cai"
136
+ };
137
+ var CANISTER_IDS_BY_ENVIRONMENT = {
138
+ mainnet: MAINNET_CANISTER_IDS
139
+ };
140
+ var SUPPORTED_CANISTER_ID_OVERRIDE_KEYS = /* @__PURE__ */ new Set([
141
+ "lending",
142
+ "pools",
143
+ "ethDeposit",
144
+ "instantLoans"
145
+ ]);
146
+ var SUPPORTED_POOL_CANISTER_ID_OVERRIDE_KEYS = /* @__PURE__ */ new Set([
147
+ "btc",
148
+ "usdt",
149
+ "usdc",
150
+ "icp"
151
+ ]);
152
+ function resolveHost(override) {
153
+ return override ?? MAINNET_HOST;
154
+ }
155
+ function resolveCanisterIds(environment = DEFAULT_ENVIRONMENT, overrides) {
156
+ const defaults = CANISTER_IDS_BY_ENVIRONMENT[environment];
157
+ validateCanisterIdOverrides(overrides);
158
+ const pools = {
159
+ ...defaults.pools,
160
+ ...overrides?.pools
161
+ };
162
+ return {
163
+ lending: overrides?.lending ?? defaults.lending,
164
+ pools,
165
+ ethDeposit: overrides?.ethDeposit ?? defaults.ethDeposit,
166
+ instantLoans: overrides?.instantLoans ?? defaults.instantLoans
167
+ };
168
+ }
169
+ function validateCanisterIdOverrides(overrides) {
170
+ if (!overrides) {
171
+ return;
172
+ }
173
+ for (const key of Object.keys(overrides)) {
174
+ if (!SUPPORTED_CANISTER_ID_OVERRIDE_KEYS.has(key)) {
175
+ throw new LiquidiumError(
176
+ LiquidiumErrorCode.VALIDATION_ERROR,
177
+ `Unsupported canisterIds override: ${key}`
178
+ );
179
+ }
180
+ }
181
+ if (!overrides.pools) {
182
+ return;
183
+ }
184
+ for (const key of Object.keys(overrides.pools)) {
185
+ if (!SUPPORTED_POOL_CANISTER_ID_OVERRIDE_KEYS.has(key)) {
186
+ throw new LiquidiumError(
187
+ LiquidiumErrorCode.VALIDATION_ERROR,
188
+ `Unsupported canisterIds.pools override: ${key}`
189
+ );
190
+ }
191
+ }
192
+ }
193
+ var DEFAULT_TIMEOUT_MS = 3e4;
194
+ var CK_CANISTER_IDS = {
195
+ BTC: {
196
+ minter: "mqygn-kiaaa-aaaar-qaadq-cai",
197
+ ledger: "mxzaz-hqaaa-aaaar-qaada-cai"},
198
+ USDT: {
199
+ ledger: "cngnf-vqaaa-aaaar-qag4q-cai"},
200
+ USDC: {
201
+ ledger: "xevnm-gaaaa-aaaar-qafnq-cai"},
202
+ ICP: {
203
+ ledger: "ryjl3-tyaaa-aaaaa-aaaba-cai"}
204
+ };
205
+
133
206
  // src/core/transports/api-client.ts
134
207
  var DOM_EXCEPTION_ABORT_ERROR_NAME = "AbortError";
135
208
  var HTTP_HEADER_CONTENT_TYPE = "content-type";
@@ -983,14 +1056,8 @@ function getVariantKey(variant) {
983
1056
  }
984
1057
 
985
1058
  // src/core/wallet-actions.ts
986
- var TransferMode = {
987
- ck: "ck",
988
- native: "native"
989
- };
990
1059
  var WalletExecutionKind = {
991
- sendEthTransaction: "send-eth-transaction",
992
- signMessage: "sign-message",
993
- signPsbt: "sign-psbt"
1060
+ signMessage: "sign-message"
994
1061
  };
995
1062
  var WalletActionKind = {
996
1063
  createAccount: "create-account",
@@ -1019,8 +1086,7 @@ function executeWith(options) {
1019
1086
  chain: options.chain,
1020
1087
  message: action.message,
1021
1088
  account: options.account ?? action.account,
1022
- actionType: action.actionType,
1023
- transferMode: action.transferMode
1089
+ actionType: action.actionType
1024
1090
  });
1025
1091
  return action.submit({
1026
1092
  signature,
@@ -1028,38 +1094,6 @@ function executeWith(options) {
1028
1094
  account: options.account ?? action.account
1029
1095
  });
1030
1096
  }
1031
- case WalletExecutionKind.signPsbt: {
1032
- if (!options.walletAdapter.signPsbt) {
1033
- throw new LiquidiumError(
1034
- LiquidiumErrorCode.VALIDATION_ERROR,
1035
- "Wallet adapter does not support PSBT signing"
1036
- );
1037
- }
1038
- const signedPsbtBase64 = await options.walletAdapter.signPsbt({
1039
- chain: Chain.BTC,
1040
- psbtBase64: action.psbtBase64,
1041
- account: options.account ?? action.account,
1042
- actionType: action.actionType,
1043
- transferMode: action.transferMode
1044
- });
1045
- return action.submit({ signedPsbtBase64 });
1046
- }
1047
- case WalletExecutionKind.sendEthTransaction: {
1048
- if (!options.walletAdapter.sendEthTransaction) {
1049
- throw new LiquidiumError(
1050
- LiquidiumErrorCode.VALIDATION_ERROR,
1051
- "Wallet adapter does not support ETH transaction sending"
1052
- );
1053
- }
1054
- const txHash = await options.walletAdapter.sendEthTransaction({
1055
- chain: Chain.ETH,
1056
- transaction: action.transaction,
1057
- account: options.account ?? action.account,
1058
- actionType: action.actionType,
1059
- transferMode: action.transferMode
1060
- });
1061
- return action.submit({ txHash });
1062
- }
1063
1097
  default:
1064
1098
  throw new LiquidiumError(
1065
1099
  LiquidiumErrorCode.VALIDATION_ERROR,
@@ -1275,7 +1309,6 @@ var AccountsModule = class {
1275
1309
  kind: WalletActionKind.createAccount,
1276
1310
  executionKind: WalletExecutionKind.signMessage,
1277
1311
  actionType: WalletActionKind.createAccount,
1278
- transferMode: TransferMode.native,
1279
1312
  account: normalizedAccount,
1280
1313
  message: createInitializeAccountMessage(expiryTimestamp, nonce),
1281
1314
  data: {
@@ -1366,8 +1399,8 @@ function mapCanisterWalletToWallet(canisterWallet) {
1366
1399
  );
1367
1400
  }
1368
1401
  }
1369
- var KNOWN_ASSET_TAGS = ["BTC", "USDC", "USDT"];
1370
- var KNOWN_CHAIN_TAGS = ["BTC", "ETH"];
1402
+ var KNOWN_ASSET_TAGS = ["BTC", "ICP", "USDC", "USDT"];
1403
+ var KNOWN_CHAIN_TAGS = ["BTC", "ETH", "ICP"];
1371
1404
  function extractVariantTag(variant, knownTags) {
1372
1405
  const [key] = Object.keys(variant);
1373
1406
  if (!key) {
@@ -1946,17 +1979,14 @@ function mapInstantLoanLookupError(error) {
1946
1979
  }
1947
1980
  function mapActivity(wire) {
1948
1981
  const amount = parseBigInt(wire.amount, "activity amount");
1949
- const activityBase = {
1950
- id: wire.id,
1951
- poolId: wire.poolId,
1952
- asset: wire.asset,
1953
- chain: wire.chain,
1954
- amount,
1955
- timestampMs: wire.timestampMs
1956
- };
1957
1982
  if (isInflowOperation(wire.status.operation)) {
1958
1983
  const activity = {
1959
- ...activityBase,
1984
+ id: wire.id,
1985
+ poolId: wire.poolId,
1986
+ asset: wire.asset,
1987
+ chain: wire.chain,
1988
+ amount,
1989
+ timestampMs: wire.timestampMs,
1960
1990
  status: wire.status
1961
1991
  };
1962
1992
  if (wire.txids) {
@@ -1969,7 +1999,12 @@ function mapActivity(wire) {
1969
1999
  }
1970
2000
  if (isOutflowOperation(wire.status.operation)) {
1971
2001
  const activity = {
1972
- ...activityBase,
2002
+ id: wire.id,
2003
+ poolId: wire.poolId,
2004
+ asset: wire.asset,
2005
+ chain: wire.chain,
2006
+ amount,
2007
+ timestampMs: wire.timestampMs,
1973
2008
  status: wire.status
1974
2009
  };
1975
2010
  if (wire.txids) {
@@ -2145,6 +2180,72 @@ function validateHistoryStateFilter(state) {
2145
2180
  );
2146
2181
  }
2147
2182
  }
2183
+ var LiquidiumAccountType = {
2184
+ ChainAddress: "ChainAddress",
2185
+ IcPrincipal: "IcPrincipal",
2186
+ IcpAccountIdentifier: "IcpAccountIdentifier",
2187
+ IcrcAccount: "IcrcAccount"
2188
+ };
2189
+ function createIcrcAccount(params) {
2190
+ return {
2191
+ type: "IcrcAccount",
2192
+ owner: params.owner.toText(),
2193
+ subaccount: params.subaccount,
2194
+ address: icrc.encodeIcrcAccount({
2195
+ owner: params.owner,
2196
+ subaccount: params.subaccount
2197
+ })
2198
+ };
2199
+ }
2200
+ function mapCanisterAccountToLiquidiumAccount(account) {
2201
+ if ("Native" in account) {
2202
+ return { type: "IcPrincipal", address: account.Native.toText() };
2203
+ }
2204
+ if ("AccountIdentifier" in account) {
2205
+ return {
2206
+ type: "IcpAccountIdentifier",
2207
+ address: account.AccountIdentifier
2208
+ };
2209
+ }
2210
+ if ("Icrc" in account) {
2211
+ const subaccount = normalizeOptionalSubaccount(account.Icrc.subaccount[0]);
2212
+ return {
2213
+ type: "IcrcAccount",
2214
+ owner: account.Icrc.owner.toText(),
2215
+ subaccount,
2216
+ address: icrc.encodeIcrcAccount({
2217
+ owner: account.Icrc.owner,
2218
+ subaccount
2219
+ })
2220
+ };
2221
+ }
2222
+ return { type: "ChainAddress", address: account.External };
2223
+ }
2224
+ function decodeIcrcAccountAddress(address) {
2225
+ const decoded = icrc.decodeIcrcAccount(address);
2226
+ const owner = principal.Principal.fromText(decoded.owner.toText());
2227
+ const subaccount = decoded.subaccount ? Uint8Array.from(decoded.subaccount) : void 0;
2228
+ return {
2229
+ owner,
2230
+ subaccount,
2231
+ account: createIcrcAccount({ owner, subaccount })
2232
+ };
2233
+ }
2234
+ function encodeIcpAccountIdentifier(params) {
2235
+ return icp.AccountIdentifier.fromPrincipal({
2236
+ principal: params.owner,
2237
+ subAccount: params.subaccount ? icp.SubAccount.fromBytes(params.subaccount) : void 0
2238
+ }).toHex();
2239
+ }
2240
+ function normalizeIcpAccountIdentifier(address) {
2241
+ return icp.AccountIdentifier.fromHex(address).toHex();
2242
+ }
2243
+ function normalizeOptionalSubaccount(subaccount) {
2244
+ if (!subaccount) {
2245
+ return void 0;
2246
+ }
2247
+ return subaccount instanceof Uint8Array ? subaccount : Uint8Array.from(subaccount);
2248
+ }
2148
2249
 
2149
2250
  // src/core/status.ts
2150
2251
  function createLiquidiumStatus(params) {
@@ -2194,6 +2295,7 @@ function throwInvalidApiResponseField(field) {
2194
2295
  // src/core/utils/asset-decimals.ts
2195
2296
  var ASSET_NATIVE_DECIMALS = {
2196
2297
  BTC: 8n,
2298
+ ICP: 8n,
2197
2299
  USDC: 6n,
2198
2300
  USDT: 6n,
2199
2301
  SOL: 9n
@@ -2589,7 +2691,7 @@ var idlFactory2 = ({ IDL }) => {
2589
2691
 
2590
2692
  // src/core/canisters/ckbtc/minter.ts
2591
2693
  function createCkBtcMinterActor(canisterContext) {
2592
- const canisterId = CK_CANISTER_IDS.btcMinter;
2694
+ const canisterId = CK_CANISTER_IDS.BTC.minter;
2593
2695
  return agent.Actor.createActor(idlFactory2, {
2594
2696
  agent: canisterContext.agent,
2595
2697
  canisterId
@@ -3040,55 +3142,43 @@ var EvmSupplyApprovalStrategy = {
3040
3142
  };
3041
3143
 
3042
3144
  // src/modules/lending/_internal/supply-targets.ts
3043
- async function resolveSupplyTarget(canisterContext, request) {
3044
- const selectedPool = await getPoolById(canisterContext, request.poolId);
3045
- const asset = selectedPool.asset;
3046
- const chain = selectedPool.chain;
3145
+ async function resolveSupplyTargetForPool(canisterContext, request, selectedPool) {
3146
+ const identifier = resolveSupplyAssetIdentifier({
3147
+ asset: selectedPool.asset,
3148
+ poolChain: selectedPool.chain,
3149
+ transferChain: request.chain ?? selectedPool.chain
3150
+ });
3047
3151
  const mechanism = resolveSupplyMechanism({
3048
- asset,
3049
- chain,
3050
- requestedMechanism: request.mechanism
3152
+ identifier,
3153
+ mechanism: request.mechanism
3051
3154
  });
3052
- switch (mechanism) {
3053
- case SupplyPlanType.transfer:
3054
- return await getNativeAddressSupplyTarget(
3055
- canisterContext,
3056
- request.profileId,
3057
- {
3058
- poolId: request.poolId,
3059
- asset,
3060
- chain,
3061
- action: request.action
3062
- }
3063
- );
3064
- case SupplyPlanType.contractInteraction:
3065
- return getIcrcAccountSupplyTarget(request.profileId, {
3066
- poolId: request.poolId,
3067
- asset,
3068
- chain,
3069
- action: request.action
3070
- });
3155
+ const targetRequest = {
3156
+ ...identifier,
3157
+ poolId: request.poolId,
3158
+ action: request.action
3159
+ };
3160
+ if (mechanism === SupplyPlanType.contractInteraction) {
3161
+ return getIcrcAccountSupplyTarget(request.profileId, targetRequest);
3071
3162
  }
3163
+ if (identifier.chain === Chain.ICP) {
3164
+ return identifier.asset === Asset.ICP ? getIcpLedgerSupplyTarget(request.profileId, targetRequest) : getIcrcAccountSupplyTarget(request.profileId, targetRequest);
3165
+ }
3166
+ return await getChainAddressSupplyTarget(
3167
+ canisterContext,
3168
+ request.profileId,
3169
+ targetRequest
3170
+ );
3072
3171
  }
3073
3172
  function resolveSupplyMechanism(params) {
3074
- if (params.asset === Asset.BTC && params.chain === Chain.BTC) {
3075
- if (params.requestedMechanism === SupplyPlanType.contractInteraction) {
3076
- throw new LiquidiumError(
3077
- LiquidiumErrorCode.VALIDATION_ERROR,
3078
- "Contract-interaction supply is not supported for BTC on BTC"
3079
- );
3080
- }
3173
+ if (params.mechanism !== SupplyPlanType.contractInteraction) {
3081
3174
  return SupplyPlanType.transfer;
3082
3175
  }
3083
- if (isEthStablecoin(params.asset, params.chain)) {
3084
- if (params.requestedMechanism) {
3085
- return params.requestedMechanism;
3086
- }
3087
- return SupplyPlanType.transfer;
3176
+ if (params.identifier.chain === Chain.ETH && (params.identifier.asset === Asset.USDC || params.identifier.asset === Asset.USDT)) {
3177
+ return SupplyPlanType.contractInteraction;
3088
3178
  }
3089
3179
  throw new LiquidiumError(
3090
3180
  LiquidiumErrorCode.VALIDATION_ERROR,
3091
- `No supply mechanism is configured for ${params.asset} on ${params.chain}`
3181
+ `Contract-interaction supply is not supported for ${params.identifier.asset} on ${params.identifier.chain}`
3092
3182
  );
3093
3183
  }
3094
3184
  function isEthStablecoin(asset, chain) {
@@ -3160,9 +3250,33 @@ async function getPoolById(canisterContext, poolId) {
3160
3250
  }
3161
3251
  return decodedPool;
3162
3252
  }
3163
- async function getNativeAddressSupplyTarget(canisterContext, profileId, request) {
3164
- assertSupportsNativeAddressInflowTarget(request.asset, request.chain);
3165
- if (isEthStablecoin(request.asset, request.chain)) {
3253
+ function resolveSupplyAssetIdentifier(params) {
3254
+ if (params.asset === Asset.BTC && params.poolChain === Chain.BTC) {
3255
+ if (params.transferChain === Chain.BTC) {
3256
+ return { asset: Asset.BTC, chain: Chain.BTC };
3257
+ }
3258
+ if (params.transferChain === Chain.ICP) {
3259
+ return { asset: Asset.BTC, chain: Chain.ICP };
3260
+ }
3261
+ }
3262
+ if ((params.asset === Asset.USDC || params.asset === Asset.USDT) && params.poolChain === Chain.ETH) {
3263
+ if (params.transferChain === Chain.ETH) {
3264
+ return { asset: params.asset, chain: Chain.ETH };
3265
+ }
3266
+ if (params.transferChain === Chain.ICP) {
3267
+ return { asset: params.asset, chain: Chain.ICP };
3268
+ }
3269
+ }
3270
+ if (params.asset === Asset.ICP && params.poolChain === Chain.ICP && params.transferChain === Chain.ICP) {
3271
+ return { asset: Asset.ICP, chain: Chain.ICP };
3272
+ }
3273
+ throw new LiquidiumError(
3274
+ LiquidiumErrorCode.VALIDATION_ERROR,
3275
+ `Supply is not supported for ${params.asset} pool on ${params.poolChain} using ${params.transferChain}`
3276
+ );
3277
+ }
3278
+ async function getChainAddressSupplyTarget(canisterContext, profileId, request) {
3279
+ if (request.chain === Chain.ETH) {
3166
3280
  const tokenAddress = getEthStablecoinContractAddress(request.asset);
3167
3281
  const subaccount2 = encodeInflowSubaccount({
3168
3282
  action: request.action,
@@ -3181,7 +3295,6 @@ async function getNativeAddressSupplyTarget(canisterContext, profileId, request)
3181
3295
  throw mapDepositAccountErrorToLiquidiumError(result.Err);
3182
3296
  }
3183
3297
  return {
3184
- type: "nativeAddress",
3185
3298
  poolId: request.poolId,
3186
3299
  asset: request.asset,
3187
3300
  chain: request.chain,
@@ -3189,11 +3302,11 @@ async function getNativeAddressSupplyTarget(canisterContext, profileId, request)
3189
3302
  address: result.Ok
3190
3303
  };
3191
3304
  }
3192
- const configuredBtcPoolId = canisterContext.canisterIds.btcPool;
3305
+ const configuredBtcPoolId = canisterContext.canisterIds.pools.btc;
3193
3306
  if (request.poolId !== configuredBtcPoolId) {
3194
3307
  throw new LiquidiumError(
3195
3308
  LiquidiumErrorCode.VALIDATION_ERROR,
3196
- `Native BTC inflow targets require the configured BTC pool ${configuredBtcPoolId}, received ${request.poolId}`
3309
+ `Chain-address BTC inflow targets require the configured BTC pool ${configuredBtcPoolId}, received ${request.poolId}`
3197
3310
  );
3198
3311
  }
3199
3312
  const subaccount = encodeInflowSubaccount({
@@ -3206,66 +3319,51 @@ async function getNativeAddressSupplyTarget(canisterContext, profileId, request)
3206
3319
  subaccount: [subaccount]
3207
3320
  }
3208
3321
  );
3209
- return {
3210
- type: "nativeAddress",
3211
- poolId: request.poolId,
3212
- asset: request.asset,
3213
- chain: request.chain,
3214
- action: request.action,
3215
- address
3216
- };
3322
+ return { ...request, address };
3217
3323
  }
3218
3324
  function getIcrcAccountSupplyTarget(profileId, request) {
3219
- assertSupportsIcrcAccountInflowTarget(request.asset);
3325
+ const account = createInflowIcrcAccount(profileId, request);
3326
+ return { ...request, address: account.address };
3327
+ }
3328
+ function getIcpLedgerSupplyTarget(profileId, request) {
3220
3329
  const poolPrincipal = principal.Principal.fromText(request.poolId);
3221
3330
  const subaccount = encodeInflowSubaccount({
3222
3331
  action: request.action,
3223
3332
  principal: principal.Principal.fromText(profileId)
3224
3333
  });
3334
+ const account = createIcrcAccount({
3335
+ owner: poolPrincipal,
3336
+ subaccount
3337
+ });
3225
3338
  return {
3226
- type: "icrcAccount",
3227
3339
  poolId: request.poolId,
3228
- asset: request.asset,
3229
- chain: request.chain,
3340
+ asset: Asset.ICP,
3341
+ chain: Chain.ICP,
3230
3342
  action: request.action,
3231
- owner: poolPrincipal.toText(),
3232
- subaccount,
3233
- account: icrc.encodeIcrcAccount({
3343
+ address: account.address,
3344
+ icpAccountIdentifier: encodeIcpAccountIdentifier({
3234
3345
  owner: poolPrincipal,
3235
3346
  subaccount
3236
3347
  })
3237
3348
  };
3238
3349
  }
3239
- function assertSupportsNativeAddressInflowTarget(asset, chain) {
3240
- if (asset === Asset.BTC && chain === Chain.BTC) {
3241
- return;
3242
- }
3243
- if (isEthStablecoin(asset, chain)) {
3244
- return;
3245
- }
3246
- throw new LiquidiumError(
3247
- LiquidiumErrorCode.VALIDATION_ERROR,
3248
- `Native address inflow targets are not supported for ${asset} on ${chain}`
3249
- );
3250
- }
3251
- function assertSupportsIcrcAccountInflowTarget(asset) {
3252
- if (asset === Asset.BTC || asset === Asset.USDT || asset === Asset.USDC) {
3253
- return;
3254
- }
3255
- throw new LiquidiumError(
3256
- LiquidiumErrorCode.VALIDATION_ERROR,
3257
- `ICRC account inflow targets are not supported for ${asset}`
3258
- );
3350
+ function createInflowIcrcAccount(profileId, request) {
3351
+ return createIcrcAccount({
3352
+ owner: principal.Principal.fromText(request.poolId),
3353
+ subaccount: encodeInflowSubaccount({
3354
+ action: request.action,
3355
+ principal: principal.Principal.fromText(profileId)
3356
+ })
3357
+ });
3259
3358
  }
3260
3359
 
3261
3360
  // src/core/borrow-minimums.ts
3361
+ var MINIMUM_BTC_BORROW_AMOUNT_SATS = 5100n;
3362
+ var MINIMUM_STABLECOIN_BORROW_AMOUNT_BASE_UNITS = 1000000n;
3262
3363
  var MIN_BORROW_AMOUNTS_BY_ASSET = {
3263
- BTC: 5100n,
3264
- // 5,100 sats = 0.000051 BTC
3265
- USDC: 1000000n,
3266
- // 1 USDC
3267
- USDT: 1000000n
3268
- // 1 USDT
3364
+ [Asset.BTC]: MINIMUM_BTC_BORROW_AMOUNT_SATS,
3365
+ [Asset.USDC]: MINIMUM_STABLECOIN_BORROW_AMOUNT_BASE_UNITS,
3366
+ [Asset.USDT]: MINIMUM_STABLECOIN_BORROW_AMOUNT_BASE_UNITS
3269
3367
  };
3270
3368
  function getMinimumBorrowAmount(asset) {
3271
3369
  if (!isMinimumBorrowAsset(asset)) {
@@ -3730,25 +3828,20 @@ function isEthStablecoin2(asset) {
3730
3828
  }
3731
3829
 
3732
3830
  // src/modules/instant-loans/_internal/address-validation.ts
3733
- function validateInstantLoanBorrowDestination(address, asset) {
3831
+ function validateInstantLoanBorrowDestination(address, asset, chain) {
3734
3832
  return normalizeExternalAddress({
3735
3833
  address,
3736
3834
  asset,
3737
- chain: getChainForInstantLoanAsset(asset)
3835
+ chain
3738
3836
  });
3739
3837
  }
3740
- function validateInstantLoanRefundDestination(address, asset) {
3838
+ function validateInstantLoanRefundDestination(address, asset, chain) {
3741
3839
  return normalizeExternalAddress({
3742
3840
  address,
3743
3841
  asset,
3744
- chain: getChainForInstantLoanAsset(asset)
3842
+ chain
3745
3843
  });
3746
3844
  }
3747
- function getChainForInstantLoanAsset(asset) {
3748
- if (asset === Asset.BTC) return Chain.BTC;
3749
- if (asset === Asset.USDC || asset === Asset.USDT) return Chain.ETH;
3750
- return asset;
3751
- }
3752
3845
 
3753
3846
  // src/modules/instant-loans/instant-loans.ts
3754
3847
  var REPAYMENT_BUFFER_SECONDS = 86400n;
@@ -3761,10 +3854,32 @@ var INSTANT_LOAN_FIND_QUERY_MAX_LENGTH = 256;
3761
3854
  var INSTANT_LOAN_WIRE_CONTEXT = "instant loan";
3762
3855
  var INSTANT_LOAN_ASSETS = [
3763
3856
  Asset.BTC,
3764
- Asset.SOL,
3857
+ Asset.ICP,
3765
3858
  Asset.USDC,
3766
3859
  Asset.USDT
3767
3860
  ];
3861
+ var INSTANT_LOAN_CHAINS = [
3862
+ Chain.BTC,
3863
+ Chain.ETH,
3864
+ Chain.ICP
3865
+ ];
3866
+ var ICP_ACCOUNT_IDENTIFIER_HEX_LENGTH = 64;
3867
+ var InstantLoanCreatedError = class extends Error {
3868
+ code = "INSTANT_LOAN_HYDRATION_FAILED";
3869
+ loanId;
3870
+ ref;
3871
+ cause;
3872
+ constructor(loanId, cause) {
3873
+ const ref = publicIdFromInt(loanId);
3874
+ super(
3875
+ `Instant loan ${ref} was created, but its enriched state could not be loaded`
3876
+ );
3877
+ this.name = "InstantLoanCreatedError";
3878
+ this.loanId = loanId;
3879
+ this.ref = ref;
3880
+ this.cause = cause;
3881
+ }
3882
+ };
3768
3883
  var InstantLoansModule = class {
3769
3884
  constructor(canisterContext, apiClient, activities, lending, positions) {
3770
3885
  this.canisterContext = canisterContext;
@@ -3787,37 +3902,39 @@ var InstantLoansModule = class {
3787
3902
  * selected pool decimals, and call `client.quote.calculateLtv(...)` before
3788
3903
  * creation to block invalid LTV input.
3789
3904
  *
3790
- * `borrowDestination` receives the borrowed asset after the loan starts.
3791
- * `refundDestination` receives collateral refunds or withdrawals. Use
3905
+ * `borrow.destination` receives the borrowed asset after the loan starts.
3906
+ * `refund.destination` receives collateral refunds or withdrawals. Use
3792
3907
  * `depositWindowSeconds` for the user-facing collateral deposit timeout; the
3793
3908
  * SDK maps it to the canister's internal `ltv_timer_s` field.
3794
3909
  *
3795
- * @param request - Pool ids, assets, base-unit amounts, LTV limit, timeout, and destinations.
3910
+ * @param request - Collateral, borrow, refund, LTV limit, timeout, and inflow options.
3796
3911
  * @returns Hydrated loan state plus generated initial-deposit and repayment quote targets.
3797
3912
  */
3798
3913
  async create(request) {
3799
3914
  validateCreateRequest(request);
3800
- const borrowDestination = {
3801
- External: validateInstantLoanBorrowDestination(
3802
- addressFromAccountInput(request.borrowDestination),
3803
- request.borrowAsset
3804
- )
3805
- };
3806
- const refundDestination = {
3807
- External: validateInstantLoanRefundDestination(
3808
- addressFromAccountInput(request.refundDestination),
3809
- request.collateralAsset
3810
- )
3811
- };
3915
+ const borrowDestination = resolveInstantLoanDestination({
3916
+ destination: request.borrow.destination,
3917
+ asset: request.borrow.asset,
3918
+ role: "borrow",
3919
+ chain: request.borrow.chain,
3920
+ normalizeExternalDestination: validateInstantLoanBorrowDestination
3921
+ });
3922
+ const refundDestination = resolveInstantLoanDestination({
3923
+ destination: request.refund.destination,
3924
+ asset: request.collateral.asset,
3925
+ role: "refund",
3926
+ chain: request.refund.chain,
3927
+ normalizeExternalDestination: validateInstantLoanRefundDestination
3928
+ });
3812
3929
  const apiClient = this.requireApi("Instant loan creation");
3813
3930
  await this.validateInstantLoanLtvPolicy(request);
3814
3931
  const response = await apiClient.post(SdkApiPath.instantLoans, {
3815
- collateralPoolId: request.collateralPoolId,
3816
- borrowPoolId: request.borrowPoolId,
3817
- collateralAsset: request.collateralAsset,
3818
- borrowAsset: request.borrowAsset,
3819
- collateralAmount: request.collateralAmount.toString(),
3820
- borrowAmount: request.borrowAmount.toString(),
3932
+ collateralPoolId: request.collateral.poolId,
3933
+ borrowPoolId: request.borrow.poolId,
3934
+ collateralAsset: request.collateral.asset,
3935
+ borrowAsset: request.borrow.asset,
3936
+ collateralAmount: request.collateral.amount.toString(),
3937
+ borrowAmount: request.borrow.amount.toString(),
3821
3938
  ltvMaxBps: request.ltvMaxBps.toString(),
3822
3939
  depositWindowSeconds: request.depositWindowSeconds.toString(),
3823
3940
  borrowDestination,
@@ -3827,15 +3944,21 @@ var InstantLoansModule = class {
3827
3944
  context: INSTANT_LOAN_WIRE_CONTEXT,
3828
3945
  label: "loan ID"
3829
3946
  });
3830
- const collateralAmount = parseUnsignedApiBigint(
3831
- response.loan.collateral.amountHint,
3832
- {
3833
- context: INSTANT_LOAN_WIRE_CONTEXT,
3834
- label: "collateral amount"
3835
- }
3836
- );
3837
- const record = await this.getLoanRecord(loanId);
3838
- return await this.mapLoanRecord(record, collateralAmount);
3947
+ try {
3948
+ const collateralAmount = parseUnsignedApiBigint(
3949
+ response.loan.collateral.amountHint,
3950
+ {
3951
+ context: INSTANT_LOAN_WIRE_CONTEXT,
3952
+ label: "collateral amount"
3953
+ }
3954
+ );
3955
+ const record = await this.getLoanRecord(loanId);
3956
+ return await this.mapLoanRecord(record, collateralAmount, {
3957
+ borrowChain: request.borrow.chain
3958
+ });
3959
+ } catch (cause) {
3960
+ throw new InstantLoanCreatedError(loanId, cause);
3961
+ }
3839
3962
  }
3840
3963
  /**
3841
3964
  * Resolves canonical canister state by loan id or short reference.
@@ -4001,7 +4124,16 @@ var InstantLoansModule = class {
4001
4124
  throw mapCanisterCallErrorToLiquidiumError("get_loan", error);
4002
4125
  }
4003
4126
  }
4004
- async mapLoanRecord(record, collateralAmount) {
4127
+ async mapLoanRecord(record, collateralAmount, chainOptions = {}) {
4128
+ const borrowDestination = accountFromCanister(record.borrow_destination);
4129
+ const collateralAsset = parseInstantLoanAsset(
4130
+ record.lend_asset,
4131
+ "collateral asset"
4132
+ );
4133
+ const borrowAsset = parseInstantLoanAsset(
4134
+ record.borrow_asset,
4135
+ "borrow asset"
4136
+ );
4005
4137
  return await this.hydrateLoan({
4006
4138
  loanId: record.id,
4007
4139
  profileId: record.lending_profile.toText(),
@@ -4010,17 +4142,18 @@ var InstantLoansModule = class {
4010
4142
  collateralPoolId: record.lend_pool_id.toText(),
4011
4143
  collateralAmount,
4012
4144
  borrowPoolId: record.borrow_pool_id.toText(),
4013
- collateralAsset: record.lend_asset,
4014
- borrowAsset: record.borrow_asset,
4145
+ collateralAsset,
4146
+ borrowAsset,
4015
4147
  borrowAmount: record.borrow_amount,
4016
- borrowDestination: accountFromCanister(record.borrow_destination),
4148
+ borrowDestination,
4017
4149
  refundDestination: accountFromCanister(record.refund_destination),
4018
4150
  started: record.started,
4019
4151
  depositDetectedTimestamp: record.deposit_detected_ts[0] ?? null,
4020
4152
  expiryTimestamp: record.expires_at[0] ?? deriveDepositExpiryTimestamp({
4021
4153
  depositDetectedTimestamp: record.deposit_detected_ts[0] ?? null,
4022
4154
  depositWindowSeconds: record.ltv_timer_s
4023
- })
4155
+ }),
4156
+ borrowChain: chainOptions.borrowChain ?? inferInstantLoanDeliveryChain(borrowDestination, borrowAsset)
4024
4157
  });
4025
4158
  }
4026
4159
  async getCollateralAmountHint(loanId) {
@@ -4040,24 +4173,22 @@ var InstantLoansModule = class {
4040
4173
  const collateralAsset = input.collateralAsset;
4041
4174
  const borrowAsset = input.borrowAsset;
4042
4175
  const [
4043
- depositTarget,
4044
- repayTarget,
4176
+ depositTargetSet,
4177
+ repaymentTargetSet,
4045
4178
  collateralPosition,
4046
4179
  borrowPosition,
4047
4180
  borrowPoolRate,
4048
4181
  activeActivities
4049
4182
  ] = await Promise.all([
4050
- resolveSupplyTarget(this.canisterContext, {
4183
+ this.resolveInstantLoanInflowTargets({
4051
4184
  profileId,
4052
4185
  poolId: collateralPoolId,
4053
- action: SupplyAction.deposit,
4054
- mechanism: SupplyPlanType.transfer
4186
+ action: SupplyAction.deposit
4055
4187
  }),
4056
- resolveSupplyTarget(this.canisterContext, {
4188
+ this.resolveInstantLoanInflowTargets({
4057
4189
  profileId,
4058
4190
  poolId: borrowPoolId,
4059
- action: SupplyAction.repayment,
4060
- mechanism: SupplyPlanType.transfer
4191
+ action: SupplyAction.repayment
4061
4192
  }),
4062
4193
  this.positions.getPosition(profileId, collateralPoolId),
4063
4194
  this.positions.getPosition(profileId, borrowPoolId),
@@ -4069,8 +4200,6 @@ var InstantLoansModule = class {
4069
4200
  borrowPosition,
4070
4201
  borrowPoolRate.borrowRate
4071
4202
  );
4072
- const repaymentInflowFee = totalDebtAmount > 0n ? await this.estimateRepaymentInflowFee(borrowAsset, repayTarget.chain) : { totalFee: 0n, estimateAvailable: false };
4073
- const repaymentAmount = totalDebtAmount + interestBufferAmount + repaymentInflowFee.totalFee;
4074
4203
  const currentCollateralAmount = collateralPosition?.deposited ?? 0n;
4075
4204
  const collateralAmount = input.collateralAmount;
4076
4205
  const collateralDecimals = collateralPosition?.depositedDecimals ?? getAssetNativeDecimals(collateralAsset);
@@ -4090,22 +4219,28 @@ var InstantLoansModule = class {
4090
4219
  collateralAmount,
4091
4220
  decimals: collateralDecimals,
4092
4221
  asset: collateralAsset,
4093
- target: depositTarget,
4222
+ targets: depositTargetSet.targets,
4094
4223
  detectedTimestamp: input.depositDetectedTimestamp,
4095
4224
  expiryTimestamp: input.expiryTimestamp
4096
4225
  });
4226
+ const repaymentTargets = await this.createRepaymentTargetQuotes({
4227
+ baseRepaymentAmount: totalDebtAmount + interestBufferAmount,
4228
+ asset: borrowAsset,
4229
+ targets: repaymentTargetSet.targets
4230
+ });
4097
4231
  const repayment = {
4098
- amount: repaymentAmount,
4099
4232
  decimals: borrowedDecimals,
4100
4233
  debtAmount: totalDebtAmount,
4101
4234
  interestBufferAmount,
4102
4235
  interestBufferSeconds: REPAYMENT_BUFFER_SECONDS,
4103
- inflowFeeAmount: repaymentInflowFee.totalFee,
4104
- inflowFeeEstimateAvailable: repaymentInflowFee.estimateAvailable,
4105
4236
  asset: borrowAsset,
4106
- chain: repayTarget.chain,
4107
- target: repayTarget
4237
+ targets: repaymentTargets
4108
4238
  };
4239
+ const borrowIdentifier = getInstantLoanAssetIdentifier(
4240
+ borrowAsset,
4241
+ input.borrowChain,
4242
+ "borrow"
4243
+ );
4109
4244
  return {
4110
4245
  loanId: input.loanId,
4111
4246
  ref: publicIdFromInt(input.loanId),
@@ -4116,16 +4251,14 @@ var InstantLoansModule = class {
4116
4251
  depositWindowSeconds: input.depositWindowSeconds
4117
4252
  },
4118
4253
  collateral: {
4119
- poolId: collateralPoolId,
4120
4254
  asset: collateralAsset,
4121
- chain: depositTarget.chain,
4255
+ poolId: collateralPoolId,
4122
4256
  decimals: collateralDecimals,
4123
4257
  amount: collateralAmount
4124
4258
  },
4125
4259
  borrow: {
4260
+ ...borrowIdentifier,
4126
4261
  poolId: borrowPoolId,
4127
- asset: borrowAsset,
4128
- chain: repayTarget.chain,
4129
4262
  decimals: borrowedDecimals,
4130
4263
  amount: input.borrowAmount,
4131
4264
  destination: input.borrowDestination
@@ -4145,32 +4278,81 @@ var InstantLoansModule = class {
4145
4278
  };
4146
4279
  }
4147
4280
  async createInitialDepositQuote(input) {
4148
- const inflowFee = await this.lending.estimateInflowFee({
4149
- asset: input.asset,
4150
- chain: input.target.chain
4151
- });
4281
+ const targets = {};
4282
+ for (const chain of INSTANT_LOAN_CHAINS) {
4283
+ const target = input.targets[chain];
4284
+ if (!target) continue;
4285
+ assertTargetAsset(target, input.asset);
4286
+ const inflowFee = await this.lending.estimateInflowFee(target);
4287
+ targets[chain] = {
4288
+ amount: input.collateralAmount + inflowFee.totalFee,
4289
+ inflowFeeAmount: inflowFee.totalFee,
4290
+ target
4291
+ };
4292
+ }
4152
4293
  return {
4153
- amount: input.collateralAmount + inflowFee.totalFee,
4154
4294
  decimals: input.decimals,
4155
4295
  collateralAmount: input.collateralAmount,
4156
- inflowFeeAmount: inflowFee.totalFee,
4157
4296
  asset: input.asset,
4158
- chain: input.target.chain,
4159
- target: input.target,
4297
+ targets,
4160
4298
  detectedTimestamp: input.detectedTimestamp,
4161
4299
  expiryTimestamp: input.expiryTimestamp
4162
4300
  };
4163
4301
  }
4164
- async estimateRepaymentInflowFee(asset, chain) {
4302
+ async resolveInstantLoanInflowTargets(request) {
4303
+ const selectedPool = await getPoolById(
4304
+ this.canisterContext,
4305
+ request.poolId
4306
+ );
4307
+ const baseRequest = {
4308
+ profileId: request.profileId,
4309
+ poolId: request.poolId,
4310
+ action: request.action,
4311
+ mechanism: SupplyPlanType.transfer
4312
+ };
4313
+ const poolChainTarget = await resolveSupplyTargetForPool(
4314
+ this.canisterContext,
4315
+ baseRequest,
4316
+ selectedPool
4317
+ );
4318
+ const poolChain = poolChainTarget.chain;
4319
+ const targets = {
4320
+ [poolChain]: poolChainTarget
4321
+ };
4322
+ if (poolChain === Chain.ICP) {
4323
+ return { targets };
4324
+ }
4325
+ const icpTarget = await resolveSupplyTargetForPool(
4326
+ this.canisterContext,
4327
+ { ...baseRequest, chain: Chain.ICP },
4328
+ selectedPool
4329
+ );
4330
+ targets[Chain.ICP] = icpTarget;
4331
+ return { targets };
4332
+ }
4333
+ async createRepaymentTargetQuotes(input) {
4334
+ const quotes = {};
4335
+ for (const chain of INSTANT_LOAN_CHAINS) {
4336
+ const target = input.targets[chain];
4337
+ if (!target) continue;
4338
+ assertTargetAsset(target, input.asset);
4339
+ const repaymentInflowFee = input.baseRepaymentAmount > 0n ? await this.estimateRepaymentInflowFee(target) : { totalFee: 0n, estimateAvailable: false };
4340
+ quotes[chain] = {
4341
+ amount: input.baseRepaymentAmount + repaymentInflowFee.totalFee,
4342
+ inflowFeeAmount: repaymentInflowFee.totalFee,
4343
+ inflowFeeEstimateAvailable: repaymentInflowFee.estimateAvailable,
4344
+ target
4345
+ };
4346
+ }
4347
+ return quotes;
4348
+ }
4349
+ async estimateRepaymentInflowFee(target) {
4165
4350
  try {
4166
- const fee = await this.lending.estimateInflowFee({
4167
- asset,
4168
- chain
4169
- });
4351
+ const fee = await this.lending.estimateInflowFee(target);
4170
4352
  return { totalFee: fee.totalFee, estimateAvailable: true };
4171
4353
  } catch {
4172
4354
  return {
4173
- totalFee: getRepaymentInflowFeeFallback(asset, chain),
4355
+ totalFee: getRepaymentInflowFeeFallback(target.asset, target.chain),
4174
4356
  estimateAvailable: false
4175
4357
  };
4176
4358
  }
@@ -4206,10 +4388,10 @@ var InstantLoansModule = class {
4206
4388
  ]);
4207
4389
  const ltvCalculation = new QuoteModule().calculateLtv(
4208
4390
  {
4209
- borrowAmount: request.borrowAmount,
4210
- borrowPoolId: request.borrowPoolId,
4211
- collateralAmount: request.collateralAmount,
4212
- collateralPoolId: request.collateralPoolId
4391
+ borrowAmount: request.borrow.amount,
4392
+ borrowPoolId: request.borrow.poolId,
4393
+ collateralAmount: request.collateral.amount,
4394
+ collateralPoolId: request.collateral.poolId
4213
4395
  },
4214
4396
  pools,
4215
4397
  assetPrices
@@ -4231,6 +4413,15 @@ function getRepaymentInflowFeeFallback(asset, chain) {
4231
4413
  }
4232
4414
  return 0n;
4233
4415
  }
4416
+ function assertTargetAsset(target, expectedAsset) {
4417
+ if (target.asset === expectedAsset) {
4418
+ return;
4419
+ }
4420
+ throw new LiquidiumError(
4421
+ LiquidiumErrorCode.INTERNAL,
4422
+ `Instant loan pool target asset ${target.asset} does not match ${expectedAsset}`
4423
+ );
4424
+ }
4234
4425
  function calculateInterestBufferAmount(borrowPosition, annualBorrowRate) {
4235
4426
  const totalDebtAmount = calculateTotalDebtAmount(borrowPosition);
4236
4427
  if (totalDebtAmount <= 0n || annualBorrowRate <= 0n) {
@@ -4323,13 +4514,23 @@ function deriveDepositExpiryTimestamp(input) {
4323
4514
  return input.depositDetectedTimestamp + input.depositWindowSeconds;
4324
4515
  }
4325
4516
  function validateCreateRequest(request) {
4326
- if (request.collateralAmount <= 0n) {
4517
+ getInstantLoanAssetIdentifier(
4518
+ request.borrow.asset,
4519
+ request.borrow.chain,
4520
+ "borrow"
4521
+ );
4522
+ getInstantLoanAssetIdentifier(
4523
+ request.collateral.asset,
4524
+ request.refund.chain,
4525
+ "refund"
4526
+ );
4527
+ if (request.collateral.amount <= 0n) {
4327
4528
  throw new LiquidiumError(
4328
4529
  LiquidiumErrorCode.VALIDATION_ERROR,
4329
4530
  "Instant loan collateral amount must be greater than zero"
4330
4531
  );
4331
4532
  }
4332
- if (request.borrowAmount <= 0n) {
4533
+ if (request.borrow.amount <= 0n) {
4333
4534
  throw new LiquidiumError(
4334
4535
  LiquidiumErrorCode.VALIDATION_ERROR,
4335
4536
  "Instant loan borrow amount must be greater than zero"
@@ -4354,45 +4555,200 @@ function throwLtvCalculationError(error) {
4354
4555
  error?.message ?? "Unable to calculate instant loan LTV"
4355
4556
  );
4356
4557
  }
4357
- function addressFromAccountInput(account) {
4358
- const address = typeof account === "string" ? account.trim() : account.address.trim();
4359
- if (!address) {
4558
+ function inferInstantLoanDeliveryChain(destination, asset) {
4559
+ if (destination.type !== "ChainAddress") {
4560
+ return Chain.ICP;
4561
+ }
4562
+ if (asset === Asset.BTC) {
4563
+ return Chain.BTC;
4564
+ }
4565
+ if (asset === Asset.USDC || asset === Asset.USDT) {
4566
+ return Chain.ETH;
4567
+ }
4568
+ return Chain.ICP;
4569
+ }
4570
+ function resolveInstantLoanDestination(params) {
4571
+ getInstantLoanAssetIdentifier(params.asset, params.chain, params.role);
4572
+ if (typeof params.destination === "string") {
4573
+ return resolveInstantLoanStringDestination({
4574
+ destination: params.destination,
4575
+ asset: params.asset,
4576
+ role: params.role,
4577
+ chain: params.chain,
4578
+ normalizeExternalDestination: params.normalizeExternalDestination
4579
+ });
4580
+ }
4581
+ assertInstantLoanDestinationTypeMatchesChain({
4582
+ destinationType: params.destination.type,
4583
+ asset: params.asset,
4584
+ role: params.role,
4585
+ chain: params.chain
4586
+ });
4587
+ switch (params.destination.type) {
4588
+ case "ChainAddress":
4589
+ assertExternalInstantLoanDestinationSupported(params.asset);
4590
+ return {
4591
+ External: params.normalizeExternalDestination(
4592
+ normalizeInstantLoanDestinationAddress(params.destination.address),
4593
+ params.asset,
4594
+ params.chain
4595
+ )
4596
+ };
4597
+ case "IcPrincipal":
4598
+ return {
4599
+ Native: normalizeInstantLoanPrincipal(
4600
+ normalizeInstantLoanDestinationAddress(params.destination.address)
4601
+ )
4602
+ };
4603
+ case "IcpAccountIdentifier":
4604
+ return parseAccountIdentifierInstantLoanDestination(
4605
+ normalizeInstantLoanDestinationAddress(params.destination.address)
4606
+ );
4607
+ case "IcrcAccount":
4608
+ return parseIcrcInstantLoanDestination(
4609
+ normalizeInstantLoanDestinationAddress(params.destination.address)
4610
+ );
4611
+ }
4612
+ }
4613
+ function resolveInstantLoanStringDestination(params) {
4614
+ const address = normalizeInstantLoanDestinationAddress(params.destination);
4615
+ if (params.chain === Chain.ICP && params.asset !== Asset.ICP) {
4616
+ try {
4617
+ return parsePrincipalInstantLoanDestination(address);
4618
+ } catch {
4619
+ throwInvalidCkInstantLoanDestination(params.role);
4620
+ }
4621
+ }
4622
+ if (params.chain !== Chain.ICP) {
4623
+ assertExternalInstantLoanDestinationSupported(params.asset);
4624
+ return {
4625
+ External: params.normalizeExternalDestination(
4626
+ address,
4627
+ params.asset,
4628
+ params.chain
4629
+ )
4630
+ };
4631
+ }
4632
+ for (const parser of [
4633
+ parseAccountIdentifierInstantLoanDestination,
4634
+ parsePrincipalInstantLoanDestination,
4635
+ parseIcrcInstantLoanDestination
4636
+ ]) {
4637
+ try {
4638
+ return parser(address);
4639
+ } catch {
4640
+ }
4641
+ }
4642
+ throwInvalidIcpInstantLoanDestination();
4643
+ }
4644
+ function getInstantLoanAssetIdentifier(asset, chain, role) {
4645
+ const identifier = { asset, chain };
4646
+ if (isAssetIdentifier(identifier)) {
4647
+ return identifier;
4648
+ }
4649
+ throw new LiquidiumError(
4650
+ LiquidiumErrorCode.VALIDATION_ERROR,
4651
+ `${chain} instant loan ${role} delivery is not supported for ${asset}`
4652
+ );
4653
+ }
4654
+ function assertInstantLoanDestinationTypeMatchesChain(params) {
4655
+ if (params.chain === Chain.ICP && params.asset !== Asset.ICP) {
4656
+ if (params.destinationType === "IcPrincipal") {
4657
+ return;
4658
+ }
4659
+ throwInvalidCkInstantLoanDestination(params.role);
4660
+ }
4661
+ if (params.chain === Chain.ICP && params.asset === Asset.ICP) {
4662
+ if (params.destinationType !== "ChainAddress") {
4663
+ return;
4664
+ }
4665
+ throwInvalidIcpInstantLoanDestination();
4666
+ }
4667
+ if (params.destinationType === "ChainAddress") {
4668
+ return;
4669
+ }
4670
+ if (params.destinationType === "IcPrincipal") {
4360
4671
  throw new LiquidiumError(
4361
4672
  LiquidiumErrorCode.VALIDATION_ERROR,
4362
- "Instant loan account address must be non-empty"
4673
+ `${params.chain} instant loan ${params.role} destination must be an external chain address for ${params.asset}`
4363
4674
  );
4364
4675
  }
4365
- return address;
4676
+ throw new LiquidiumError(
4677
+ LiquidiumErrorCode.VALIDATION_ERROR,
4678
+ `${params.asset} instant loan ${params.role} destination only supports ChainAddress or IcPrincipal destinations`
4679
+ );
4366
4680
  }
4367
- function accountFromCanister(account) {
4368
- if ("Native" in account) {
4369
- return { type: "Native", principal: account.Native.toText() };
4681
+ function throwInvalidCkInstantLoanDestination(role) {
4682
+ throw new LiquidiumError(
4683
+ LiquidiumErrorCode.VALIDATION_ERROR,
4684
+ `ICP instant loan ${role} destination must be an IC principal`
4685
+ );
4686
+ }
4687
+ function assertExternalInstantLoanDestinationSupported(asset) {
4688
+ if (asset !== Asset.ICP) {
4689
+ return;
4370
4690
  }
4371
- if ("AccountIdentifier" in account) {
4372
- return {
4373
- type: "AccountIdentifier",
4374
- address: account.AccountIdentifier
4375
- };
4691
+ throwInvalidIcpInstantLoanDestination();
4692
+ }
4693
+ function throwInvalidIcpInstantLoanDestination() {
4694
+ throw new LiquidiumError(
4695
+ LiquidiumErrorCode.INVALID_ADDRESS,
4696
+ "ICP instant loan destination must be an IC principal, ICP account identifier, or ICRC account"
4697
+ );
4698
+ }
4699
+ function parsePrincipalInstantLoanDestination(address) {
4700
+ return { Native: normalizeInstantLoanPrincipal(address) };
4701
+ }
4702
+ function parseAccountIdentifierInstantLoanDestination(address) {
4703
+ if (address.length !== ICP_ACCOUNT_IDENTIFIER_HEX_LENGTH) {
4704
+ throw new LiquidiumError(
4705
+ LiquidiumErrorCode.INVALID_ADDRESS,
4706
+ "Invalid ICP account identifier"
4707
+ );
4376
4708
  }
4377
- if ("Icrc" in account) {
4378
- const subaccount = normalizeOptionalSubaccount(account.Icrc.subaccount[0]);
4379
- return {
4380
- type: "Icrc",
4381
- owner: account.Icrc.owner.toText(),
4382
- subaccount,
4383
- address: icrc.encodeIcrcAccount({
4384
- owner: account.Icrc.owner,
4385
- subaccount
4386
- })
4387
- };
4709
+ try {
4710
+ return { AccountIdentifier: normalizeIcpAccountIdentifier(address) };
4711
+ } catch {
4712
+ throw new LiquidiumError(
4713
+ LiquidiumErrorCode.INVALID_ADDRESS,
4714
+ "Invalid ICP account identifier"
4715
+ );
4388
4716
  }
4389
- return { type: "External", address: account.External };
4390
4717
  }
4391
- function normalizeOptionalSubaccount(subaccount) {
4392
- if (!subaccount) {
4393
- return void 0;
4718
+ function parseIcrcInstantLoanDestination(address) {
4719
+ let decoded;
4720
+ try {
4721
+ decoded = decodeIcrcAccountAddress(address);
4722
+ } catch {
4723
+ throw new LiquidiumError(
4724
+ LiquidiumErrorCode.INVALID_ADDRESS,
4725
+ "Invalid instant loan ICRC destination"
4726
+ );
4394
4727
  }
4395
- return subaccount instanceof Uint8Array ? subaccount : Uint8Array.from(subaccount);
4728
+ return { Icrc: decoded.account.address };
4729
+ }
4730
+ function normalizeInstantLoanPrincipal(address) {
4731
+ try {
4732
+ return principal.Principal.fromText(address).toText();
4733
+ } catch {
4734
+ throw new LiquidiumError(
4735
+ LiquidiumErrorCode.INVALID_ADDRESS,
4736
+ "Invalid instant loan IC principal destination"
4737
+ );
4738
+ }
4739
+ }
4740
+ function normalizeInstantLoanDestinationAddress(address) {
4741
+ const trimmedAddress = address.trim();
4742
+ if (!trimmedAddress) {
4743
+ throw new LiquidiumError(
4744
+ LiquidiumErrorCode.VALIDATION_ERROR,
4745
+ "Instant loan account address must be non-empty"
4746
+ );
4747
+ }
4748
+ return trimmedAddress;
4749
+ }
4750
+ function accountFromCanister(account) {
4751
+ return mapCanisterAccountToLiquidiumAccount(account);
4396
4752
  }
4397
4753
  function mapInstantLoanEvent(event) {
4398
4754
  return {
@@ -4409,7 +4765,10 @@ function mapInstantLoanEventType(eventType) {
4409
4765
  type: "LoanCreated",
4410
4766
  loanId: event.loan_id,
4411
4767
  borrowDestination: accountFromCanister(event.borrow_destination),
4412
- collateralAsset: event.lend_asset,
4768
+ collateralAsset: parseInstantLoanAsset(
4769
+ event.lend_asset,
4770
+ "event collateral asset"
4771
+ ),
4413
4772
  borrowAmount: event.borrow_amount,
4414
4773
  collateralPoolId: event.lend_pool_id.toText(),
4415
4774
  refundDestination: accountFromCanister(event.refund_destination),
@@ -4417,7 +4776,10 @@ function mapInstantLoanEventType(eventType) {
4417
4776
  depositWindowSeconds: event.ltv_timer_s,
4418
4777
  profileId: event.lending_profile.toText(),
4419
4778
  borrowPoolId: event.borrow_pool_id.toText(),
4420
- borrowAsset: event.borrow_asset
4779
+ borrowAsset: parseInstantLoanAsset(
4780
+ event.borrow_asset,
4781
+ "event borrow asset"
4782
+ )
4421
4783
  };
4422
4784
  }
4423
4785
  if ("FullLendWithdrawalRequested" in eventType) {
@@ -4584,6 +4946,12 @@ function mapCandidateWire(wire) {
4584
4946
  })
4585
4947
  };
4586
4948
  }
4949
+ function parseInstantLoanAsset(value, label) {
4950
+ return parseApiStringUnion(value, INSTANT_LOAN_ASSETS, {
4951
+ context: INSTANT_LOAN_WIRE_CONTEXT,
4952
+ label
4953
+ });
4954
+ }
4587
4955
  function mapInstantLoansErrorToLiquidiumError(error) {
4588
4956
  const [key, payload] = Object.entries(error)[0];
4589
4957
  switch (key) {
@@ -4725,11 +5093,25 @@ function encodeBytes32Hex(bytes) {
4725
5093
  var idlFactory4 = ({ IDL }) => IDL.Service({
4726
5094
  icrc1_fee: IDL.Func([], [IDL.Nat], ["query"])
4727
5095
  });
4728
- function createCkBtcLedgerActor(canisterContext) {
4729
- const canisterId = CK_CANISTER_IDS.btcLedger;
5096
+ function createIcrcLedgerActor(params) {
5097
+ if (!params.canisterId) {
5098
+ throw new LiquidiumError(
5099
+ LiquidiumErrorCode.SERVICE_UNAVAILABLE,
5100
+ `${params.ledgerName} ledger canister ID is not configured`
5101
+ );
5102
+ }
4730
5103
  return agent.Actor.createActor(idlFactory4, {
4731
- agent: canisterContext.agent,
4732
- canisterId
5104
+ agent: params.canisterContext.agent,
5105
+ canisterId: params.canisterId
5106
+ });
5107
+ }
5108
+
5109
+ // src/core/canisters/ckbtc/ledger.ts
5110
+ function createCkBtcLedgerActor(canisterContext) {
5111
+ return createIcrcLedgerActor({
5112
+ canisterContext,
5113
+ canisterId: CK_CANISTER_IDS.BTC.ledger,
5114
+ ledgerName: "ckBTC"
4733
5115
  });
4734
5116
  }
4735
5117
 
@@ -4758,11 +5140,83 @@ function accountTypeToString(accountType) {
4758
5140
  switch (accountType.type) {
4759
5141
  case "External":
4760
5142
  return `Address:${accountType.data}`;
5143
+ case "Icrc":
5144
+ return `Icrc:${accountType.data}`;
5145
+ case "AccountIdentifier":
5146
+ return `AccountId:${accountType.data}`;
4761
5147
  case "Native":
4762
5148
  return `Principal:${accountType.data}`;
4763
5149
  }
4764
5150
  }
4765
5151
 
5152
+ // src/core/pool-ledger-assets.ts
5153
+ function getPoolLedgerAssetRoute(params) {
5154
+ if (params.asset === Asset.BTC && (params.chain === Chain.BTC || params.chain === Chain.ICP)) {
5155
+ return {
5156
+ ledgerCanisterId: CK_CANISTER_IDS.BTC.ledger,
5157
+ asset: Asset.BTC,
5158
+ chain: params.chain
5159
+ };
5160
+ }
5161
+ if (params.asset === Asset.USDT && (params.chain === Chain.ETH || params.chain === Chain.ICP)) {
5162
+ return {
5163
+ ledgerCanisterId: CK_CANISTER_IDS.USDT.ledger,
5164
+ asset: Asset.USDT,
5165
+ chain: params.chain
5166
+ };
5167
+ }
5168
+ if (params.asset === Asset.USDC && (params.chain === Chain.ETH || params.chain === Chain.ICP)) {
5169
+ return {
5170
+ ledgerCanisterId: CK_CANISTER_IDS.USDC.ledger,
5171
+ asset: Asset.USDC,
5172
+ chain: params.chain
5173
+ };
5174
+ }
5175
+ if (params.asset === Asset.ICP && params.chain === Chain.ICP) {
5176
+ return {
5177
+ ledgerCanisterId: CK_CANISTER_IDS.ICP.ledger,
5178
+ asset: Asset.ICP,
5179
+ chain: Chain.ICP
5180
+ };
5181
+ }
5182
+ throw new LiquidiumError(
5183
+ LiquidiumErrorCode.VALIDATION_ERROR,
5184
+ `IC ledger transfers are not supported for ${params.asset} on ${params.chain}`
5185
+ );
5186
+ }
5187
+
5188
+ // src/core/withdraw-minimums.ts
5189
+ var MINIMUM_BTC_WITHDRAW_AMOUNT_SATS = 5000n;
5190
+ var MINIMUM_STABLECOIN_WITHDRAW_AMOUNT_BASE_UNITS = 1000000n;
5191
+ var MIN_WITHDRAW_AMOUNTS_BY_ASSET = {
5192
+ [Asset.BTC]: MINIMUM_BTC_WITHDRAW_AMOUNT_SATS,
5193
+ [Asset.USDC]: MINIMUM_STABLECOIN_WITHDRAW_AMOUNT_BASE_UNITS,
5194
+ [Asset.USDT]: MINIMUM_STABLECOIN_WITHDRAW_AMOUNT_BASE_UNITS
5195
+ };
5196
+ function getMinimumWithdrawAmount(asset) {
5197
+ if (!isMinimumWithdrawAsset(asset)) {
5198
+ return 0n;
5199
+ }
5200
+ return MIN_WITHDRAW_AMOUNTS_BY_ASSET[asset];
5201
+ }
5202
+ function getWithdrawAmountMinimumValidationError(params) {
5203
+ const minimumAmount = getMinimumWithdrawAmount(params.asset);
5204
+ if (minimumAmount <= 0n || params.amount >= minimumAmount) {
5205
+ return null;
5206
+ }
5207
+ return {
5208
+ asset: params.asset,
5209
+ minimumAmount,
5210
+ message: formatMinimumWithdrawAmountMessage(params.asset, minimumAmount)
5211
+ };
5212
+ }
5213
+ function formatMinimumWithdrawAmountMessage(asset, minimumAmount) {
5214
+ return `Withdraw amount must be at least ${minimumAmount} base units for ${asset}`;
5215
+ }
5216
+ function isMinimumWithdrawAsset(asset) {
5217
+ return Object.hasOwn(MIN_WITHDRAW_AMOUNTS_BY_ASSET, asset);
5218
+ }
5219
+
4766
5220
  // src/modules/lending/_internal/inflow-fee-rounding.ts
4767
5221
  var ETH_STABLECOIN_INFLOW_FEE_ROUND_UP_TO_NEAREST_10_CENTS = 100000n;
4768
5222
  var BTC_INFLOW_FEE_ROUND_UP_TO_NEAREST_SATS = 500n;
@@ -4848,48 +5302,61 @@ var SupplyFlowExecutor = class {
4848
5302
  }
4849
5303
  params;
4850
5304
  async create(request) {
4851
- const target = await resolveSupplyTarget(
5305
+ const requestedMechanism = request.mechanism ?? null;
5306
+ if (requestedMechanism === null && request.walletAdapter === void 0 && (request.account !== void 0 || request.amount !== void 0)) {
5307
+ throw new LiquidiumError(
5308
+ LiquidiumErrorCode.VALIDATION_ERROR,
5309
+ "Wallet-executed supply requires walletAdapter, account, and amount"
5310
+ );
5311
+ }
5312
+ const selectedPool = await getPoolById(
4852
5313
  this.params.canisterContext,
4853
- request
5314
+ request.poolId
4854
5315
  );
4855
- const instruction = {
4856
- poolId: request.poolId,
4857
- asset: target.asset,
4858
- chain: target.chain,
4859
- action: request.action,
4860
- target
4861
- };
4862
- const mechanism = resolveSupplyMechanism({
4863
- asset: instruction.asset,
4864
- chain: instruction.chain,
4865
- requestedMechanism: request.mechanism
4866
- });
5316
+ const target = await resolveSupplyTargetForPool(
5317
+ this.params.canisterContext,
5318
+ {
5319
+ profileId: request.profileId,
5320
+ poolId: request.poolId,
5321
+ action: request.action,
5322
+ mechanism: requestedMechanism,
5323
+ chain: request.chain
5324
+ },
5325
+ selectedPool
5326
+ );
5327
+ const mechanism = requestedMechanism === SupplyPlanType.contractInteraction ? SupplyPlanType.contractInteraction : SupplyPlanType.transfer;
4867
5328
  const defaultSubmitInflowRequest = getDefaultSubmitInflowRequest({
4868
5329
  action: request.action,
4869
- chain: mechanism === SupplyPlanType.contractInteraction ? Chain.ETH : instruction.chain
5330
+ chain: mechanism === SupplyPlanType.contractInteraction ? Chain.ETH : target.chain
4870
5331
  });
4871
5332
  let txid;
4872
5333
  switch (mechanism) {
4873
5334
  case SupplyPlanType.transfer:
4874
- if (request.walletAdapter) {
4875
- txid = await this.sendAndSubmitNativeSupplyInflow({
5335
+ if (isWalletTransferSupplyRequest(request)) {
5336
+ txid = await this.sendAndSubmitTransferSupplyInflow({
4876
5337
  request,
4877
- instruction,
5338
+ target,
4878
5339
  defaultSubmitInflowRequest
4879
5340
  });
4880
5341
  }
4881
5342
  break;
4882
5343
  case SupplyPlanType.contractInteraction:
5344
+ if (!isContractInteractionSupplyRequest(request)) {
5345
+ throw new LiquidiumError(
5346
+ LiquidiumErrorCode.VALIDATION_ERROR,
5347
+ "Contract-interaction supply requires contract-interaction request fields"
5348
+ );
5349
+ }
4883
5350
  txid = await this.executeContractSupply({
4884
5351
  request,
4885
- instruction,
5352
+ target,
4886
5353
  defaultSubmitInflowRequest
4887
5354
  });
4888
5355
  break;
4889
5356
  }
4890
5357
  return {
4891
5358
  type: mechanism,
4892
- target: instruction.target,
5359
+ target,
4893
5360
  txid,
4894
5361
  status: createLiquidiumStatus({
4895
5362
  operation: mapSupplyActionToStatusOperation(request.action),
@@ -4897,7 +5364,7 @@ var SupplyFlowExecutor = class {
4897
5364
  }),
4898
5365
  submit: async (submitRequest) => {
4899
5366
  return await this.submitSupplyFlowInflow({
4900
- instruction,
5367
+ target,
4901
5368
  mechanism,
4902
5369
  defaultSubmitInflowRequest,
4903
5370
  submitRequest
@@ -4906,7 +5373,10 @@ var SupplyFlowExecutor = class {
4906
5373
  };
4907
5374
  }
4908
5375
  async getEvmSupplyContext(request) {
4909
- const selectedPool = await this.params.getPoolById(request.poolId);
5376
+ const selectedPool = await getPoolById(
5377
+ this.params.canisterContext,
5378
+ request.poolId
5379
+ );
4910
5380
  return await this.getEvmSupplyContextForPool({
4911
5381
  request,
4912
5382
  asset: selectedPool.asset,
@@ -4970,14 +5440,8 @@ var SupplyFlowExecutor = class {
4970
5440
  approvalStrategy
4971
5441
  };
4972
5442
  }
4973
- async sendAndSubmitNativeSupplyInflow(params) {
4974
- const { request, instruction, defaultSubmitInflowRequest } = params;
4975
- if (instruction.target.type !== "nativeAddress") {
4976
- throw new LiquidiumError(
4977
- LiquidiumErrorCode.VALIDATION_ERROR,
4978
- "Wallet-executed supply requires a native-address target"
4979
- );
4980
- }
5443
+ async sendAndSubmitTransferSupplyInflow(params) {
5444
+ const { request, target, defaultSubmitInflowRequest } = params;
4981
5445
  if (!request.walletAdapter) {
4982
5446
  throw new LiquidiumError(
4983
5447
  LiquidiumErrorCode.VALIDATION_ERROR,
@@ -4997,16 +5461,27 @@ var SupplyFlowExecutor = class {
4997
5461
  "Wallet-executed supply requires a positive amount"
4998
5462
  );
4999
5463
  }
5000
- const txid = await this.sendNativeSupplyTransaction({
5464
+ const txid = target.chain === Chain.ICP ? await this.sendIcrcSupplyTransaction({
5465
+ walletAdapter: request.walletAdapter,
5466
+ asset: target.asset,
5467
+ chain: target.chain,
5468
+ transfer: {
5469
+ ledgerCanisterId: getPoolLedgerAssetRoute(target).ledgerCanisterId,
5470
+ to: decodeIcrcAccountAddress(target.address).account,
5471
+ amount: request.amount
5472
+ },
5473
+ senderAccount: account,
5474
+ action: request.action
5475
+ }) : await this.sendChainAddressSupplyTransaction({
5001
5476
  walletAdapter: request.walletAdapter,
5002
- chain: instruction.chain,
5003
- toAddress: instruction.target.address,
5477
+ chain: target.chain,
5478
+ toAddress: target.address,
5004
5479
  amount: request.amount,
5005
5480
  senderAccount: account,
5006
- asset: instruction.asset,
5481
+ asset: target.asset,
5007
5482
  action: request.action
5008
5483
  });
5009
- if (shouldSubmitInflow({ instruction, mechanism: SupplyPlanType.transfer })) {
5484
+ if (shouldSubmitInflow({ target, mechanism: SupplyPlanType.transfer })) {
5010
5485
  try {
5011
5486
  await this.submitInflowWithRetry(txid, defaultSubmitInflowRequest);
5012
5487
  } catch {
@@ -5033,11 +5508,11 @@ var SupplyFlowExecutor = class {
5033
5508
  });
5034
5509
  }
5035
5510
  async executeContractSupply(params) {
5036
- const { request, instruction, defaultSubmitInflowRequest } = params;
5037
- if (instruction.target.type !== "icrcAccount" || instruction.chain !== Chain.ETH) {
5511
+ const { request, target, defaultSubmitInflowRequest } = params;
5512
+ if (!isEthStablecoin(target.asset, target.chain)) {
5038
5513
  throw new LiquidiumError(
5039
5514
  LiquidiumErrorCode.VALIDATION_ERROR,
5040
- "Contract-interaction supply is only supported for ETH ICRC-account targets"
5515
+ "Contract-interaction supply is only supported for ETH stablecoin targets"
5041
5516
  );
5042
5517
  }
5043
5518
  const walletAddressInput = request.account?.trim();
@@ -5076,8 +5551,8 @@ var SupplyFlowExecutor = class {
5076
5551
  amount: request.amount,
5077
5552
  action: request.action
5078
5553
  },
5079
- asset: instruction.asset,
5080
- chain: instruction.chain
5554
+ asset: target.asset,
5555
+ chain: target.chain
5081
5556
  });
5082
5557
  const supplyAmount = BigInt(evmSupplyContext.amount);
5083
5558
  if (BigInt(evmSupplyContext.balance) < supplyAmount) {
@@ -5133,7 +5608,7 @@ var SupplyFlowExecutor = class {
5133
5608
  amount: supplyAmount,
5134
5609
  poolId: request.poolId,
5135
5610
  profileId: request.profileId,
5136
- destinationAccount: instruction.target.account,
5611
+ destinationAccount: target.address,
5137
5612
  action: request.action
5138
5613
  }),
5139
5614
  `supply-${request.action}-deposit-erc20`
@@ -5144,7 +5619,7 @@ var SupplyFlowExecutor = class {
5144
5619
  }
5145
5620
  return depositTxid;
5146
5621
  }
5147
- async sendNativeSupplyTransaction(params) {
5622
+ async sendChainAddressSupplyTransaction(params) {
5148
5623
  switch (params.chain) {
5149
5624
  case Chain.BTC: {
5150
5625
  if (!params.walletAdapter.sendBtcTransaction) {
@@ -5158,8 +5633,7 @@ var SupplyFlowExecutor = class {
5158
5633
  toAddress: params.toAddress,
5159
5634
  amountSats: params.amount,
5160
5635
  account: params.senderAccount,
5161
- actionType: `supply-${params.action}`,
5162
- transferMode: TransferMode.native
5636
+ actionType: `supply-${params.action}`
5163
5637
  });
5164
5638
  }
5165
5639
  case Chain.ETH: {
@@ -5174,7 +5648,6 @@ var SupplyFlowExecutor = class {
5174
5648
  chain: Chain.ETH,
5175
5649
  account: params.senderAccount,
5176
5650
  actionType: `supply-${params.action}`,
5177
- transferMode: TransferMode.native,
5178
5651
  transaction: createTransferErc20Transaction({
5179
5652
  tokenAddress: getEthStablecoinContractAddress(params.asset),
5180
5653
  recipientAddress: params.toAddress,
@@ -5186,7 +5659,6 @@ var SupplyFlowExecutor = class {
5186
5659
  chain: Chain.ETH,
5187
5660
  account: params.senderAccount,
5188
5661
  actionType: `supply-${params.action}`,
5189
- transferMode: TransferMode.native,
5190
5662
  transaction: {
5191
5663
  to: params.toAddress,
5192
5664
  value: params.amount.toString()
@@ -5196,10 +5668,32 @@ var SupplyFlowExecutor = class {
5196
5668
  default:
5197
5669
  throw new LiquidiumError(
5198
5670
  LiquidiumErrorCode.VALIDATION_ERROR,
5199
- `Native-address wallet execution is not supported for ${params.chain}`
5671
+ `Chain-address wallet execution is not supported for ${params.chain}`
5200
5672
  );
5201
5673
  }
5202
5674
  }
5675
+ async sendIcrcSupplyTransaction(params) {
5676
+ if (!params.walletAdapter.sendIcrcTransfer) {
5677
+ throw new LiquidiumError(
5678
+ LiquidiumErrorCode.VALIDATION_ERROR,
5679
+ "ICRC wallet adapter does not support ledger transfers"
5680
+ );
5681
+ }
5682
+ const route = getPoolLedgerAssetRoute({
5683
+ asset: params.asset,
5684
+ chain: params.chain
5685
+ });
5686
+ return await params.walletAdapter.sendIcrcTransfer({
5687
+ chain: Chain.ICP,
5688
+ asset: route.asset,
5689
+ transfer: {
5690
+ ...params.transfer,
5691
+ ledgerCanisterId: route.ledgerCanisterId
5692
+ },
5693
+ account: params.senderAccount,
5694
+ actionType: `supply-${params.action}`
5695
+ });
5696
+ }
5203
5697
  async submitInflowWithRetry(txid, extraRequest) {
5204
5698
  if (!extraRequest) {
5205
5699
  throw new LiquidiumError(
@@ -5228,7 +5722,6 @@ var SupplyFlowExecutor = class {
5228
5722
  chain: Chain.ETH,
5229
5723
  account: walletAddress,
5230
5724
  actionType,
5231
- transferMode: TransferMode.native,
5232
5725
  transaction: request
5233
5726
  });
5234
5727
  }
@@ -5317,13 +5810,19 @@ function getUnknownErrorMessage(error) {
5317
5810
  }
5318
5811
  function getDefaultSubmitInflowRequest(params) {
5319
5812
  if (params.chain !== Chain.BTC && params.chain !== Chain.ETH) {
5320
- return void 0;
5813
+ return null;
5321
5814
  }
5322
5815
  return {
5323
5816
  chain: params.chain,
5324
5817
  operation: mapSupplyActionToStatusOperation(params.action)
5325
5818
  };
5326
5819
  }
5820
+ function isWalletTransferSupplyRequest(request) {
5821
+ return request.mechanism !== SupplyPlanType.contractInteraction && request.walletAdapter !== void 0;
5822
+ }
5823
+ function isContractInteractionSupplyRequest(request) {
5824
+ return request.mechanism === SupplyPlanType.contractInteraction;
5825
+ }
5327
5826
  function mapSupplyActionToStatusOperation(action) {
5328
5827
  return action === SupplyAction.repayment ? "repayment" : "deposit";
5329
5828
  }
@@ -5331,7 +5830,10 @@ function shouldSubmitInflow(params) {
5331
5830
  if (params.mechanism !== SupplyPlanType.transfer) {
5332
5831
  return true;
5333
5832
  }
5334
- return !isEthStablecoin(params.instruction.asset, params.instruction.chain);
5833
+ if (params.target.chain === Chain.ICP) {
5834
+ return false;
5835
+ }
5836
+ return !isEthStablecoin(params.target.asset, params.target.chain);
5335
5837
  }
5336
5838
  function mapCanisterOutflowDetails(outflow) {
5337
5839
  const rawOutflowType = getVariantKey(outflow.outflow_type);
@@ -5345,41 +5847,31 @@ function mapCanisterOutflowDetails(outflow) {
5345
5847
  };
5346
5848
  }
5347
5849
  function mapCanisterAccountType(receiver) {
5348
- if ("Native" in receiver) {
5349
- return {
5350
- type: "Native",
5351
- account: receiver.Native.toText()
5352
- };
5353
- }
5354
- if ("AccountIdentifier" in receiver) {
5355
- return {
5356
- type: "AccountIdentifier",
5357
- account: receiver.AccountIdentifier
5358
- };
5359
- }
5360
- if ("Icrc" in receiver) {
5361
- const subaccount = normalizeOptionalSubaccount2(receiver.Icrc.subaccount[0]);
5362
- return {
5363
- type: "Icrc",
5364
- owner: receiver.Icrc.owner.toText(),
5365
- subaccount,
5366
- account: icrc.encodeIcrcAccount({
5367
- owner: receiver.Icrc.owner,
5368
- subaccount
5369
- })
5370
- };
5850
+ return mapCanisterAccountToLiquidiumAccount(receiver);
5851
+ }
5852
+ function parseOutflowDestination(params) {
5853
+ const destination = normalizeOutflowDestinationInput(params.destination);
5854
+ const parsedAccount = destination.type ? parseOutflowDestinationWithHint(destination) : parseOutflowDestinationAutomatically(destination.address);
5855
+ assertDestinationTypeSupportedByChain({
5856
+ accountType: parsedAccount.accountType,
5857
+ poolChain: params.poolChain,
5858
+ destinationChain: params.destinationChain
5859
+ });
5860
+ if (parsedAccount.accountType !== "ChainAddress") {
5861
+ return parsedAccount;
5371
5862
  }
5863
+ const externalAddress = normalizeExternalAddress({
5864
+ address: parsedAccount.address,
5865
+ asset: params.asset,
5866
+ chain: params.destinationChain
5867
+ });
5372
5868
  return {
5373
- type: "External",
5374
- account: receiver.External
5869
+ address: externalAddress,
5870
+ accountType: "ChainAddress",
5871
+ canisterAccount: { External: externalAddress },
5872
+ messageAccount: { type: "External", data: externalAddress }
5375
5873
  };
5376
5874
  }
5377
- function normalizeOptionalSubaccount2(subaccount) {
5378
- if (!subaccount) {
5379
- return void 0;
5380
- }
5381
- return subaccount instanceof Uint8Array ? subaccount : Uint8Array.from(subaccount);
5382
- }
5383
5875
  function normalizeOutflowType(rawOutflowType) {
5384
5876
  switch (rawOutflowType) {
5385
5877
  case "Withdraw":
@@ -5402,6 +5894,117 @@ function mapWalletChainToLendingChain(chain) {
5402
5894
  case Chain.ETH:
5403
5895
  return { ETH: null };
5404
5896
  }
5897
+ throw new LiquidiumError(
5898
+ LiquidiumErrorCode.VALIDATION_ERROR,
5899
+ `Message signing is not supported for ${String(chain)}`
5900
+ );
5901
+ }
5902
+ function normalizeOutflowDestinationInput(destination) {
5903
+ if (typeof destination === "string") {
5904
+ return { address: destination, type: null };
5905
+ }
5906
+ return destination;
5907
+ }
5908
+ function parseOutflowDestinationWithHint(destination) {
5909
+ try {
5910
+ switch (destination.type) {
5911
+ case "ChainAddress":
5912
+ return parseExternalDestination(destination.address);
5913
+ case "IcPrincipal":
5914
+ return parseIcPrincipalDestination(destination.address);
5915
+ case "IcrcAccount":
5916
+ return parseIcrcDestination(destination.address);
5917
+ case "IcpAccountIdentifier":
5918
+ return parseAccountIdentifierDestination(destination.address);
5919
+ default:
5920
+ throw new LiquidiumError(
5921
+ LiquidiumErrorCode.VALIDATION_ERROR,
5922
+ `Unsupported outflow account type: ${String(destination.type)}`
5923
+ );
5924
+ }
5925
+ } catch (error) {
5926
+ if (error instanceof LiquidiumError) {
5927
+ throw error;
5928
+ }
5929
+ throw new LiquidiumError(
5930
+ LiquidiumErrorCode.INVALID_ADDRESS,
5931
+ `Invalid ${destination.type} outflow destination`
5932
+ );
5933
+ }
5934
+ }
5935
+ function parseOutflowDestinationAutomatically(address) {
5936
+ const parsers = [
5937
+ parseAccountIdentifierDestination,
5938
+ parseIcPrincipalDestination,
5939
+ parseIcrcDestination
5940
+ ];
5941
+ for (const parser of parsers) {
5942
+ try {
5943
+ return parser(address);
5944
+ } catch {
5945
+ }
5946
+ }
5947
+ return parseExternalDestination(address);
5948
+ }
5949
+ function parseExternalDestination(address) {
5950
+ return {
5951
+ address,
5952
+ accountType: "ChainAddress",
5953
+ canisterAccount: { External: address },
5954
+ messageAccount: { type: "External", data: address }
5955
+ };
5956
+ }
5957
+ function parseIcPrincipalDestination(address) {
5958
+ const principal$1 = principal.Principal.fromText(address);
5959
+ const principalText = principal$1.toText();
5960
+ return {
5961
+ address: principalText,
5962
+ accountType: "IcPrincipal",
5963
+ canisterAccount: { Native: principal$1 },
5964
+ messageAccount: { type: "Native", data: principalText }
5965
+ };
5966
+ }
5967
+ function parseIcrcDestination(address) {
5968
+ const decoded = decodeIcrcAccountAddress(address);
5969
+ return {
5970
+ address: decoded.account.address,
5971
+ accountType: "IcrcAccount",
5972
+ canisterAccount: {
5973
+ Icrc: {
5974
+ owner: decoded.owner,
5975
+ subaccount: decoded.subaccount ? [decoded.subaccount] : []
5976
+ }
5977
+ },
5978
+ messageAccount: { type: "Icrc", data: decoded.account.address }
5979
+ };
5980
+ }
5981
+ function parseAccountIdentifierDestination(address) {
5982
+ const accountIdentifier = normalizeIcpAccountIdentifier(address);
5983
+ return {
5984
+ address: accountIdentifier,
5985
+ accountType: "IcpAccountIdentifier",
5986
+ canisterAccount: { AccountIdentifier: accountIdentifier },
5987
+ messageAccount: { type: "AccountIdentifier", data: accountIdentifier }
5988
+ };
5989
+ }
5990
+ function assertDestinationTypeSupportedByChain(params) {
5991
+ if (params.poolChain === Chain.BTC || params.poolChain === Chain.ETH) {
5992
+ if (params.destinationChain === params.poolChain && params.accountType === "ChainAddress") {
5993
+ return;
5994
+ }
5995
+ if (params.destinationChain === Chain.ICP && params.accountType === "IcPrincipal") {
5996
+ return;
5997
+ }
5998
+ }
5999
+ if (params.poolChain === Chain.ICP && params.destinationChain === Chain.ICP) {
6000
+ if (params.accountType === "IcpAccountIdentifier" || params.accountType === "IcrcAccount" || params.accountType === "IcPrincipal") {
6001
+ return;
6002
+ }
6003
+ }
6004
+ throw new LiquidiumError(
6005
+ LiquidiumErrorCode.VALIDATION_ERROR,
6006
+ "Target pool does not support this address type"
6007
+ );
5405
6008
  }
5406
6009
 
5407
6010
  // src/modules/lending/lending.ts
@@ -5423,56 +6026,86 @@ var LendingModule = class {
5423
6026
  * @returns A signable {@link WithdrawAction} with `submit` wired to the canister.
5424
6027
  */
5425
6028
  async prepareWithdraw(request) {
5426
- const destinationAccount = request.receiverAddress.trim();
6029
+ const destination = resolveOutflowDestinationInput({
6030
+ receiver: request.receiver,
6031
+ errorMessage: "Withdraw requires a custom outflow account"
6032
+ });
5427
6033
  const signerAccount = normalizeEvmAddress(
5428
6034
  request.signerWalletAddress.trim()
5429
6035
  );
5430
- if (!destinationAccount) {
6036
+ if (!signerAccount) {
5431
6037
  throw new LiquidiumError(
5432
6038
  LiquidiumErrorCode.VALIDATION_ERROR,
5433
- "Withdraw requires a custom outflow account"
6039
+ "Withdraw requires a signer account"
5434
6040
  );
5435
6041
  }
5436
- if (!signerAccount) {
6042
+ if (request.amount <= 0n) {
5437
6043
  throw new LiquidiumError(
5438
6044
  LiquidiumErrorCode.VALIDATION_ERROR,
5439
- "Withdraw requires a signer account"
6045
+ "Withdraw amount must be greater than 0"
5440
6046
  );
5441
6047
  }
5442
- const receiverAddress = await this.normalizeOutflowReceiverAddress({
5443
- poolId: request.poolId,
5444
- receiverAddress: destinationAccount
6048
+ const selectedPool = await getPoolById(
6049
+ this.canisterContext,
6050
+ request.poolId
6051
+ );
6052
+ const selectedAsset = selectedPool.asset;
6053
+ const minimumWithdrawAmountError = getWithdrawAmountMinimumValidationError({
6054
+ amount: request.amount,
6055
+ asset: selectedAsset
6056
+ });
6057
+ if (minimumWithdrawAmountError) {
6058
+ throw new LiquidiumError(
6059
+ LiquidiumErrorCode.VALIDATION_ERROR,
6060
+ minimumWithdrawAmountError.message
6061
+ );
6062
+ }
6063
+ const receiver = parseOutflowDestination({
6064
+ destination,
6065
+ asset: selectedAsset,
6066
+ poolChain: selectedPool.chain,
6067
+ destinationChain: request.chain
5445
6068
  });
5446
6069
  const lendingActor = createLendingActor(this.canisterContext);
5447
6070
  try {
5448
6071
  const expiryTimestamp = computeExpiryTimestampFromNow();
5449
6072
  const nonce = await lendingActor.get_nonce(signerAccount);
5450
- const withdrawRequestData = {
6073
+ const withdrawActionData = {
5451
6074
  profileId: request.profileId,
5452
6075
  poolId: request.poolId,
5453
6076
  amount: request.amount,
5454
- receiverAddress,
6077
+ chain: request.chain,
6078
+ receiver: {
6079
+ address: receiver.address,
6080
+ type: receiver.accountType
6081
+ },
5455
6082
  signerWalletAddress: signerAccount,
5456
6083
  expiryTimestamp
5457
6084
  };
6085
+ const withdrawSubmissionData = {
6086
+ ...withdrawActionData,
6087
+ receiverAccount: receiver.canisterAccount
6088
+ };
5458
6089
  return {
5459
6090
  kind: WalletActionKind.createWithdraw,
5460
6091
  executionKind: WalletExecutionKind.signMessage,
5461
6092
  actionType: WalletActionKind.createWithdraw,
5462
- transferMode: TransferMode.native,
5463
6093
  account: signerAccount,
5464
6094
  message: createWithdrawAssetMessage(
5465
6095
  {
5466
6096
  pool_id: request.poolId,
5467
6097
  amount: request.amount.toString(),
5468
- account: { type: "External", data: receiverAddress },
6098
+ account: receiver.messageAccount,
5469
6099
  expiry_timestamp: expiryTimestamp
5470
6100
  },
5471
6101
  nonce
5472
6102
  ),
5473
- data: withdrawRequestData,
6103
+ data: withdrawActionData,
5474
6104
  submit: async (signatureInfo) => {
5475
- return await this.submitWithdraw(withdrawRequestData, signatureInfo);
6105
+ return await this.submitWithdraw(
6106
+ withdrawSubmissionData,
6107
+ signatureInfo
6108
+ );
5476
6109
  }
5477
6110
  };
5478
6111
  } catch (error) {
@@ -5489,7 +6122,7 @@ var LendingModule = class {
5489
6122
  {
5490
6123
  data: {
5491
6124
  expiry_timestamp: request.expiryTimestamp,
5492
- account: { External: request.receiverAddress },
6125
+ account: request.receiverAccount,
5493
6126
  pool_id: principal.Principal.fromText(request.poolId),
5494
6127
  amount: request.amount
5495
6128
  },
@@ -5545,16 +6178,13 @@ var LendingModule = class {
5545
6178
  * @returns A signable {@link BorrowAction} with `submit` wired to the canister.
5546
6179
  */
5547
6180
  async prepareBorrow(request) {
5548
- const destinationAccount = request.receiverAddress.trim();
6181
+ const destination = resolveOutflowDestinationInput({
6182
+ receiver: request.receiver,
6183
+ errorMessage: "Borrow requires a custom outflow account"
6184
+ });
5549
6185
  const signerAccount = normalizeEvmAddress(
5550
6186
  request.signerWalletAddress.trim()
5551
6187
  );
5552
- if (!destinationAccount) {
5553
- throw new LiquidiumError(
5554
- LiquidiumErrorCode.VALIDATION_ERROR,
5555
- "Borrow requires a custom outflow account"
5556
- );
5557
- }
5558
6188
  if (!signerAccount) {
5559
6189
  throw new LiquidiumError(
5560
6190
  LiquidiumErrorCode.VALIDATION_ERROR,
@@ -5567,7 +6197,10 @@ var LendingModule = class {
5567
6197
  "Borrow amount must be greater than 0"
5568
6198
  );
5569
6199
  }
5570
- const selectedPool = await this.getPoolById(request.poolId);
6200
+ const selectedPool = await getPoolById(
6201
+ this.canisterContext,
6202
+ request.poolId
6203
+ );
5571
6204
  const selectedAsset = selectedPool.asset;
5572
6205
  const minimumBorrowAmountError = getBorrowAmountMinimumValidationError({
5573
6206
  amount: request.amount,
@@ -5579,41 +6212,49 @@ var LendingModule = class {
5579
6212
  minimumBorrowAmountError.message
5580
6213
  );
5581
6214
  }
5582
- const receiverAddress = normalizeExternalAddress({
5583
- address: destinationAccount,
6215
+ const receiver = parseOutflowDestination({
6216
+ destination,
5584
6217
  asset: selectedAsset,
5585
- chain: selectedPool.chain
6218
+ poolChain: selectedPool.chain,
6219
+ destinationChain: request.chain
5586
6220
  });
5587
6221
  const lendingActor = createLendingActor(this.canisterContext);
5588
6222
  try {
5589
6223
  const expiryTimestamp = computeExpiryTimestampFromNow();
5590
6224
  const nonce = await lendingActor.get_nonce(signerAccount);
5591
- const borrowRequestData = {
6225
+ const borrowActionData = {
5592
6226
  profileId: request.profileId,
5593
6227
  poolId: request.poolId,
5594
6228
  amount: request.amount,
5595
- receiverAddress,
6229
+ chain: request.chain,
6230
+ receiver: {
6231
+ address: receiver.address,
6232
+ type: receiver.accountType
6233
+ },
5596
6234
  signerWalletAddress: signerAccount,
5597
6235
  expiryTimestamp
5598
6236
  };
6237
+ const borrowSubmissionData = {
6238
+ ...borrowActionData,
6239
+ receiverAccount: receiver.canisterAccount
6240
+ };
5599
6241
  return {
5600
6242
  kind: WalletActionKind.createBorrow,
5601
6243
  executionKind: WalletExecutionKind.signMessage,
5602
6244
  actionType: WalletActionKind.createBorrow,
5603
- transferMode: TransferMode.native,
5604
6245
  account: signerAccount,
5605
6246
  message: createBorrowAssetMessage(
5606
6247
  {
5607
6248
  pool_id: request.poolId,
5608
6249
  amount: request.amount.toString(),
5609
- account: { type: "External", data: receiverAddress },
6250
+ account: receiver.messageAccount,
5610
6251
  expiry_timestamp: expiryTimestamp
5611
6252
  },
5612
6253
  nonce
5613
6254
  ),
5614
- data: borrowRequestData,
6255
+ data: borrowActionData,
5615
6256
  submit: async (signatureInfo) => {
5616
- return await this.submitBorrow(borrowRequestData, signatureInfo);
6257
+ return await this.submitBorrow(borrowSubmissionData, signatureInfo);
5617
6258
  }
5618
6259
  };
5619
6260
  } catch (error) {
@@ -5630,7 +6271,7 @@ var LendingModule = class {
5630
6271
  ).borrow_assets(principal.Principal.fromText(request.profileId), {
5631
6272
  data: {
5632
6273
  expiry_timestamp: request.expiryTimestamp,
5633
- account: { External: request.receiverAddress },
6274
+ account: request.receiverAccount,
5634
6275
  pool_id: principal.Principal.fromText(request.poolId),
5635
6276
  amount: request.amount
5636
6277
  },
@@ -5748,11 +6389,18 @@ var LendingModule = class {
5748
6389
  *
5749
6390
  * ETH stablecoin deposit-address estimates are served by the deposit-address
5750
6391
  * canister. BTC estimates include the ckBTC minter deposit fee and ledger fee.
6392
+ * ICP-chain estimates return the corresponding ICRC ledger fee.
5751
6393
  *
5752
6394
  * @param request - Asset and chain pair to estimate for.
5753
6395
  * @returns Total fee estimate rounded up in the asset's base units.
5754
6396
  */
5755
6397
  async estimateInflowFee(request) {
6398
+ if (request.chain === Chain.ICP) {
6399
+ const ledgerFee = await this.estimateIcrcLedgerFee(request);
6400
+ return {
6401
+ totalFee: roundInflowFeeEstimate(request, ledgerFee)
6402
+ };
6403
+ }
5756
6404
  if (isEthStablecoin(request.asset, request.chain)) {
5757
6405
  const result = await createDepositAccountsActor(
5758
6406
  this.canisterContext
@@ -5782,6 +6430,14 @@ var LendingModule = class {
5782
6430
  ]);
5783
6431
  return { totalFee: minterFee + ledgerFee };
5784
6432
  }
6433
+ async estimateIcrcLedgerFee(request) {
6434
+ const route = getInflowFeeLedgerAssetRoute(request);
6435
+ return await createIcrcLedgerActor({
6436
+ canisterContext: this.canisterContext,
6437
+ canisterId: route.ledgerCanisterId,
6438
+ ledgerName: `${request.asset} ${request.chain}`
6439
+ }).icrc1_fee();
6440
+ }
5785
6441
  /**
5786
6442
  * Submits an inflow transaction id for faster indexing.
5787
6443
  *
@@ -5791,6 +6447,12 @@ var LendingModule = class {
5791
6447
  * @returns Acknowledgement including the submitted `txid`.
5792
6448
  */
5793
6449
  async submitInflow(request) {
6450
+ if (request.chain !== void 0 && request.chain !== Chain.BTC && request.chain !== Chain.ETH) {
6451
+ throw new LiquidiumError(
6452
+ LiquidiumErrorCode.VALIDATION_ERROR,
6453
+ `Inflow submission is not supported for ${String(request.chain)}`
6454
+ );
6455
+ }
5794
6456
  const apiClient = this.requireApi();
5795
6457
  const response = await apiClient.post(SdkApiPath.inflow, request);
5796
6458
  return {
@@ -5830,38 +6492,39 @@ var LendingModule = class {
5830
6492
  return new SupplyFlowExecutor({
5831
6493
  canisterContext: this.canisterContext,
5832
6494
  evmReadClient: this.evmReadClient,
5833
- getPoolById: async (poolId) => await this.getPoolById(poolId),
5834
6495
  requireApi: () => {
5835
6496
  this.requireApi();
5836
6497
  },
5837
6498
  submitInflow: async (request) => await this.submitInflow(request)
5838
6499
  });
5839
6500
  }
5840
- async getPoolById(poolId) {
5841
- const pools = await createFlexibleLendingActor(
5842
- this.canisterContext
5843
- ).list_pools();
5844
- const selectedPool = pools.find(
5845
- (pool) => pool.principal.toText() === poolId
6501
+ };
6502
+ function getInflowFeeLedgerAssetRoute(request) {
6503
+ if (request.chain !== Chain.ICP) {
6504
+ throw new LiquidiumError(
6505
+ LiquidiumErrorCode.VALIDATION_ERROR,
6506
+ `IC ledger inflow fees are not supported for ${request.asset} on ${request.chain}`
5846
6507
  );
5847
- const decodedPool = selectedPool ? decodeFlexiblePool(selectedPool) : null;
5848
- if (!decodedPool) {
5849
- throw new LiquidiumError(
5850
- LiquidiumErrorCode.POOL_NOT_FOUND,
5851
- `Pool not found: ${poolId}`
5852
- );
5853
- }
5854
- return decodedPool;
5855
6508
  }
5856
- async normalizeOutflowReceiverAddress(params) {
5857
- const selectedPool = await this.getPoolById(params.poolId);
5858
- return normalizeExternalAddress({
5859
- address: params.receiverAddress,
5860
- asset: selectedPool.asset,
5861
- chain: selectedPool.chain
5862
- });
6509
+ return getPoolLedgerAssetRoute(request);
6510
+ }
6511
+ function resolveOutflowDestinationInput(params) {
6512
+ const receiver = params.receiver;
6513
+ const address = typeof receiver === "string" ? receiver.trim() : receiver.address.trim();
6514
+ if (!address) {
6515
+ throw new LiquidiumError(
6516
+ LiquidiumErrorCode.VALIDATION_ERROR,
6517
+ params.errorMessage
6518
+ );
5863
6519
  }
5864
- };
6520
+ if (typeof receiver === "string") {
6521
+ return address;
6522
+ }
6523
+ return {
6524
+ address,
6525
+ type: receiver.type
6526
+ };
6527
+ }
5865
6528
  function mapExpectedOutflowDetails(details, expectedOutflowType, operation) {
5866
6529
  if (details.outflowType !== expectedOutflowType) {
5867
6530
  throw new LiquidiumError(
@@ -5955,13 +6618,17 @@ function formatPrice(price, decimals) {
5955
6618
 
5956
6619
  // src/modules/market/market.ts
5957
6620
  var ZERO_POOL_RATE = [0n, 0n, 0n];
6621
+ var BACKING_POOL_CHAIN_BY_ASSET = {
6622
+ [Asset.BTC]: Chain.BTC,
6623
+ [Asset.ICP]: Chain.ICP,
6624
+ [Asset.USDC]: Chain.ETH,
6625
+ [Asset.USDT]: Chain.ETH
6626
+ };
5958
6627
  var MarketModule = class {
5959
- constructor(canisterContext, apiClient) {
6628
+ constructor(canisterContext) {
5960
6629
  this.canisterContext = canisterContext;
5961
- this.apiClient = apiClient;
5962
6630
  }
5963
6631
  canisterContext;
5964
- apiClient;
5965
6632
  /**
5966
6633
  * Lists SDK-supported pools with their current rates.
5967
6634
  *
@@ -5970,7 +6637,6 @@ var MarketModule = class {
5970
6637
  * @returns Supported lending pools enriched with their current rate data.
5971
6638
  */
5972
6639
  async listPools() {
5973
- void this.apiClient;
5974
6640
  try {
5975
6641
  const flexibleActor = createFlexibleLendingActor(this.canisterContext);
5976
6642
  const rawPools = await flexibleActor.list_pools();
@@ -6015,15 +6681,26 @@ var MarketModule = class {
6015
6681
  }
6016
6682
  }
6017
6683
  /**
6018
- * Resolves a single pool for the given asset and chain pair.
6684
+ * Resolves a single backing pool for the given Chain + Asset identifier.
6685
+ *
6686
+ * Native and chain-key identifiers share a pool. For example, both
6687
+ * `ETH/USDT` and `ICP/USDT` resolve to the USDT lending pool.
6019
6688
  *
6020
6689
  * @param query - The market asset and chain pair to match.
6021
6690
  * @returns The single pool that matches the requested asset and chain.
6022
6691
  */
6023
6692
  async findPool(query) {
6693
+ const identifier = { chain: query.chain, asset: query.asset };
6694
+ if (!isAssetIdentifier(identifier)) {
6695
+ throw new LiquidiumError(
6696
+ LiquidiumErrorCode.VALIDATION_ERROR,
6697
+ `Unsupported asset identifier: ${identifier.chain}/${identifier.asset}`
6698
+ );
6699
+ }
6024
6700
  const pools = await this.listPools();
6701
+ const backingPoolChain = BACKING_POOL_CHAIN_BY_ASSET[identifier.asset];
6025
6702
  const matchedPools = pools.filter(
6026
- (pool) => pool.asset === query.asset && pool.chain === query.chain
6703
+ (pool) => pool.asset === identifier.asset && pool.chain === backingPoolChain
6027
6704
  );
6028
6705
  if (matchedPools.length === 0) {
6029
6706
  throw new LiquidiumError(
@@ -6412,7 +7089,7 @@ var LiquidiumClient = class {
6412
7089
  this.apiClient,
6413
7090
  this.evmReadClient
6414
7091
  );
6415
- this.market = new MarketModule(this.canisterContext, this.apiClient);
7092
+ this.market = new MarketModule(this.canisterContext);
6416
7093
  this.positions = new PositionsModule(this.canisterContext, this.market);
6417
7094
  this.activities = new ActivitiesModule(
6418
7095
  this.apiClient,
@@ -6453,12 +7130,15 @@ exports.Chain = Chain;
6453
7130
  exports.Environment = Environment;
6454
7131
  exports.EvmSupplyApprovalStrategy = EvmSupplyApprovalStrategy;
6455
7132
  exports.HistoryModule = HistoryModule;
7133
+ exports.InstantLoanCreatedError = InstantLoanCreatedError;
6456
7134
  exports.InstantLoansModule = InstantLoansModule;
6457
7135
  exports.LendingModule = LendingModule;
7136
+ exports.LiquidiumAccountType = LiquidiumAccountType;
6458
7137
  exports.LiquidiumClient = LiquidiumClient;
6459
7138
  exports.LiquidiumError = LiquidiumError;
6460
7139
  exports.LiquidiumErrorCode = LiquidiumErrorCode;
6461
7140
  exports.MIN_BORROW_AMOUNTS_BY_ASSET = MIN_BORROW_AMOUNTS_BY_ASSET;
7141
+ exports.MIN_WITHDRAW_AMOUNTS_BY_ASSET = MIN_WITHDRAW_AMOUNTS_BY_ASSET;
6462
7142
  exports.MarketModule = MarketModule;
6463
7143
  exports.OutflowType = OutflowType;
6464
7144
  exports.PositionsModule = PositionsModule;
@@ -6469,7 +7149,6 @@ exports.RATE_DECIMALS = RATE_DECIMALS;
6469
7149
  exports.RATE_SCALE = RATE_SCALE2;
6470
7150
  exports.SupplyAction = SupplyAction;
6471
7151
  exports.SupplyPlanType = SupplyPlanType;
6472
- exports.TransferMode = TransferMode;
6473
7152
  exports.USDC_CONTRACT_ADDRESS = USDC_CONTRACT_ADDRESS;
6474
7153
  exports.USDT_CONTRACT_ADDRESS = USDT_CONTRACT_ADDRESS;
6475
7154
  exports.WalletActionKind = WalletActionKind;
@@ -6477,7 +7156,9 @@ exports.WalletExecutionKind = WalletExecutionKind;
6477
7156
  exports.createTransferErc20Transaction = createTransferErc20Transaction;
6478
7157
  exports.executeWith = executeWith;
6479
7158
  exports.getMinimumBorrowAmount = getMinimumBorrowAmount;
7159
+ exports.getMinimumWithdrawAmount = getMinimumWithdrawAmount;
6480
7160
  exports.intFromPublicId = intFromPublicId;
7161
+ exports.isAssetIdentifier = isAssetIdentifier;
6481
7162
  exports.publicIdFromInt = publicIdFromInt;
6482
7163
  //# sourceMappingURL=index.cjs.map
6483
7164
  //# sourceMappingURL=index.cjs.map