Skip to main content
Version: v2

Asset deposits and withdrawals

Applications may want to enable users to:

  • Deposit assets on L1 to L2 (note these pre-requisites)
  • Withdraw assets from L2 to L1

For more information and use cases, see our explanatory article, Deep dive into deposits and withdrawals.

Deposit pre-requisites:​

Typescript SDK​

1. Initialize the SDK​

In order to use the SDK, you need to initialize it.

2. Generate signers​

Enabling users to transfer assets requires a user's signature, so your application will need to create signers. See the guide on how to generate signers. Then setup GenericIMXProvider

3. Deposit assets onto L2​

📚SDK reference

Request​

The function below will deposit ETH from L1 to L2. If you wish to deposit a different asset, you can use different TokenAmount to represent ETH, ERC20 or ERC721.

(async (): Promise<void> => {
// Create deposit
const deposit = await imxProvider.deposit({
type: 'ETH', // There are three avaible types: ETH, ERC20, ERC721
amount: '50000000000000000', // Amount in wei, this currently is 0.05 ETH
});
console.log(deposit);
})().catch((e) => {
// implement error handling here
});

Example response​

{
type: 2, // Transaction type, currently 2 means EIP-1559
chainId: 11155111, // The Chain ID of the network, 11155111 is the Chain ID for Sepolia.
nonce: 5, // The nonce of the transaction used
maxPriorityFeePerGas: BigNumber { _hex: '0x59682f00', _isBigNumber: true }, // Maximum fee to bribe miners into giving this transaction priority
maxFeePerGas: BigNumber { _hex: '0x5968cf94', _isBigNumber: true }, // The maximum fee per gas that the sender is willing to pay
gasPrice: null, // The gas price of the transaction determined by the network
gasLimit: BigNumber { _hex: '0x013617', _isBigNumber: true }, // The maximum amount of gas that the transaction can use
to: '0x7917eDb51ecD6CdB3F9854c3cc593F33de10c623', // The address of the recipient
value: BigNumber { _hex: '0xb1a2bc2ec50000', _isBigNumber: true }, // The amount of ETH to send in wei
data: '0x00aeef8a02f9a87a2eae83e024312ea33e2bec5df10cdef1470f6aaf5d5adb23f735a60002705737cd248ac819034b5de474c8f0368224f72a0fda9e031499d519992d9e000000000000000000000000000000000000000000000000000000000001ed7a', // Data included in this log
accessList: [], // Optional list of addresses and storage keys the transaction can access
hash: '0x357d6e1c09cf09b5746279bb77377b2353deb1c538eb18c8c9fa9e2363aa3d58', // The hash of the transaction
v: 0, // The recovery ID of the ECDSA signature
r: '0xc02c07ff0f80405f34917c9e300abff5da7dda319828fe1843a06f041cd3cbf5', // The first 32 bytes of the ECDSA signature
s: '0x5c08b76a9ab9f766b82e6f273a1503d064d1604b8e071d7267a6735c5900bb1c', // The second 32 bytes of the ECDSA signature
from: '0xF656956cA54778056f1191791876db54Aa6eb61B', // The address of the sender
confirmations: 0, // The number of confirmations that the transaction has received
wait: [Function (anonymous)] // A function that returns a promise that resolves when the transaction is mined
}

4. Withdraw assets to L1​

API​

Deposit​

To understand what is going on under the hood with asset deposits, please see this explainer.

Steps:​

  1. Get details of the deposit
  2. Generate signers
  3. Call contract method to deposit tokens

1. Get details of the deposit​

💡Endpoint:

Javascript example of depositing ETH from L1 to L2:

import fetch from 'node-fetch';

const depositDetails = {
amount: '0.001',
token: {
type: 'ETH',
data: {
decimals: 18,
},
},
user: '0x..', // Public L1 Ethereum address
};

fetch('https://api.sandbox.x.immutable.com/v1/signable-deposit-details', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(depositDetails),
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.error(err);
});

Example response:

{
"amount": "string", // Amount this user is depositing
"asset_id": "string", // ID of the asset this user is depositing (applicable only to depositing ERC721)
"nonce": 0, // Random number generated of this transaction to verify that specific values are not reused
"stark_key": "string", // Public stark key of the depositing user
"vault_id": 0 // ID of the vault this user is depositing to
}

The following response params are used in the depositEth contract method (see Step 3):

  • stark_key
  • vault_id

2. Generate signers​

Enabling users to deposit assets requires a user's signature, so your application will need to create signers. See the guide on how to generate signers.

3. Call contract method to deposit tokens​

import { Wallet } from "ethers";
import { x, config } from "@imtbl/sdk";

const {
Environment,
} = config;

const {
Contracts,
IMXClient,
imxClientConfig, // helper method to create a client config
} = x;

// Create Ethereum Signer
const ethSigner = new Wallet('YOUR_PRIVATE_ETH_KEY');

// Initialize client
const environment = Environment.SANDBOX; // or Environment.PRODUCTION
const client = new IMXClient(imxClientConfig({ environment }));;
const coreContractAddress = client.imxConfig.immutableXConfig.ethConfiguration.coreContractAddress;

