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