@liquidium/client 0.7.0 → 0.7.1

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.d.cts CHANGED
@@ -1,8 +1,18 @@
1
1
  import { Identity, HttpAgent } from '@icp-sdk/core/agent';
2
2
  import { PublicClient } from 'viem';
3
3
 
4
- /** Minimal viem-compatible client shape required for SDK EVM read calls. */
5
- type EvmReadClient = Pick<PublicClient, "readContract">;
4
+ /**
5
+ * Minimal viem-compatible client shape required for SDK EVM read calls.
6
+ *
7
+ * When `getCode` is present and `chain` identifies Ethereum mainnet, the SDK
8
+ * uses it for a best-effort native ETH contract-destination check. Provider
9
+ * failures fail open and do not block the outflow.
10
+ */
11
+ interface EvmReadClient {
12
+ readContract: PublicClient["readContract"];
13
+ chain?: PublicClient["chain"];
14
+ getCode?: PublicClient["getCode"];
15
+ }
6
16
  /**
7
17
  * Runtime options for `new LiquidiumClient(config)`.
8
18
  *
@@ -30,11 +40,11 @@ interface LiquidiumClientConfig {
30
40
  fetch?: typeof fetch;
31
41
  /** Per-request timeout for SDK API calls in milliseconds. */
32
42
  timeoutMs?: number;
33
- /** Ethereum RPC URL used for public ERC-20 reads in EVM supply flows. */
43
+ /** Ethereum RPC URL used for best-effort native ETH outflow checks and public ERC-20 reads. */
34
44
  evmRpcUrl?: string;
35
45
  /** Optional headers for RPC providers that authenticate via HTTP headers. */
36
46
  evmRpcHeaders?: Record<string, string>;
37
- /** Existing viem public client or compatible read client for EVM reads. */
47
+ /** Existing viem client; mainnet `chain` and `getCode` enable native ETH outflow checks. */
38
48
  evmPublicClient?: EvmReadClient;
39
49
  }
40
50
  /** Pool canister principal text values grouped by pool asset. */
@@ -2305,6 +2315,7 @@ declare const LiquidiumErrorCode: {
2305
2315
  readonly WITHDRAW_TOO_LOW: "WITHDRAW_TOO_LOW";
2306
2316
  readonly REPAYMENT_EXCEEDS_DEBT: "REPAYMENT_EXCEEDS_DEBT";
2307
2317
  readonly INVALID_ADDRESS: "INVALID_ADDRESS";
2318
+ readonly CONTRACT_DESTINATION_UNSUPPORTED: "CONTRACT_DESTINATION_UNSUPPORTED";
2308
2319
  readonly DEPOSIT_ADDRESS_ERROR: "DEPOSIT_ADDRESS_ERROR";
2309
2320
  readonly NETWORK_ERROR: "NETWORK_ERROR";
2310
2321
  readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
package/dist/index.d.ts CHANGED
@@ -1,8 +1,18 @@
1
1
  import { Identity, HttpAgent } from '@icp-sdk/core/agent';
2
2
  import { PublicClient } from 'viem';
3
3
 
4
- /** Minimal viem-compatible client shape required for SDK EVM read calls. */
5
- type EvmReadClient = Pick<PublicClient, "readContract">;
4
+ /**
5
+ * Minimal viem-compatible client shape required for SDK EVM read calls.
6
+ *
7
+ * When `getCode` is present and `chain` identifies Ethereum mainnet, the SDK
8
+ * uses it for a best-effort native ETH contract-destination check. Provider
9
+ * failures fail open and do not block the outflow.
10
+ */
11
+ interface EvmReadClient {
12
+ readContract: PublicClient["readContract"];
13
+ chain?: PublicClient["chain"];
14
+ getCode?: PublicClient["getCode"];
15
+ }
6
16
  /**
7
17
  * Runtime options for `new LiquidiumClient(config)`.
8
18
  *
@@ -30,11 +40,11 @@ interface LiquidiumClientConfig {
30
40
  fetch?: typeof fetch;
31
41
  /** Per-request timeout for SDK API calls in milliseconds. */
32
42
  timeoutMs?: number;
33
- /** Ethereum RPC URL used for public ERC-20 reads in EVM supply flows. */
43
+ /** Ethereum RPC URL used for best-effort native ETH outflow checks and public ERC-20 reads. */
34
44
  evmRpcUrl?: string;
35
45
  /** Optional headers for RPC providers that authenticate via HTTP headers. */
36
46
  evmRpcHeaders?: Record<string, string>;
37
- /** Existing viem public client or compatible read client for EVM reads. */
47
+ /** Existing viem client; mainnet `chain` and `getCode` enable native ETH outflow checks. */
38
48
  evmPublicClient?: EvmReadClient;
39
49
  }