// Get instance of core contract
const contract = Contracts.Core.connect(
coreContractAddress,
ethSigner
);

// Populate and send transaction
const populatedTransaction = await contract.populateTransaction.depositEth(
starkPublicKey, // Use `stark_key` obtained in previous step
assetType, // "ETH", "ERC20" or "ERC721"
vaultId // Use `vault_id` obtained in previous step
);

const transactionResponse = await ethSigner.sendTransaction(
populatedTransaction
);

Withdrawal​

To understand what is going on under the hood with asset withdrawals, please see this explainer.

Steps:​

  1. Get details of the withdrawal
  2. Generate signers
  3. Create withdrawal
  4. Call contract to complete withdrawal

1. Get details of the withdrawal​

Javascript example of withdrawing ETH from L2 to L1:

import fetch from 'node-fetch';

const withdrawalDetails = {
amount: '0.001',
token: {
type: 'ETH',
data: {
decimals: 18,
},
},
user: '0x..', // Public L1 Ethereum address
};

fetch('https://api.sandbox.x.immutable.com/v1/signable-withdrawal-details', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(withdrawalDetails),
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.error(err);
});

Example response:

{
"amount": "string",
"asset_id": "string",
"nonce": 0,
"payload_hash": "string",
"signable_message": "string",
"stark_key": "string",
"vault_id": 0
}

Explanation:

Response paramDescriptionHow is it used in the createWithdrawal request?
(See Step 3)
amountAmount of token to be withdrawn to L1As amount in the request body
asset_idID of the asset this user is withdrawing (applicable only to ERC721 asset type)As asset_id in the request body
nonceRandom number generated of this transaction to verify that specific values are not reusedAs nonce in the request body
payload_hashEncoded payload hashUsed to generate the stark_signature in the request body by using the Stark (L2) signer to sign the payload_hash.
signable_messageMessage to sign with L1 wallet to verity withdrawal requestUsed to generate the x-imx-eth-signature header by using the Ethereum (L1) signer to sign the signable_message
stark_keyPublic stark key of the withdrawing userAs stark_key in the request body
vault_idThe ID of the vault the asset belong toAs vault_id in the request body

2. Generate signers​

Enabling users to withdraw assets requires a user's signature, so your application will need to create signers. See the guide on how to generate signers.

3. Create withdrawal​

💡Endpoint:

Javascript example of withdrawing ETH from L2 to L1:

import fetch from 'node-fetch';

// See previous step for how to generate signers
const ethSignature = ethSigner.sign(signable_message);
const starkSignature = starkSigner.sign(payload_hash);

const withdrawalBody = {
amount: 'string',
asset_id: 'string',
nonce: 'string',
stark_key: 'string',
stark_signature: starkSignature,
vauld_id: 'string',
};

fetch('https://api.sandbox.x.immutable.com/v1/withdrawals', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-imx-eth-address': '0x..', // Public Ethereum address of the withdrawing user
'x-imx-eth-signature': ethSignature,
},
body: JSON.stringify(withdrawalBody),
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.error(err);
});

4. Get the assetType value​

This value is required in the next step to complete the withdrawal.

import fetch from 'node-fetch';

const assetType = 'asset'; // "mintable-asset" if you are withdrawing an ERC721 token that was minted on L2 to L1 for the first time

const encodeAssetBody = {
token: {
data: {
blueprint: 'string',
id: 'string',
token_address: 'string',
token_id: 'string',
},
type: 'ETH', // Or "ERC20" or "ERC721"
},
};

fetch(`https://api.sandbox.x.immutable.com/v1/encode/${assetType}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-imx-eth-address': '0x..', // Public Ethereum address of the withdrawing user
'x-imx-eth-signature': ethSignature,
},
body: JSON.stringify(encodeAssetBody),
})
.then((response) => {
console.log(response);

// Example response
// {
// "asset_id": "string",
// "asset_type": "string"
// }
})
.catch((err) => {
console.error(err);
});

5. Call contract to complete withdrawal​

import { x, config } from "@imtbl/sdk";
import { Wallet } from "ethers";

const {
Environment,
} = config;

const {
Contracts,
IMXClient,
imxClientConfig, // helper method to create a client config
} = x;

// Create Ethereum Signer
const ethSigner = new Wallet('YOUR_PRIVATE_ETH_KEY');

// Initialize client
const environment = Environment.SANDBOX; // or Environment.PRODUCTION
const client = new IMXClient(imxClientConfig({ environment }));;
const coreContractAddress = client.imxConfig.immutableXConfig.ethConfiguration.coreContractAddress;

// Get instance of core contract
const contract = Contracts.Core.connect(
coreContractAddress,
ethSigner
);

// Get instance of core contract
const contract = Contracts.Core.connect(coreContractAddress, ethSigner);

// Populate and send transaction
const populatedTransaction = await contract.populateTransaction.withdraw(
starkPublicKey, // Use `stark_key` obtained in previous step
assetType // Use the `asset_type` value returned in the response object in step 4
);

const transactionResponse = await signer.sendTransaction(
populatedTransaction
);