@liquidium/client 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,6 +62,8 @@ Amounts use `bigint` values in each asset's smallest unit. Read pool decimals be
62
62
 
63
63
  `Asset.ETH` supports native ETH on `Chain.ETH` and ckETH ICRC transfers on `Chain.ICP`. Both routes use 18 decimals, so amounts are expressed in wei (`0.005 ETH` is `5_000_000_000_000_000n`). For native ETH deposits and repayments, select `mechanism: "transfer"` to send ETH to a generated deposit address or `mechanism: "contractInteraction"` to send ETH through the payable deposit helper. Transfer is the default when `mechanism` is omitted. ckETH routes use the generated ICRC target.
64
64
 
65
+ Native ETH borrows and withdrawals do not support addresses with deployed contract bytecode, including smart contract wallets. The same restriction applies to Simple Loan borrow destinations and native ETH collateral refund destinations.
66
+
65
67
  See the [quick start](https://liquidium-inc.github.io/liquidium-sdk/getting-started/quick-start/) for LTV validation, repayment, and recovery.
66
68
 
67
69
  ## Examples
package/dist/index.cjs CHANGED
@@ -47,6 +47,7 @@ var LiquidiumErrorCode = {
47
47
  WITHDRAW_TOO_LOW: "WITHDRAW_TOO_LOW",
48
48
  REPAYMENT_EXCEEDS_DEBT: "REPAYMENT_EXCEEDS_DEBT",
49
49
  INVALID_ADDRESS: "INVALID_ADDRESS",
50
+ CONTRACT_DESTINATION_UNSUPPORTED: "CONTRACT_DESTINATION_UNSUPPORTED",
50
51
  DEPOSIT_ADDRESS_ERROR: "DEPOSIT_ADDRESS_ERROR",
51
52
  // Transport
52
53
  NETWORK_ERROR: "NETWORK_ERROR",
@@ -320,6 +321,12 @@ function appendErrorContext(message, context) {
320
321
  return `${message} (${details.join(", ")})`;
321
322
  }
322
323
  function createCanisterContext(opts) {
324
+ if (opts.agent) {
325
+ return {
326
+ agent: opts.agent,
327
+ canisterIds: opts.canisterIds
328
+ };
329
+ }
323
330
  const host = resolveHost(opts.icHost);
324
331
  const agent$1 = agent.HttpAgent.createSync({
325
332
  host,
@@ -1787,6 +1794,11 @@ var HISTORY_ACTIVITIES = `${SDK_API_V2_PATH}/history/activities`;
1787
1794
  var HISTORY_USERS = `${SDK_API_V2_PATH}/history/users`;
1788
1795
  var INFLOW = `${SDK_API_V2_PATH}/inflow`;
1789
1796
  var SIMPLE_LOANS = `${SDK_API_V1_PATH}/instant-loans`;
1797
+ function buildEthereumAddressBytecodePath(request) {
1798
+ return `${SDK_API_V2_PATH}/ethereum/addresses/${encodeURIComponent(
1799
+ request.address
1800
+ )}/bytecode`;
1801
+ }
1790
1802
  function buildHistoryUserTransactionsPath(profileId, query) {
1791
1803
  const base = `${HISTORY_USERS}/${encodeURIComponent(profileId)}/transactions`;
1792
1804
  const qs = query.toString();
@@ -3343,6 +3355,71 @@ function accountTypeToString(accountType) {
3343
3355
  return `Principal:${accountType.data}`;
3344
3356
  }
3345
3357
  }
3358
+ var EMPTY_EVM_BYTECODE = "0x";
3359
+ var LAST_ETHEREUM_MAINNET_PRECOMPILE_ADDRESS = 0x11n;
3360
+ var CONTRACT_DESTINATION_UNSUPPORTED_MESSAGE = "Contract addresses are not supported for native ETH withdrawals or borrowing";
3361
+ var RESERVED_DESTINATION_MESSAGE = "Ethereum outflow destination must not be the zero address or a precompile";
3362
+ async function guardEthereumOutflowDestination({
3363
+ address,
3364
+ apiClient,
3365
+ asset,
3366
+ chain,
3367
+ evmReadClient
3368
+ }) {
3369
+ if (chain !== Chain.ETH) {
3370
+ return;
3371
+ }
3372
+ const normalizedAddress = viem.getAddress(address);
3373
+ if (BigInt(normalizedAddress) <= LAST_ETHEREUM_MAINNET_PRECOMPILE_ADDRESS) {
3374
+ throw new LiquidiumError(
3375
+ LiquidiumErrorCode.INVALID_ADDRESS,
3376
+ RESERVED_DESTINATION_MESSAGE
3377
+ );
3378
+ }
3379
+ if (asset !== Asset.ETH) {
3380
+ return;
3381
+ }
3382
+ if (evmReadClient?.chain?.id === chains.mainnet.id && evmReadClient.getCode) {
3383
+ let bytecode;
3384
+ try {
3385
+ bytecode = await evmReadClient.getCode({
3386
+ address: normalizedAddress
3387
+ });
3388
+ } catch {
3389
+ return;
3390
+ }
3391
+ if (bytecode === void 0 || bytecode === EMPTY_EVM_BYTECODE) {
3392
+ return;
3393
+ }
3394
+ if (typeof bytecode !== "string" || !/^0x(?:[0-9a-fA-F]{2})*$/.test(bytecode)) {
3395
+ return;
3396
+ }
3397
+ throw new LiquidiumError(
3398
+ LiquidiumErrorCode.CONTRACT_DESTINATION_UNSUPPORTED,
3399
+ CONTRACT_DESTINATION_UNSUPPORTED_MESSAGE
3400
+ );
3401
+ }
3402
+ if (!apiClient) {
3403
+ return;
3404
+ }
3405
+ let response;
3406
+ try {
3407
+ response = await apiClient.get(
3408
+ buildEthereumAddressBytecodePath({ address: normalizedAddress })
3409
+ );
3410
+ } catch {
3411
+ return;
3412
+ }
3413
+ if (!response || typeof response !== "object" || !("hasDeployedBytecode" in response) || typeof response.hasDeployedBytecode !== "boolean") {
3414
+ return;
3415
+ }
3416
+ if (response.hasDeployedBytecode) {
3417
+ throw new LiquidiumError(
3418
+ LiquidiumErrorCode.CONTRACT_DESTINATION_UNSUPPORTED,
3419
+ CONTRACT_DESTINATION_UNSUPPORTED_MESSAGE
3420
+ );
3421
+ }
3422
+ }
3346
3423
 
3347
3424
  // src/core/pool-ledger-assets.ts
3348
3425
  function getPoolLedgerAssetRoute(params) {
@@ -4725,6 +4802,13 @@ var LendingModule = class {
4725
4802
  poolChain: selectedPool.chain,
4726
4803
  destinationChain: request.chain
4727
4804
  });
4805
+ await guardEthereumOutflowDestination({
4806
+ address: receiver.address,
4807
+ apiClient: this.apiClient,
4808
+ asset: selectedAsset,
4809
+ chain: request.chain,
4810
+ evmReadClient: this.evmReadClient
4811
+ });
4728
4812
  const lendingActor = createLendingActor(this.canisterContext);
4729
4813
  try {
4730
4814
  const expiryTimestamp = computeExpiryTimestampFromNow();
@@ -4743,7 +4827,9 @@ var LendingModule = class {
4743
4827
  };
4744
4828
  const withdrawSubmissionData = {
4745
4829
  ...withdrawActionData,
4746
- receiverAccount: receiver.canisterAccount
4830
+ asset: selectedAsset,
4831
+ receiverAccount: receiver.canisterAccount,
4832
+ receiverAddress: receiver.address
4747
4833
  };
4748
4834
  return {
4749
4835
  kind: WalletActionKind.createWithdraw,
@@ -4775,6 +4861,13 @@ var LendingModule = class {
4775
4861
  }
4776
4862
  }
4777
4863
  async submitWithdraw(request, signatureInfo) {
4864
+ await guardEthereumOutflowDestination({
4865
+ address: request.receiverAddress,
4866
+ apiClient: this.apiClient,
4867
+ asset: request.asset,
4868
+ chain: request.chain,
4869
+ evmReadClient: this.evmReadClient
4870
+ });
4778
4871
  try {
4779
4872
  const result = await createLendingActor(this.canisterContext).withdraw(
4780
4873
  principal.Principal.fromText(request.profileId),
@@ -4879,6 +4972,13 @@ var LendingModule = class {
4879
4972
  poolChain: selectedPool.chain,
4880
4973
  destinationChain: request.chain
4881
4974
  });
4975
+ await guardEthereumOutflowDestination({
4976
+ address: receiver.address,
4977
+ apiClient: this.apiClient,
4978
+ asset: selectedAsset,
4979
+ chain: request.chain,
4980
+ evmReadClient: this.evmReadClient
4981
+ });
4882
4982
  await this.guardBorrowSameAssetPolicy({
4883
4983
  profileId: request.profileId,
4884
4984
  pool: selectedPool
@@ -4901,7 +5001,9 @@ var LendingModule = class {
4901
5001
  };
4902
5002
  const borrowSubmissionData = {
4903
5003
  ...borrowActionData,
4904
- receiverAccount: receiver.canisterAccount
5004
+ asset: selectedAsset,
5005
+ receiverAccount: receiver.canisterAccount,
5006
+ receiverAddress: receiver.address
4905
5007
  };
4906
5008
  return {
4907
5009
  kind: WalletActionKind.createBorrow,
@@ -4930,6 +5032,13 @@ var LendingModule = class {
4930
5032
  }
4931
5033
  }
4932
5034
  async submitBorrow(request, signatureInfo) {
5035
+ await guardEthereumOutflowDestination({
5036
+ address: request.receiverAddress,
5037
+ apiClient: this.apiClient,
5038
+ asset: request.asset,
5039
+ chain: request.chain,
5040
+ evmReadClient: this.evmReadClient
5041
+ });
4933
5042
  try {
4934
5043
  const result = await createLendingActor(
4935
5044
  this.canisterContext
@@ -7622,6 +7731,7 @@ var LiquidiumClient = class {
7622
7731
  const canisterIds = resolveCanisterIds(environment, config.canisterIds);
7623
7732
  const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
7624
7733
  this.canisterContext = createCanisterContext({
7734
+ agent: config.agent,
7625
7735
  icHost: config.icHost,
7626
7736
  identity: config.identity,
7627
7737
  canisterIds