40
50
  /** Pool canister principal text values grouped by pool asset. */
@@ -2305,6 +2315,7 @@ declare const LiquidiumErrorCode: {
2305
2315
  readonly WITHDRAW_TOO_LOW: "WITHDRAW_TOO_LOW";
2306
2316
  readonly REPAYMENT_EXCEEDS_DEBT: "REPAYMENT_EXCEEDS_DEBT";
2307
2317
  readonly INVALID_ADDRESS: "INVALID_ADDRESS";
2318
+ readonly CONTRACT_DESTINATION_UNSUPPORTED: "CONTRACT_DESTINATION_UNSUPPORTED";
2308
2319
  readonly DEPOSIT_ADDRESS_ERROR: "DEPOSIT_ADDRESS_ERROR";
2309
2320
  readonly NETWORK_ERROR: "NETWORK_ERROR";
2310
2321
  readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ var LiquidiumErrorCode = {
45
45
  WITHDRAW_TOO_LOW: "WITHDRAW_TOO_LOW",
46
46
  REPAYMENT_EXCEEDS_DEBT: "REPAYMENT_EXCEEDS_DEBT",
47
47
  INVALID_ADDRESS: "INVALID_ADDRESS",
48
+ CONTRACT_DESTINATION_UNSUPPORTED: "CONTRACT_DESTINATION_UNSUPPORTED",
48
49
  DEPOSIT_ADDRESS_ERROR: "DEPOSIT_ADDRESS_ERROR",
49
50
  // Transport
50
51
  NETWORK_ERROR: "NETWORK_ERROR",
@@ -1785,6 +1786,11 @@ var HISTORY_ACTIVITIES = `${SDK_API_V2_PATH}/history/activities`;
1785
1786
  var HISTORY_USERS = `${SDK_API_V2_PATH}/history/users`;
1786
1787
  var INFLOW = `${SDK_API_V2_PATH}/inflow`;
1787
1788
  var SIMPLE_LOANS = `${SDK_API_V1_PATH}/instant-loans`;
1789
+ function buildEthereumAddressBytecodePath(request) {
1790
+ return `${SDK_API_V2_PATH}/ethereum/addresses/${encodeURIComponent(
1791
+ request.address
1792
+ )}/bytecode`;
1793
+ }
1788
1794
  function buildHistoryUserTransactionsPath(profileId, query) {
1789
1795
  const base = `${HISTORY_USERS}/${encodeURIComponent(profileId)}/transactions`;
1790
1796
  const qs = query.toString();
@@ -3341,6 +3347,71 @@ function accountTypeToString(accountType) {
3341
3347
  return `Principal:${accountType.data}`;
3342
3348
  }
3343
3349
  }
3350
+ var EMPTY_EVM_BYTECODE = "0x";
3351
+ var LAST_ETHEREUM_MAINNET_PRECOMPILE_ADDRESS = 0x11n;
3352
+ var CONTRACT_DESTINATION_UNSUPPORTED_MESSAGE = "Contract addresses are not supported for native ETH withdrawals or borrowing";
3353
+ var RESERVED_DESTINATION_MESSAGE = "Ethereum outflow destination must not be the zero address or a precompile";
3354
+ async function guardEthereumOutflowDestination({
3355
+ address,
3356
+ apiClient,
3357
+ asset,
3358
+ chain,
3359
+ evmReadClient
3360
+ }) {
3361
+ if (chain !== Chain.ETH) {
3362
+ return;
3363
+ }
3364
+ const normalizedAddress = getAddress(address);
3365
+ if (BigInt(normalizedAddress) <= LAST_ETHEREUM_MAINNET_PRECOMPILE_ADDRESS) {
3366
+ throw new LiquidiumError(
3367
+ LiquidiumErrorCode.INVALID_ADDRESS,
3368
+ RESERVED_DESTINATION_MESSAGE
3369
+ );
3370
+ }
3371
+ if (asset !== Asset.ETH) {
3372
+ return;
3373
+ }
3374
+ if (evmReadClient?.chain?.id === mainnet.id && evmReadClient.getCode) {
3375
+ let bytecode;
3376
+ try {
3377
+ bytecode = await evmReadClient.getCode({
3378
+ address: normalizedAddress
3379
+ });
3380
+ } catch {
3381
+ return;
3382
+ }
3383
+ if (bytecode === void 0 || bytecode === EMPTY_EVM_BYTECODE) {
3384
+ return;
3385
+ }
3386
+ if (typeof bytecode !== "string" || !/^0x(?:[0-9a-fA-F]{2})*$/.test(bytecode)) {
3387
+ return;
3388
+ }
3389
+ throw new LiquidiumError(
3390
+ LiquidiumErrorCode.CONTRACT_DESTINATION_UNSUPPORTED,
3391
+ CONTRACT_DESTINATION_UNSUPPORTED_MESSAGE
3392
+ );
3393
+ }
3394
+ if (!apiClient) {
3395
+ return;
3396
+ }
3397
+ let response;
3398
+ try {
3399
+ response = await apiClient.get(
3400
+ buildEthereumAddressBytecodePath({ address: normalizedAddress })
3401
+ );
3402
+ } catch {
3403
+ return;
3404
+ }
3405
+ if (!response || typeof response !== "object" || !("hasDeployedBytecode" in response) || typeof response.hasDeployedBytecode !== "boolean") {
3406
+ return;
3407
+ }
3408
+ if (response.hasDeployedBytecode) {
3409
+ throw new LiquidiumError(
3410
+ LiquidiumErrorCode.CONTRACT_DESTINATION_UNSUPPORTED,
3411
+ CONTRACT_DESTINATION_UNSUPPORTED_MESSAGE
3412
+ );
3413
+ }
3414
+ }
3344
3415
 
3345
3416
  // src/core/pool-ledger-assets.ts
3346
3417
  function getPoolLedgerAssetRoute(params) {
@@ -4723,6 +4794,13 @@ var LendingModule = class {
4723
4794
  poolChain: selectedPool.chain,
4724
4795
  destinationChain: request.chain
4725
4796
  });
4797
+ await guardEthereumOutflowDestination({
4798
+ address: receiver.address,
4799
+ apiClient: this.apiClient,
4800
+ asset: selectedAsset,
4801
+ chain: request.chain,
4802
+ evmReadClient: this.evmReadClient
4803
+ });
4726
4804
  const lendingActor = createLendingActor(this.canisterContext);
4727
4805
  try {
4728
4806
  const expiryTimestamp = computeExpiryTimestampFromNow();
@@ -4741,7 +4819,9 @@ var LendingModule = class {
4741
4819
  };
4742
4820
  const withdrawSubmissionData = {
4743
4821
  ...withdrawActionData,
4744
- receiverAccount: receiver.canisterAccount
4822
+ asset: selectedAsset,
4823
+ receiverAccount: receiver.canisterAccount,
4824
+ receiverAddress: receiver.address
4745
4825
  };
4746
4826
  return {
4747
4827
  kind: WalletActionKind.createWithdraw,
@@ -4773,6 +4853,13 @@ var LendingModule = class {
4773
4853
  }
4774
4854
  }
4775
4855
  async submitWithdraw(request, signatureInfo) {
4856
+ await guardEthereumOutflowDestination({
4857
+ address: request.receiverAddress,
4858
+ apiClient: this.apiClient,
4859
+ asset: request.asset,
4860
+ chain: request.chain,
4861
+ evmReadClient: this.evmReadClient
4862
+ });
4776
4863
  try {
4777
4864
  const result = await createLendingActor(this.canisterContext).withdraw(
4778
4865
  Principal.fromText(request.profileId),
@@ -4877,6 +4964,13 @@ var LendingModule = class {
4877
4964
  poolChain: selectedPool.chain,
4878
4965
  destinationChain: request.chain
4879
4966
  });
4967
+ await guardEthereumOutflowDestination({
4968
+ address: receiver.address,
4969
+ apiClient: this.apiClient,
4970
+ asset: selectedAsset,
4971
+ chain: request.chain,
4972
+ evmReadClient: this.evmReadClient
4973
+ });
4880
4974
  await this.guardBorrowSameAssetPolicy({
4881
4975
  profileId: request.profileId,
4882
4976
  pool: selectedPool
@@ -4899,7 +4993,9 @@ var LendingModule = class {
4899
4993
  };
4900
4994
  const borrowSubmissionData = {
4901
4995
  ...borrowActionData,
4902
- receiverAccount: receiver.canisterAccount
4996
+ asset: selectedAsset,
4997
+ receiverAccount: receiver.canisterAccount,
4998
+ receiverAddress: receiver.address
4903
4999
  };
4904
5000
  return {
4905
5001
  kind: WalletActionKind.createBorrow,
@@ -4928,6 +5024,13 @@ var LendingModule = class {
4928
5024
  }
4929
5025
  }
4930
5026
  async submitBorrow(request, signatureInfo) {
5027
+ await guardEthereumOutflowDestination({
5028
+ address: request.receiverAddress,
5029
+ apiClient: this.apiClient,
5030
+ asset: request.asset,
5031
+ chain: request.chain,
5032
+ evmReadClient: this.evmReadClient
5033
+ });
4931
5034
  try {
4932
5035
  const result = await createLendingActor(
4933
5036
  this.canisterContext