Swagger

Private tokens

3 endpoints.

3 endpoints

Private tokens

GET/private/tokensList private tokens

The active private tokens for a network, for example esUSDT. Read the symbol from here rather than hard-coding it: it is what every other private endpoint takes, and the set is per network.

  • Each entry carries the encrypted-token and registrar contract addresses, the 1:1 public underlying with its decimals, the private token's own internal precision, and display metadata.
  • One encrypted-token contract serves every token on a network — the underlying binds on first deposit — so eercAddress repeats across entries.
  • eercDecimals is that contract's single converter-wide precision, not the underlying's. A deposit converts only whole multiples of it and returns the remainder as dust: a visible remainder on a high-priced underlying, a cent on a stablecoin.

Authentication: X-API-Key

Input — query and path parameters

networkId string required
Chain ID (e.g. 43114 for Avalanche)

Output — example response (200)

{
  "tokens": [
    {
      "symbol": "esUSDT",
      "displayName": "ES USDT",
      "networkId": "43114",
      "eercAddress": "0x10dFB26576d490FF985f61B4Ec3F8DA65aF7B656",
      "registrarAddress": "0x906FAF1D7AD681c92E37B4843A18c56a56f0c85d",
      "underlyingAddress": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
      "underlyingSymbol": "USDT",
      "underlyingDecimals": 6,
      "eercDecimals": 2,
      "iconUrl": "https://assets.extrasafe.chat/crypto/private-tokens/43114/esUSDT/icon.svg?v=2"
    },
    {
      "symbol": "esUSDC",
      "displayName": "ES USDC",
      "networkId": "43114",
      "eercAddress": "0x10dFB26576d490FF985f61B4Ec3F8DA65aF7B656",
      "registrarAddress": "0x906FAF1D7AD681c92E37B4843A18c56a56f0c85d",
      "underlyingAddress": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
      "underlyingSymbol": "USDC",
      "underlyingDecimals": 6,
      "eercDecimals": 2,
      "iconUrl": "https://assets.extrasafe.chat/crypto/private-tokens/43114/esUSDC/icon.svg?v=2"
    },
    {
      "symbol": "esWAVAX",
      "displayName": "ES WAVAX",
      "networkId": "43114",
      "eercAddress": "0x10dFB26576d490FF985f61B4Ec3F8DA65aF7B656",
      "registrarAddress": "0x906FAF1D7AD681c92E37B4843A18c56a56f0c85d",
      "underlyingAddress": "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7",
      "underlyingSymbol": "WAVAX",
      "underlyingDecimals": 18,
      "eercDecimals": 2,
      "iconUrl": "https://assets.extrasafe.chat/crypto/private-tokens/43114/esWAVAX/icon.svg?v=2"
    },
    {
      "symbol": "esDAI",
      "displayName": "ES DAI",
      "networkId": "43114",
      "eercAddress": "0x10dFB26576d490FF985f61B4Ec3F8DA65aF7B656",
      "registrarAddress": "0x906FAF1D7AD681c92E37B4843A18c56a56f0c85d",
      "underlyingAddress": "0xd586E7F844cEa2F87f50152665BCbc2C279D8d70",
      "underlyingSymbol": "DAI",
      "underlyingDecimals": 18,
      "eercDecimals": 2,
      "iconUrl": "https://assets.extrasafe.chat/crypto/private-tokens/43114/esDAI/icon.svg?v=2"
    }
  ],
  "count": 4
}

Output — every status this endpoint answers with

StatusWhen
200Private tokens
400Invalid request body or query parameters
401Missing or invalid credentials
429Rate limit exceeded — retry after the number of seconds in the `Retry-After` response header
500Internal server error
Open in Swagger
POST/private/quoteQuote a private operation

