Ethereal Docs
  • Introduction
    • What is Ethereal?
  • Public Testnet
  • Trading
    • Perpetual Futures
      • Margining
      • Order Types
      • Positions
      • Funding Rates
      • Market Specifications
      • Liquidations
      • Auto Deleveraging
      • Subaccounts
      • Price Oracles
  • Points
    • Staking sENA
    • Season Zero
  • developer-guides
    • Trading API
      • Quick Start
      • Message Signing
      • Accounts & Signers
      • Order Placement
      • System Limits
      • Products
      • Token Transfers
      • Supported Tokens
      • Websocket Gateway
    • Python SDK
  • Protocol Reference
    • API Hosts
    • Contracts
    • Audits
Powered by GitBook
On this page
  • Overview
  • How Do I Deposit?
  • Depositing USDe
  • Initiate Token Withdrawals
  • Retrieve Transfer History
  • Token Transfer Fees
  1. developer-guides
  2. Trading API

Token Transfers

Overview

Token transfers encompass the management and tracking of supported tokens moving between your wallet and subaccounts on the Ethereal exchange. These movements primarily fall into two categories: deposits and withdrawals.

How Do I Deposit?

Deposits allow you to move tokens from your wallet into a specified subaccount on the Ethereal exchange. The process follows these steps:

  1. Token approval: ERC20 token approval to allow Ethereal to transfer tokens on your behalf

  2. Transact: Execute the deposit transaction, specifying the token, amount, and target subaccount

  3. Pending: Once deposited, tokens enter a pending state while confirmation occurs

  4. Confirmation: The deposit is synchronised with offchain systems

  5. Availability: After confirmation, funds become available for trading

Token deposits to a fresh subaccount will also automatically create a subaccount. See subaccounts and signers section to learn more.

Default gas estimation can occasionally be too low, where explicit setting the gas limit for the transaction will be needed. If you encounter the DelegationFailed() error when depositing, try adding gas: 250_000n to the viem deposit call.

Here's an example of how you could deposit using viem in TypeScript:

