Skip to content

Deposits & Account Creation

This guide covers how to deposit collateral into your Synthetix trading account and how accounts are created.

Overview

Deposits are on-chain transactions made directly to the SynthetixDepositContract smart contract. Your first deposit automatically creates your trading account.

Contract Addresses

NetworkContractAddress
MainnetSynthetixDepositContract (Proxy)0xD62595c3c23B690BAEE0935e107A209Cb1Dbd37B
MainnetUSDT0xdAC17F958D2ee523a2206206994597C13D831ec7
MainnetPermit20x000000000022D473030F116dDEE9F6B43aC78BA3
SepoliaSynthetixDepositContract (Proxy)0x5Ed4a299E9fa36E6bDb4E0723bD3ad9D233f33A0
SepoliaUSDT (Mock)0xE02d1640dcaB494327DbE4B63fBcD441b92c8205

Supported Collateral

Currently supported collateral types:

AssetDecimalsNetwork
USDT6Mainnet

Each collateral has configurable limits:

  • Minimum Deposit: Minimum amount per deposit transaction
  • Maximum Balance: Maximum total balance per user
  • Minimum Withdrawal: Minimum amount per withdrawal

Deposit Flow

Account Creation

Your first deposit automatically creates a master account. No separate account creation step is required.

Understanding Account IDs:
  • subAccountId: The unique identifier for any account (master or sub). This is a non-zero value (e.g., 987654321) generated when the account is created.
  • masterId: Indicates the account hierarchy:
    • masterId = 0 means this account IS the master account
    • masterId = <non-zero ID> means this is a subaccount belonging to that master

When depositing, passing subAccountId: 0 in the request is a special value that means "deposit to my master account" - it does not mean the master account has ID 0.

Standard Deposit (ERC20 Approve + Deposit)

Deposits require two transactions:

  1. Approve: Allow the deposit contract to spend your tokens
  2. Deposit: Call the deposit function on the contract

JavaScript Example (ethers.js v6)

import { ethers } from 'ethers';
 
// Contract addresses (Mainnet)
const DEPOSIT_CONTRACT = '0xD62595c3c23B690BAEE0935e107A209Cb1Dbd37B';
const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
 
// ABI for deposit function
const depositABI = [
  'function deposit((address token, uint256 amount, address beneficiary, uint256 subAccountId, (((address token, uint256 amount) permitted, uint256 nonce, uint256 deadline) permit, bytes signature) permitDetails)[] deposits)'
];
 
const erc20ABI = [
  'function approve(address spender, uint256 amount) returns (bool)',
  'function allowance(address owner, address spender) view returns (uint256)'
];
 
async function deposit(signer, amount) {
  const walletAddress = await signer.getAddress();
  
  // Step 1: Approve USDT spending (if needed)
  const usdt = new ethers.Contract(USDT, erc20ABI, signer);
  const currentAllowance = await usdt.allowance(walletAddress, DEPOSIT_CONTRACT);
  
  if (currentAllowance < amount) {
    console.log('Approving USDT...');
    const approveTx = await usdt.approve(DEPOSIT_CONTRACT, amount);
    await approveTx.wait();
    console.log('Approval confirmed');
  }
 
  // Step 2: Deposit
  const depositContract = new ethers.Contract(DEPOSIT_CONTRACT, depositABI, signer);
  
  const tx = await depositContract.deposit([{
    token: USDT,
    amount: amount,
    beneficiary: walletAddress,
    subAccountId: 0, // 0 = deposit to master account (auto-created on first deposit)
    permitDetails: {
      permit: {
        permitted: { token: ethers.ZeroAddress, amount: 0 },
        nonce: 0,
        deadline: 0
      },
      signature: '0x'
    }
  }]);
 
  console.log('Deposit transaction sent:', tx.hash);
  const receipt = await tx.wait();
  console.log('Deposit confirmed in block:', receipt.blockNumber);
  
  return receipt;
}
 
// Usage
const provider = new ethers.JsonRpcProvider('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY');
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
 
const amount = ethers.parseUnits('1000', 6); // 1000 USDT
await deposit(signer, amount);

Python Example (web3.py)

from web3 import Web3
import os
 
# Contract addresses (Mainnet)
DEPOSIT_CONTRACT = '0xD62595c3c23B690BAEE0935e107A209Cb1Dbd37B'
USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
 
# Connect to Ethereum
w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'))
account = w3.eth.account.from_key(os.environ['PRIVATE_KEY'])
 
# ERC20 ABI (minimal)
erc20_abi = [
    {
        "name": "approve",
        "type": "function",
        "inputs": [
            {"name": "spender", "type": "address"},
            {"name": "amount", "type": "uint256"}
        ],
        "outputs": [{"type": "bool"}]
    }
]
 