What the account must hold in the public underlying for a private operation to pass validation, asked before the client builds its proof. This is the call to gate a Send button on.

  • It exists because both fees — the product fee and the rail fee — are taken in the public underlying, so an account can hold plenty of the private token and still be refused for want of the public one.
  • /private/prepare cannot answer this: it needs the proof, which is the expensive part of the flow.
  • requiredRaw is a ceiling. It reserves against the worst-case network fee, because the signed terms are capped on that same maximum — quoting the expected fee here would enable a send the backend then refuses.
  • productFeeRaw is the product fee the user pays: ours plus the project's markup, the one figure to show and to size a Max on. projectFeeRaw is the project's part of it.
  • Sufficient compares requiredRaw against balanceRaw, so a client can gate on one boolean.
  • 404 when the symbol names no active private token for the network.

Authentication: X-API-Key

Input — request body

ownerAddress string required
The account the operation is for
networkId string required
Chain id
tokenSymbol string required
Private token symbol from GET /private/tokens
operationKind string required
One of private_deposit | private_transfer | private_withdraw | private_register
amountRaw string
Public underlying amount in base units. Required for deposit and withdraw; omit for a private transfer and for register.

Input — example request

{
  "ownerAddress": "0x098afb6194f54a1ea0097fca5c9593b5ab81348e",
  "networkId": "43114",
  "tokenSymbol": "esUSDT",
  "operationKind": "private_deposit",
  "amountRaw": "1000000"
}

Output — example response (200)

{
  "feeToken": {
    "address": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
    "symbol": "USDT",
    "decimals": 6
  },
  "productFeeRaw": "2500",
  "projectFeeRaw": "0",
  "networkFeeRaw": "52000",
  "requiredRaw": "1054500",
  "balanceRaw": "5000000",
  "sufficient": true
}

Output — every status this endpoint answers with

StatusWhen
200The operation's cost in the public underlying
400Invalid body
401Missing or invalid API key
404Unknown private token for this network
409code=PRIVATE_QUOTE_RAIL_UNSUPPORTED — the project's operations do not travel the executor rail
Open in Swagger
POST/private/preparePrepare a private-token operation

Wraps client-built encrypted-token calldata — a deposit, a private transfer, a withdraw, or the one-time register activation — into a single operation. the RESPONSE SHAPE FOLLOWS the RAIL, so branch on the presence of rail, then poll GET /transaction/:id like any other operation.

  • No rail field: the UserOperation rail. Sign userOperationHash and submit.
  • rail "forward_request": the executor rail. Sign the EIP-712 signingPayload with eth_signTypedData_v4 and submit that; no UserOperation is returned.
  • rail "sponsored": signed and submitted exactly like the executor rail, and ugtpFee is optional on it — when the field is absent, treat it as absent rather than as zero.
  • FEES ARE always IN the public underlying, never in the private token, and only A DEPOSIT and A WITHDRAW PAY one: a percentage of the public amount, 0.25% by default and configurable per project, on top of that operation's network fee. The account must therefore hold the public token even when it is moving the private one.
  • Call POST /private/quote first to learn what it must hold, and treat its figures as authoritative over any rate quoted here.
  • productFee.feeRaw is the product fee the user pays, ours plus the project's markup; productFee.projectFeeRaw is the project's part.
  • A PRIVATE TRANSFER COSTS the ACCOUNT nothing: its productFee comes back as kind "none", which is also why its amount never has to be disclosed.
  • PRIVACY: for a private transfer amountRaw must be omitted, and no amount is received or persisted.
  • register is a one-time, per-network account activation and it COSTS the ACCOUNT nothing: it moves no asset and its productFee comes back as kind "none". There is no fee token to hold and no allowance to grant, so an account that has never deposited can register. It is the prerequisite for holding or receiving a private token, it can never be repeated for the same account, and an account that loses its key must use a fresh one.
  • AN ACCOUNT'S first DEPOSIT CARRIES ITS REGISTRATION: send two calls, the registration first, because the converter refuses a deposit from an unregistered address. Everything else sends one. A standalone registration is its own operationKind and is free.
  • Two DIFFERENT SPENDERS: a deposit's principal is pulled by the encrypted-token contract itself, refused as EERC_ALLOWANCE_MISSING, while the fee is pulled by the executor, refused as EXECUTOR_ALLOWANCE_MISSING. Clear either through /transaction/allowance-setup, naming privateTokenSymbol for the first.
  • 404 for an unknown private token. 400 for calldata this rail cannot relay, or an amount supplied where it must not be. 409 on a duplicate.

