Skip to content

Get Balance Updates (WebSocket)

Retrieve historical deposit and withdrawal transactions for a subaccount through the WebSocket connection. This endpoint returns balance changes from on-chain deposits and withdrawals, excluding funding payments.

Endpoint

ws.send() wss://api.synthetix.io/v1/ws/trade

Request

Request Format

{
  "id": "balance-updates-1",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "actionFilter": "DEPOSIT",
    "limit": 50,
    "offset": 0,
    "expiresAfter": 1704153600,
    "signature": {
      "v": 28,
      "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
      "s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
    }
  }
}

Parameters

Request Parameters

ParameterTypeRequiredDescription
idstringYesClient-generated unique request identifier
methodstringYesMust be "post"
paramsobjectYesContains all parameters for the request

Params Object

ParameterTypeRequiredDescription
actionstringYesMust be "getBalanceUpdates"
subAccountIdstringYesSubaccount identifier
actionFilterstringNoFilter by action type: "DEPOSIT", "WITHDRAWAL", or comma-separated "DEPOSIT,WITHDRAWAL". Defaults to both
limitintegerNoMaximum number of results to return (default: 50, max: 1000)
offsetintegerNoPagination offset (default: 0)
expiresAfterintegerNoOptional expiration timestamp in seconds
signatureobjectYesEIP-712 signature using SubAccountAction type
Important Notes:
  • This endpoint uses SubAccountAction EIP-712 type (no nonce required)
  • Returns deposit and withdrawal transactions only (excludes funding payments)
  • For funding payment history, use Get Funding Payments

EIP-712 Signature

const domain = {
  name: "Synthetix",
  version: "1",
  chainId: 1,
  verifyingContract: "0x0000000000000000000000000000000000000000"
};
 
const types = {
  SubAccountAction: [
    { name: "subAccountId", type: "uint256" },
    { name: "action", type: "string" },
    { name: "expiresAfter", type: "uint256" }
  ]
};
 
const message = {
  subAccountId: "1867542890123456789",
  action: "getBalanceUpdates",
  expiresAfter: 0
};
 
const signature = await signer._signTypedData(domain, types, message);

Response Format

Success Response

{
  "id": "balance-updates-1",
  "status": 200,
  "result": {
    "balanceUpdates": [
      {
        "id": 12345,
        "subAccountId": "1867542890123456789",
        "action": "DEPOSIT",
        "status": 1,
        "amount": "1000.00",
        "collateral": "USDT",
        "timestamp": 1704067200000
      },
      {
        "id": 12346,
        "subAccountId": "1867542890123456789",
        "action": "WITHDRAWAL",
        "status": 1,
        "amount": "-500.00",
        "collateral": "USDT",
        "timestamp": 1704153600000,
        "destinationAddress": "0xabcdef1234567890abcdef1234567890abcdef12",
        "txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
      }
    ]
  }
}

Empty Result

{
  "id": "balance-updates-1",
  "status": 200,
  "result": {
    "balanceUpdates": []
  }
}

Error Response

{
  "id": "balance-updates-1",
  "status": 400,
  "result": null,
  "error": {
    "code": 400,
    "message": "limit cannot exceed 1000"
  }
}

Response Fields

Balance Update Object

FieldTypeDescription
idintegerUnique transaction ID
subAccountIdstringSubaccount ID associated with the transaction
actionstringAction type: "DEPOSIT" or "WITHDRAWAL"
statusintegerTransaction status (1 = success)
amountstringAmount changed (positive for deposits, negative for withdrawals)
collateralstringCollateral symbol (e.g., "USDT", "WETH")
timestampintegerUnix timestamp in milliseconds when the transaction was created
destinationAddressstringDestination address for withdrawals (optional, only for withdrawals)
txHashstringOn-chain transaction hash (optional)
toSubAccountIdstringTarget subaccount ID for internal transfers (optional)

Code Examples

Get All Balance Updates

{
  "id": "all-updates",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Get Deposits Only

{
  "id": "deposits-only",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "actionFilter": "DEPOSIT",
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Get Withdrawals Only

{
  "id": "withdrawals-only",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "actionFilter": "WITHDRAWAL",
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Paginated Request

{
  "id": "paginated",
  "method": "post",
  "params": {
    "action": "getBalanceUpdates",
    "subAccountId": "1867542890123456789",
    "limit": 100,
    "offset": 100,
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Implementation Example

class BalanceUpdatesQuery {
  constructor(ws, signer) {
    this.ws = ws;
    this.signer = signer;
    this.pendingRequests = new Map();
  }
 
  async getBalanceUpdates(options = {}) {
    const {
      subAccountId,
      actionFilter = null,
      limit = 50,
      offset = 0,
      expiresAfter = 0
    } = options;
 
    const signature = await this.signSubAccountAction(subAccountId, "getBalanceUpdates", expiresAfter);
 
    const request = {
      id: `balance-updates-${Date.now()}`,
      method: "post",
      params: {
        action: "getBalanceUpdates",
        subAccountId,
        limit,
        offset,
        expiresAfter,
        signature
      }
    };
 
    if (actionFilter) {
      request.params.actionFilter = actionFilter;
    }
 
    return this.sendRequest(request);
  }
 
  async signSubAccountAction(subAccountId, action, expiresAfter = 0) {
    const domain = {
      name: "Synthetix",
      version: "1",
      chainId: 1,
      verifyingContract: "0x0000000000000000000000000000000000000000"
    };
 
    const types = {
      SubAccountAction: [
        { name: "subAccountId", type: "uint256" },
        { name: "action", type: "string" },
        { name: "expiresAfter", type: "uint256" }
      ]
    };
 
    const message = {
      subAccountId: BigInt(subAccountId),
      action,
      expiresAfter: BigInt(expiresAfter)
    };
 
    const sig = await this.signer.signTypedData(domain, types, message);
    const { v, r, s } = ethers.Signature.from(sig);
    return { v, r, s };
  }
 
  sendRequest(request) {
    return new Promise((resolve, reject) => {
      this.pendingRequests.set(request.id, { resolve, reject });
      this.ws.send(JSON.stringify(request));
 
      setTimeout(() => {
        if (this.pendingRequests.has(request.id)) {
          this.pendingRequests.delete(request.id);
          reject(new Error('Request timeout'));
        }
      }, 10000);
    });
  }
}
 
// Usage
const query = new BalanceUpdatesQuery(ws, signer);
 
// Get all deposits
const deposits = await query.getBalanceUpdates({
  subAccountId: "1867542890123456789",
  actionFilter: "DEPOSIT"
});
 
console.log(`Found ${deposits.balanceUpdates.length} deposits`);

Implementation Notes

  • Results are ordered by timestamp (most recent first)
  • Default limit is 50 results per request, maximum is 1000
  • Use pagination with limit and offset for large result sets
  • Authentication requires signature by account owner or authorized delegate

Common Errors

Error CodeMessageDescription
400subAccountId is requiredNo subaccount ID was provided
400limit cannot exceed 1000Limit parameter exceeds maximum
400limit must be non-negativeNegative limit value provided
400offset must be non-negativeNegative offset value provided
400actionFilter must be 'DEPOSIT', 'WITHDRAWAL', or comma-separated combinationInvalid action filter value
ErrorDescription
Invalid signatureEIP-712 signature validation failed
Invalid market symbolMarket symbol not recognized
Nonce already usedNonce must be greater than previous value
Rate limit exceededToo many requests in time window
Request expiredexpiresAfter timestamp has passed

Next Steps