# Deposit contract ABI (minimal)
deposit_abi = [
    {
        "name": "deposit",
        "type": "function",
        "inputs": [{
            "name": "deposits",
            "type": "tuple[]",
            "components": [
                {"name": "token", "type": "address"},
                {"name": "amount", "type": "uint256"},
                {"name": "beneficiary", "type": "address"},
                {"name": "subAccountId", "type": "uint256"},
                {"name": "permitDetails", "type": "tuple", "components": [
                    {"name": "permit", "type": "tuple", "components": [
                        {"name": "permitted", "type": "tuple", "components": [
                            {"name": "token", "type": "address"},
                            {"name": "amount", "type": "uint256"}
                        ]},
                        {"name": "nonce", "type": "uint256"},
                        {"name": "deadline", "type": "uint256"}
                    ]},
                    {"name": "signature", "type": "bytes"}
                ]}
            ]
        }]
    }
]
 
def deposit(amount_usdt):
    amount = amount_usdt * 10**6  # USDT has 6 decimals
    
    # Step 1: Approve
    usdt = w3.eth.contract(address=USDT, abi=erc20_abi)
    approve_tx = usdt.functions.approve(DEPOSIT_CONTRACT, amount).build_transaction({
        'from': account.address,
        'nonce': w3.eth.get_transaction_count(account.address),
        'gas': 100000,
        'gasPrice': w3.eth.gas_price
    })
    signed_approve = account.sign_transaction(approve_tx)
    tx_hash = w3.eth.send_raw_transaction(signed_approve.raw_transaction)
    w3.eth.wait_for_transaction_receipt(tx_hash)
    print(f"Approval confirmed: {tx_hash.hex()}")
 
    # Step 2: Deposit
    deposit_contract = w3.eth.contract(address=DEPOSIT_CONTRACT, abi=deposit_abi)
    deposit_tx = deposit_contract.functions.deposit([{
        'token': USDT,
        'amount': amount,
        'beneficiary': account.address,
        'subAccountId': 0,  # 0 = deposit to master account (auto-created on first deposit)
        'permitDetails': {
            'permit': {
                'permitted': {'token': '0x0000000000000000000000000000000000000000', 'amount': 0},
                'nonce': 0,
                'deadline': 0
            },
            'signature': b''
        }
    }]).build_transaction({
        'from': account.address,
        'nonce': w3.eth.get_transaction_count(account.address),
        'gas': 200000,
        'gasPrice': w3.eth.gas_price
    })
    signed_deposit = account.sign_transaction(deposit_tx)
    tx_hash = w3.eth.send_raw_transaction(signed_deposit.raw_transaction)
    receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
    print(f"Deposit confirmed in block: {receipt['blockNumber']}")
    
    return receipt
 
# Usage
deposit(1000)  # Deposit 1000 USDT

Depositing to a Specific Subaccount

Once you have created additional subaccounts under your master account, you can deposit directly to them by specifying their subAccountId:

// Deposit to a specific subaccount
const tx = await depositContract.deposit([{
  token: USDT,
  amount: amount,
  beneficiary: walletAddress,
  subAccountId: 987654321, // Specific subaccount ID
  permitDetails: {
    permit: {
      permitted: { token: ethers.ZeroAddress, amount: 0 },
      nonce: 0,
      deadline: 0
    },
    signature: '0x'
  }
}]);

Gasless Deposits (Permit2)

For a better user experience, you can use Permit2 to combine approval and deposit into a single signature + transaction:

  1. Sign a Permit2 message authorizing the transfer
  2. Call deposit() with the permit details

This avoids the separate approval transaction. The Permit2 contract address is 0x000000000022D473030F116dDEE9F6B43aC78BA3 (same on all networks).

Verifying Your Deposit

After submitting a deposit transaction, you can verify it was successful in two ways:

1. Check Transaction Receipt

The deposit emits an AssetDeposited event:

// Check for AssetDeposited event in receipt
const AssetDepositedTopic = ethers.id(
  "AssetDeposited(address,address,address,uint256,uint256)"
);
 
const depositEvent = receipt.logs.find(
  log => log.topics[0] === AssetDepositedTopic
);
 
if (depositEvent) {
  console.log('Deposit successful!');
  // Parse event data: depositor, beneficiary, token, amount, subAccountId
}

2. Query via API

After a short indexing delay (typically 5-30 seconds), your deposit will appear in the API:

import axios from 'axios';
 
async function verifyDeposit(subAccountId, signature, expiresAfter) {
  const response = await axios.post('https://papi.synthetix.io/v1/trade', {
    params: {
      action: 'getBalanceUpdates',
      subAccountId: subAccountId,
      actionFilter: 'DEPOSIT',
      limit: 5
    },
    signature: signature,
    expiresAfter: expiresAfter
  });
  
  return response.data.response.balanceUpdates;
}

See Get Balance Updates for full API documentation.

Verification Flow

  1. Submit transaction: Call approve() then deposit() on the contract
  2. Get receipt: Transaction receipt contains AssetDeposited event
  3. Wait for indexing: Events are indexed within ~5-30 seconds
  4. Query API: Call getBalanceUpdates to confirm deposit appears in history

Troubleshooting

IssueCauseSolution
Transaction revertsInsufficient approvalEnsure you've approved enough tokens for the deposit contract
"Amount below minimum"Deposit too smallCheck minimum deposit requirements
"Exceeds maximum"User or global cap reachedContact team about limits
Deposit not showing in APIIndexing delayWait 30-60 seconds and retry

Related