Authentication: X-API-Key

Input — request body

ownerAddress string required
The account the operation is for
networkId string required
Chain id
tokenSymbol string required
Private token symbol from GET /private/tokens
operationKind string required
One of private_deposit | private_transfer | private_withdraw | private_register
calls array required
Client-built encrypted-token calls ({ to, data, value }), value is "0x0" when supplied, or omitted to use the zero default. One call, or two when a registration leads a first deposit. The target and the 4-byte selector are checked against the registry and against the function this rail relays for the kind
amountRaw string
Public underlying amount in base units. Required for deposit and withdraw; must be omitted for a private transfer and for register.

Input — example request

{
  "ownerAddress": "0x098afb6194f54a1ea0097fca5c9593b5ab81348e",
  "networkId": "43114",
  "tokenSymbol": "esUSDT",
  "operationKind": "private_withdraw",
  "amountRaw": "200000",
  "calls": [
    {
      "to": "0x…the encrypted-token contract",
      "data": "0x…the client's withdrawal proof",
      "value": "0x0"
    }
  ]
}

Output — example response (201)

{
  "operationId": "ugtp_operation_BJL0njW5DcGh",
  "rail": "forward_request",
  "requestHash": "0x141d4abead5569196b9c3165e040dab50a6a91940a1da44b779a76c5cc01307d",
  "signingPayload": {
    "domain": {
      "name": "UGTPAvalancheExecutor",
      "version": "1",
      "chainId": 43114,
      "verifyingContract": "0xA7C8776C6ac6f21eC923321793F62A4887f9E767"
    },
    "types": {
      "ExecutionRequest": [
        {
          "name": "account",
          "type": "address"
        },
        {
          "name": "nonce",
          "type": "uint256"
        },
        {
          "name": "sends",
          "type": "Send[]"
        },
        {
          "name": "swaps",
          "type": "Swap[]"
        },
        {
          "name": "bridges",
          "type": "Bridge[]"
        },
        {
          "name": "privates",
          "type": "Private[]"
        },
        {
          "name": "terms",
          "type": "FeeTerms"
        }
      ],
      "Send": [
        {
          "name": "token",
          "type": "address"
        },
        {
          "name": "recipient",
          "type": "address"
        },
        {
          "name": "amount",
          "type": "uint256"
        }
      ],
      "...": "Swap, Bridge, Private and FeeTerms members follow; the payload carries the whole tree"
    },
    "primaryType": "ExecutionRequest",
    "message": {
      "account": "0x098afb6194f54a1ea0097fca5c9593b5ab81348e",
      "nonce": "1",
      "sends": [
        {
          "token": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
          "recipient": "0x…the fee recipient",
          "amount": "500"
        }
      ],
      "swaps": [],
      "bridges": [],
      "privates": [
        "…one private action, 1280 bytes of proof and calldata"
      ],
      "terms": {
        "feeToken": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
        "transferredValueInFeeToken": "500",
        "platformTransferFeeBps": 0,
        "projectTransferFeeBps": 0,
        "platformStaticFeeUsd8dp": "5000000",
        "projectStaticFeeUsd8dp": "0",
        "projectPayoutAddress": "0x0000000000000000000000000000000000000000",
        "calldataGas": "24044",
        "maxFeeTokenAmount": "55304",
        "validUntil": 1788511916
      }
    }
  },
  "ugtpFee": {
    "tokenAmount": "55304",
    "expectedTokenAmount": "54671",
    "feeToken": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7"
  },
  "productFee": {
    "kind": "bps",
    "asset": "underlying",
    "feeRaw": "500",
    "tokenAddress": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7"
  }
}

Output — every status this endpoint answers with

StatusWhen
201Prepared private operation
400Invalid input, an unsupported network, or a self-funded call that cannot execute. Inspect the response code and reason.
401Missing or invalid credentials
404Unknown private token
409Duplicate user operation
422Blocked by security verdict
429Rate limit exceeded — retry after the number of seconds in the `Retry-After` response header
500Internal server error
Open in Swagger