import {
  defineChain,
  erc20Abi,
  toHex,
  parseUnits,
  createWalletClient,
  http,
  createPublicClient,
  getAddress,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { exchangeContractAbi } from './sample/exchange.abi';

export const ETHEREAL_TESTNET_CHAIN = defineChain({
  id: 657468,
  name: 'Ethereal Testnet',
  nativeCurrency: {
    decimals: 18,
    name: 'USDe',
    symbol: 'USDe',
  },
  rpcUrls: {
    default: {
      http: ['https://rpc.etherealtest.net'],
      webSocket: ['wss://rpc.etherealtest.net'],
    },
  },
  testnet: true,
});

const DEFAULT_SUBACCOUNT = toHex('primary', { size: 32 });

// @see: https://explorer.etherealtest.net/address/0xa1623E0AA40B142Cf755938b325321fB2c61Cf05
const USDE_ADDRESS = '0xa1623E0AA40B142Cf755938b325321fB2c61Cf05';

const MAKER_MAKER_PK = '0x...'; // Your test private key

// `verifyingContract` can be found via `HTTP GET /v1/rpc/config`
const exchangeContract = getAddress(domain.verifyingContract);

const wallet = createWalletClient({
  account: privateKeyToAccount(MAKER_MAKER_PK),
  chain: ETHEREAL_TESTNET_CHAIN,
  transport: http(),
});
const publicClient = createPublicClient({ chain: ETHEREAL_TESTNET_CHAIN, transport: http() });

const deposit = async (amount: string) => {
  const nativeAmount = parseUnits(amount, 18); // USDe has 18 deciamls.
  const approveHash = await wallet.writeContract({
    address: USDE_ADDRESS,
    abi: erc20Abi,
    functionName: 'approve',
    args: [exchangeContract, nativeAmount],
  });
  await publicClient.waitForTransactionReceipt({ hash: approveHash });
  const hash = await wallet.writeContract({
    address: exchangeContract,
    abi: exchangeContractAbi,
    functionName: 'deposit',
    args: [DEFAULT_SUBACCOUNT, USDE_ADDRESS, nativeAmount, toHex('refCode', { size: 32 })],
  });
  await publicClient.waitForTransactionReceipt({ hash });
  console.log('Deposited!');
};

deposit('1000') // Deposit 1000 USDe

Once your deposit is confirmed, you can view your subaccount balance by calling:

curl -X 'GET' \
  'https://api.etherealtest.net/v1/subaccount/balance?subaccountId=45783bec-4675-4116-8829-f277afe063d7' \
  -H 'accept: application/json'
{
  "hasNext": false,
  "data": [
    {
      "subaccountId": "45783bec-4675-4116-8829-f277afe063d7",
      "tokenId": "ff761c8b-6248-4673-b63d-d1f980551959",
      "tokenAddress": "0xa1623E0AA40B142Cf755938b325321fB2c61Cf05",
      "tokenName": "USD",
      "amount": "35.644112906",
      "available": "12005.889199245",
      "totalUsed": "326.590624473",
      "updatedAt": 1743851518800
    }
  ]
}

Each token in your subaccount has two balance components:

  • Available (free): Tokens that can be used to open new positions or withdraw

  • TotalUsed (locked): Tokens currently allocated as margin for open positions and resting orders

Visit supported tokens and subaccounts and signers to learn more.

Important to raise that the trading API does NOT allow users to withdraw unrealized profits. Any unrealized profits must be realized before they become available margin. Refer to margin health to learn more about initial and maintenance margin.

Depositing USDe

Ethereal is designed to support any approved ERC20 token, though USDe will be the only depositable token available at launch. Since USDe serves as the native gas token on the Ethereal appchain, it requires an additional wrapping step before deposit.

Users must first wrap their USDe to WUSDe using our immutable wrapping contract, which implements the same interface as WETH for familiar functionality. The WUSDe contract address can be retrieved by querying the USD endpoint in our API, and once wrapped, the tokens can be deposited normally into the platform.

curl -X 'GET' \
  'https://api.etherealtest.net/v1/token?depositEnabled=true&withdrawEnabled=true&orderBy=createdAt' \
  -H 'accept: application/json'
{
  "hasNext": false,
  "data": [
    {
      "id": "049dbd1a-bc21-4219-8264-886b64c0c046",
      "address": "0x7e3203241340C579d6f5061419E9d352Eff1d9F2",
      "name": "USD",
      "erc20Name": "Wrapped USDe",
      "erc20Symbol": "WUSDe",
      "erc20Decimals": 18,
      "depositEnabled": true,
      "withdrawEnabled": true,
      "depositFee": "0",
      "withdrawFee": "1",
      "minDeposit": "10",
      "createdAt": 1750401257026
    }
  ]
}

However, this process can be cumbersome, so we recommend using the provided depositUsd convenience method instead, which handles the wrapping and deposit process automatically.

const exchange = getContract({
  address: exchangeAddress,
  abi: exchangeGatewayAbi,
  client: { public: publicClient, wallet },
});

const refCode = toHex("refcode", { size: 32 });
const hash = await exchange.write.depositUsd([this.subaccountName, refCode], {
  value: 1e18, // 1 USDe
});
await publicClient.waitForTransactionReceipt({ hash });

Initiate Token Withdrawals

Withdrawals move tokens from your subaccount back to your wallet. The process follows these steps:

  1. Initiation: Withdrawals begin with a request through the trading API

  2. Deduction: Once request is processed, funds are immediately deducted from the subaccount

  3. Onchain relay: The initiate withdrawal request is relayed onchain

  4. Pending period: Withdrawals enter a pending state with a mandatory lockout period

  5. Finalize: Users can claim their withdrawal after the lockout period expires

The withdrawal lockout period is a configurable parameter and currently set to 15 seconds.

To initiate a token withdrawal:

curl -X 'POST' \
  'https://api.etherealtest.net/v1/token/ff761c8b-6248-4673-b63d-d1f980551959/withdraw' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "signature": "string",
  "data": {
    "account": "<address_of_account>",
    "subaccount": "<bytes32_subaccount_name>",
    "token": "<token_address_to_withdraw>",
    "amount": "<amount_to_withdrawl_decimal>",
    "nonce": "<nonce_in_nanoseconds_as_string>",
    "signedAt": <timestamp_in_seconds>
  }
}'

You can query any pending or past withdrawals by:

curl -X 'GET' \
  'https://api.etherealtest.net/v1/token/withdraw?subaccountId=45783bec-4675-4116-8829-f277afe063d7&orderBy=createdAt' \
  -H 'accept: application/json'
{
  "hasNext": false,
  "data": [
    {
      "id": "34510374-0010-4630-9954-46bb196806b2",
      "initiatedBlockNumber": "11272338",
      "finalizedBlockNumber": "11272403",
      "status": "COMPLETED",
      "subaccount": "0x7072696d61727900000000000000000000000000000000000000000000000000",
      "token": "0xa1623E0AA40B142Cf755938b325321fB2c61Cf05",
      "amount": "9",
      "isReady": false,
      "createdAt": 1743812502148
    }
  ]
}

Once relayed onchain and after the lockout period has past, users can finalize their withdrawal:

/// @notice Actions pending withdraw for a subaccount.
/// @param account Address of the account to perform on behalf of
/// @param subaccount bytes32 encoded string of the subaccount
function finalizeWithdraw(address account, bytes32 subaccount) external;

There can only ever be one pending withdrawal at any time. The previous withdrawal must be finalized before initiating a new.

Retrieve Transfer History

To view all historical transfers for a specific subaccount, query:

curl -X 'GET' \
  'https://api.etherealtest.net/v1/token/transfer?subaccountId=45783bec-4675-4116-8829-f277afe063d7&statuses=COMPLETED&orderBy=createdAt' \
  -H 'accept: application/json'
{
  "hasNext": false,
  "data": [
    {
      "id": "34510374-0010-4630-9954-46bb196806b2",
      "initiatedBlockNumber": "11272338",
      "finalizedBlockNumber": "11272403",
      "status": "COMPLETED",
      "subaccount": "0x7072696d61727900000000000000000000000000000000000000000000000000",
      "token": "0xa1623E0AA40B142Cf755938b325321fB2c61Cf05",
      "type": "WITHDRAW",
      "amount": "9",
      "createdAt": 1743812502148,
      "initiatedTransactionHash": "0xe21b386030fcbc1ff6948aac02e9e83db150e03ef9d2383c2a579d42be055a2b",
      "finalizedTransactionHash": "0xe0b33c8e92f164b9bf1114987a7712e30bafbba23490436f4cc5e7ff1a44b9d7"
    },
    {
      "id": "8cfae486-714f-4731-85ca-1b5da32e3755",
      "initiatedBlockNumber": "11272299",
      "finalizedBlockNumber": "11272306",
      "status": "COMPLETED",
      "subaccount": "0x7072696d61727900000000000000000000000000000000000000000000000000",
      "token": "0xa1623E0AA40B142Cf755938b325321fB2c61Cf05",
      "type": "DEPOSIT",
      "amount": "10.000000001",
      "createdAt": 1743812469703,
      "initiatedTransactionHash": "0x6729fd9d7a2e154aa3704849fb720c3ec2f10f3d145adc0290b43761e25aacef",
      "finalizedTransactionHash": "0xf22706a0c292061e3e9d843acad98f33a96b8b7e70b3920da81c1d032f18295d"
    },
    {
      "id": "2eb362ff-f317-4865-9bee-032b4caf0b0d",
      "initiatedBlockNumber": "11205366",
      "finalizedBlockNumber": "11205371",
      "status": "COMPLETED",
      "subaccount": "0x7072696d61727900000000000000000000000000000000000000000000000000",
      "token": "0xa1623E0AA40B142Cf755938b325321fB2c61Cf05",
      "type": "DEPOSIT",
      "amount": "20000",
      "createdAt": 1743745535947,
      "initiatedTransactionHash": "0x4591653ab33603c934b9c3041edad1af779293f347ec71e2c89c0f8b22cfc8a5",
      "finalizedTransactionHash": "0xa11fbd46d39d8249820619a91e81962e4922546b6a13ce453b0db2b5bc9e3c5d"
    }
  ]
}

Token Transfer Fees

Fees on transfers primarily serve to deter spam and minimize network congestion, rather than as a revenue source. Fee structures are configurable per token and may be adjusted over time.

Both deposit and withdrawals can be configured to charge fees (denomianted in native token units). As of writing right now, there are zero deposit fees however USDe is configured to charge a nominal withdrawal fee of 1.

PreviousProductsNextSupported Tokens

Last updated 50 minutes ago

Page cover image