Builder Integration Guide
Overview
This guide explains how to integrate your application with GRVT and enable trading on behalf of your users.
Integration steps
1. Connect User Wallet
2. Authorize Builder
Choose the environment you want to authenticate against.
# stg
GRVT_AUTH_ENDPOINT="https://edge.staging.gravitymarkets.io/auth/builder/authorize"
# testnet
GRVT_AUTH_ENDPOINT="https://edge.testnet.grvt.io/auth/builder/authorize"
# prod
GRVT_AUTH_ENDPOINT="https://edge.grvt.io/auth/builder/authorize"
Request Parameters
Retrieving Funding Addresses
Both main_account_id and builder_account_id are funding addresses that can be retrieved by accessing the GRVT Exchange API Keys page: https://grvt.io/exchange/account/api-keys
This endpoint supports two modes depending on whether you provide the API key fields:
- With API key (provide
builder_api_key_signer+builder_api_key_permissions+builder_api_key_label): Authorizes the builder on-chain viaAddAccountSignerWithBuilderand creates a GRVT API key. The API key is returned in the response. - Without API key (omit all three
builder_api_key_*fields): Authorizes the builder on-chain viaAuthorizeBuilderwithout creating an API key. Returns an empty response.
| "name" | Type | Required | Description |
|---|---|---|---|
| main_account_id | string | True | The funding address of the user granting the authorization. |
| builder_account_id | string | True | The funding address of the Builder receiving the authorization. |
| max_futures_fee_rate | string | True | The maximum fee rate cap in percentage for Futures trades executed by this builder. The builder cannot charge fees exceeding this limit. Eg. "0.1" means 0.1% |
| max_spot_fee_rate | string | True | The maximum fee rate cap in percentage for Spot trades executed by this builder. The builder cannot charge fees exceeding this limit. Eg. "0.1" means 0.1% |
| signature | Signature | True | The cryptographic signature authenticating this request. Must be signed by the private key associated with mainAccountID. See Signing Payload section below for details. |
| builder_api_key_label | string | False | Required when builder_api_key_signer and builder_api_key_permissions are provided. The user will see this label on the gravity api list UI. |
| builder_api_key_signer | string | False | An Ethereum public key pair that you generate for your user. This key can sign trades on behalf of your user across all sub-accounts they have. You can use the private key to sign trades on behalf of your user without sending Grvt the private key. Must be provided together with builder_api_key_permissions. |
| builder_api_key_permissions | string | False | Permissions as a sorted string (lowest bit to highest bit), separated by &:- Examples: "Trade", "Admin", "Admin&Trade"- Bit mapping: ADMIN=1, INTERNAL_TRANSFER=2, EXTERNAL_TRANSFER=3, WITHDRAW=4, VAULT_INVESTOR=5, TRADE=6 Please use TRADE for now Must be provided together with builder_api_key_signer. |
Example Request
With API key creation:
{
"main_account_id": "'0x...'",
"builder_account_id": "'0x....'",
"max_futures_fee_rate": "0.001",
"max_spot_fee_rate": "0.0001",
"signature": {
"signer": "0xc73c0c2538fd9b833d20933ccc88fdaa74fcb0d0",
"r": "0xb788d96fee91c7cdc35918e0441b756d4000ec1d07d900c73347d9abbc20acc8",
"s": "0x3d786193125f7c29c958647da64d0e2875ece2c3f845a591bdd7dae8c475e26d",
"v": 28,
"expiration": "1697788800000000000",
"nonce": 1234567890,
"chain_id": "327"
},
"builder_api_key_label": "superbuilder",
"builder_api_key_signer": "0x....",
"builder_api_key_permissions": "Admin&Trade"
}
Without API key (on-chain authorization only):
{
"main_account_id": "'0x...'",
"builder_account_id": "'0x....'",
"max_futures_fee_rate": "0.001",
"max_spot_fee_rate": "0.0001",
"signature": {
"signer": "0xc73c0c2538fd9b833d20933ccc88fdaa74fcb0d0",
"r": "0xb788d96fee91c7cdc35918e0441b756d4000ec1d07d900c73347d9abbc20acc8",
"s": "0x3d786193125f7c29c958647da64d0e2875ece2c3f845a591bdd7dae8c475e26d",
"v": 28,
"expiration": "1697788800000000000",
"nonce": 1234567890,
"chain_id": "327"
}
}
Signing Payload
The signature must be created using EIP-712 typed data signing. The structure differs based on the authorization mode:
With API key (when providing builder_api_key_signer + builder_api_key_permissions):
{
"domain": {
"chainId": 327,
"name": "GRVT Exchange",
"version": "0"
},
"message": {
"accountID": "'0x...'",
"signer": "'0x....'",
"permissions": "Trade",
"builderAccountID": "'0x....'",
"maxFutureFeeRate": 100,
"maxSpotFeeRate": 10,
"nonce": 1234567890,
"expiration": 1697788800000000000
},
"primaryType": "AddAccountSignerWithBuilder",
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" }
],
"AddAccountSignerWithBuilder": [
{ "name": "accountID", "type": "address" },
{ "name": "signer", "type": "address" },
{ "name": "permissions", "type": "string" },
{ "name": "builderAccountID", "type": "address" },
{ "name": "maxFutureFeeRate", "type": "uint32" },
{ "name": "maxSpotFeeRate", "type": "uint32" },
{ "name": "nonce", "type": "uint32" },
{ "name": "expiration", "type": "int64" }
]
}
}
Without API key (on-chain authorization only):
{
"domain": {
"chainId": 327,
"name": "GRVT Exchange",
"version": "0"
},
"message": {
"mainAccountID": "'0x...'",
"builderAccountID": "'0x....'",
"maxFutureFeeRate": 100,
"maxSpotFeeRate": 10,
"nonce": 1234567890,
"expiration": 1697788800000000000
},
"primaryType": "AuthorizeBuilder",
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" }
],
"AuthorizeBuilder": [
{ "name": "mainAccountID", "type": "address" },
{ "name": "builderAccountID", "type": "address" },
{ "name": "maxFutureFeeRate", "type": "uint32" },
{ "name": "maxSpotFeeRate", "type": "uint32" },
{ "name": "nonce", "type": "uint32" },
{ "name": "expiration", "type": "int64" }
]
}
}
Chain ID
The chainId value in the domain field must match the GRVT L2 Chain ID for your target environment. The example above uses 327 (Sepolia Stg). Refer to the Chain IDs table for all network-specific values.
Fee Rate Units
The maxFutureFeeRate and maxSpotFeeRate fields in the signing payload are expressed as integers multiplied by 10,000 (1e4):
- Calculation: fee_rate × 10,000
- Example conversions:
- `0.001` fee rate = `10` (0.001 × 10,000)
- `0.0005` fee rate = `5` (0.0005 × 10,000)
- `0.0001` fee rate = `1` (0.0001 × 10,000)
Payload Fields:
| Field | Type | Mode | Description |
|---|---|---|---|
| chainId | uint256 | Both | The GRVT L2 Chain ID for the target network (in domain field). Refer to Chain IDs table for network-specific values. |
| mainAccountID | address | Without API key | The Main Account ID (Ethereum address) of the user granting authorization (used in AuthorizeBuilder type) |
| accountID | address | With API key | The Main Account ID (Ethereum address) of the user granting authorization (used in AddAccountSignerWithBuilder type) |
| builderAccountID | address | Both | The builder_account_id - Main Account ID of the Builder |
| maxFutureFeeRate | uint32 | Both | The max_futures_fee_rate - Maximum fee rate for Futures trades (multiplied by 10,000) |
| maxSpotFeeRate | uint32 | Both | The max_spot_fee_rate - Maximum fee rate for Spot trades (multiplied by 10,000) |
| nonce | uint32 | Both | Random value for signature deconflicting (0 to 4,294,967,295) |
| expiration | int64 | Both | Timestamp in unix nanoseconds when signature expires (max 30 days) |
| signer | address | With API key | The builder_api_key_signer - Ethereum public key that can sign trades on behalf of the user |
| permissions | string | With API key | The builder_api_key_permissions as a sorted string (lowest bit to highest bit), separated by &:- Single permission: "Admin" or "Trade"- Multiple permissions: "Admin&InternalTransfer", "Admin&InternalTransfer&Trade"- Bit mapping: ADMIN=1, INTERNAL_TRANSFER=2, EXTERNAL_TRANSFER=3, WITHDRAW=4, VAULT_INVESTOR=5, TRADE=6 - Note: Permissions are always sorted by bit position (e.g., "Admin&Trade", never "Trade&Admin") |
Permission String Format
- Both the
permissionsfield in the signing payload and thebuilder_api_key_permissionsrequest parameter must use the sorted string format - When multiple permissions are granted, they must be sorted by bit position (lowest to highest) and joined with
& - Examples:
- Single permission:
"Trade"or"Admin" - Multiple permissions:
"Admin&InternalTransfer"(correct) vs(incorrect - wrong order)"InternalTransfer&Admin" - Combined:
"Admin&InternalTransfer&Trade"
- Single permission:
- Note: The permission string format ensures a 1-to-1 mapping between the string and its bitmask value
Signing Process:
- The user must sign this EIP-712 typed data payload with their private key associated with
main_account_id - The signing produces three values:
r,s, andv - These values, along with the
signeraddress,nonce,expiration, andchain_idare included in thesignaturefield of the request
Note
The signature fields correspond to the Signature type used throughout the GRVT API.
Example Response
With API key creation — the API key is used for authentication in step 3:
{
"api_key": "abc....."
}
Without API key — returns an empty response on success:
{}
3. Authenticate with API Key
Note
This step applies only when using the with API key authorization mode.
Use the api_key returned from step 2 to authenticate with the GRVT API and use trading functions.
For detailed authentication instructions, please refer to the Authentication page.
4. Manage Sub-Accounts
Query available sub-accounts that your builder can trade on.
Reference: Builder Codes - Get Sub Accounts
5. Execute Trades
Your builder can now execute trades on all authorized sub-accounts using the Trade permission and the builder_api_signer key pair from one of the sub accounts.
Reference: Builder Codes - Trading on Behalf of Users
Get Deposit Addresses
Returns the chains and tokens supported for depositing into the authenticated user's GRVT account, along with the on-chain deposit address to send funds to on each chain.
Use this endpoint to discover where a user should send funds when topping up their GRVT account. Each chain entry includes the deposit address generated for the user, the supported tokens, and their on-chain contract addresses.
Endpoint
# staging
GRVT_DEPOSIT_ENDPOINT="https://edge.staging.gravitymarkets.io/api/v1/deposit/addresses"
# testnet
GRVT_DEPOSIT_ENDPOINT="https://edge.testnet.grvt.io/api/v1/deposit/addresses"
# prod
GRVT_DEPOSIT_ENDPOINT="https://edge.grvt.io/api/v1/deposit/addresses"
| Method | Path |
|---|---|
| GET | /api/v1/deposit/addresses |
Authentication
This endpoint accepts either authentication method:
- API key — pass your API key in the
X-API-Keyheader. The account is resolved directly from the key, so no session cookie is required. - Session cookie — authenticate via login to obtain a session cookie (
gravity=...) and theX-Grvt-Account-Idheader value, then pass both on the request.
See the Authentication page for details on both methods.
The resolved account must have completed chain account setup. Requests are rejected when the bridge feature is disabled or the account has no chain account address.
Request Parameters
This endpoint takes no query parameters or request body. The chains and tokens are derived from the resolved account.
Example Request
Using an API key:
GRVT_API_KEY="<insert_key_here>"
curl "$GRVT_DEPOSIT_ENDPOINT" \
-H "X-API-Key: $GRVT_API_KEY" \
-s
Using a session cookie:
curl "$GRVT_DEPOSIT_ENDPOINT" \
-H "Cookie: $GRVT_COOKIE" \
-H "X-Grvt-Account-Id: $GRVT_ACCOUNT_ID" \
-s
Response Fields
| Field | Type | Description |
|---|---|---|
| chains | array of ChainInfo | List of chains supported for deposits, one per chain. |
ChainInfo
| Field | Type | Description |
|---|---|---|
| chain | string | The GRVT chain name (e.g. ARBITRUM, BASE, BSC, KAIA, POLYGON, SOLANA, TRON). |
| chain_id | string | The native chain ID, derived from chain (e.g. 42161 for ARBITRUM, 56 for BSC). For non-EVM chains this is a chain name such as solana or tron. |
| deposit_address | string | The on-chain address to send deposits to for this chain. Empty for on-demand-only chains where a deposit address has not yet been generated for the user (see note below). |
| supported_tokens | array of TokenInfo | The tokens that can be deposited on this chain. |
TokenInfo
| Field | Type | Description |
|---|---|---|
| name | string | The token symbol (e.g. USDT, USDC). |
| contract_address | string | The token's on-chain contract address on the given chain. May be empty for native chain assets. |
Example Response
{
"chains": [
{
"chain": "ARBITRUM",
"chain_id": "42161",
"deposit_address": "0xYourArbitrumDepositAddress",
"supported_tokens": [
{
"name": "USDT",
"contract_address": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"
},
{
"name": "USDC",
"contract_address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
}
]
},
{
"chain": "BASE",
"chain_id": "8453",
"deposit_address": "0xYourBaseDepositAddress",
"supported_tokens": [
{
"name": "USDC",
"contract_address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
}
]
},
{
"chain": "BSC",
"chain_id": "56",
"deposit_address": "0xYourBscDepositAddress",
"supported_tokens": [
{
"name": "USDT",
"contract_address": "0x55d398326f99059fF775485246999027B3197955"
},
{
"name": "USDC",
"contract_address": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"
}
]
},
{
"chain": "KAIA",
"chain_id": "8217",
"deposit_address": "0xYourKaiaDepositAddress",
"supported_tokens": [
{
"name": "USDT",
"contract_address": "0xd077a400968890eacc75cdc901f0356c943e4fdb"
}
]
},
{
"chain": "TRON",
"chain_id": "tron",
"deposit_address": "TYourTronDepositAddress",
"supported_tokens": [
{
"name": "USDT",
"contract_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
}
]
},
{
"chain": "SOLANA",
"chain_id": "solana",
"deposit_address": "",
"supported_tokens": [
{
"name": "USDT",
"contract_address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
},
{
"name": "USDC",
"contract_address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
}
]
}
]
}
On-demand chains
Some chains (e.g. SOLANA) are on-demand only. They are not automatically registered for wallet generation, so they are returned with an empty deposit_address until a deposit address is provisioned for the user. The chain and its supported tokens are still listed so clients can surface them in the UI.
Token contract addresses
Token contract_address values are resolved dynamically from the bridge provider's configuration. If the bridge provider cannot be reached, a chain's tokens may be returned with empty contract_address values.
Chain names
The chain value identifies the chain across every GRVT API — pass it back exactly as returned whenever a request takes a chain. chain_id and chain_type are provided for reference only.
Generate Deposit Address
Provisions the deposit address for a single chain and returns it in the same shape as one entry of Get Deposit Addresses.
Some chains are provisioned on demand rather than up front — see Appendix: Supported Chains for which ones. Call this endpoint to provision a deposit address on such a chain. It is idempotent: calling it for a chain that already has an address returns that address.
Endpoint
# staging
GRVT_DEPOSIT_ADDRESS_ENDPOINT="https://edge.staging.gravitymarkets.io/api/v1/deposit/generate-address"
# testnet
GRVT_DEPOSIT_ADDRESS_ENDPOINT="https://edge.testnet.grvt.io/api/v1/deposit/generate-address"
# prod
GRVT_DEPOSIT_ADDRESS_ENDPOINT="https://edge.grvt.io/api/v1/deposit/generate-address"
| Method | Path |
|---|---|
| POST | /api/v1/deposit/generate-address |
Authentication
Same as Get Deposit Addresses — either an X-API-Key header or a session cookie (gravity=...) plus the X-Grvt-Account-Id header. See the Authentication page.
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| chain | string | True | The GRVT chain name to provision, as listed in Appendix: Supported Chains (e.g. SOLANA). Case-insensitive. |
Example Request
GRVT_API_KEY="<insert_key_here>"
curl "$GRVT_DEPOSIT_ADDRESS_ENDPOINT" \
-X POST \
-H "X-API-Key: $GRVT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"chain": "SOLANA"}' \
-s
Response Fields
A single ChainInfo object, with the same fields as an entry of Get Deposit Addresses.
| Field | Type | Description |
|---|---|---|
| chain | string | The GRVT chain name. |
| chain_id | string | The native chain ID, derived from chain. |
| deposit_address | string | The on-chain address to send deposits to for this chain. |
| supported_tokens | array of TokenInfo | The tokens that can be deposited on this chain. |
Example Response
{
"chain": "SOLANA",
"chain_id": "solana",
"deposit_address": "7CyChDU4JfcL6iYxHFF9vD83mcEdS1H9SGrFtPe6mY5G",
"supported_tokens": [
{
"name": "USDT",
"contract_address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
},
{
"name": "USDC",
"contract_address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
}
]
}
Unsupported chains
A chain that is not a supported GRVT chain name, or one that is not enabled for the account, is rejected with a 400 error. See Appendix: Supported Chains for the chains available in each environment.
Withdraw via Non-Native Bridge
Withdraw funds from a GRVT funding account directly to an external wallet on another chain, using GRVT's non-native bridge — a third-party bridge provider that pays the user out of its own liquidity. It is faster and cheaper than a native withdrawal, which settles through GRVT's own on-chain bridge.
This is a quote-then-execute flow: request a firm quote, sign a standard GRVT Transfer message for the withdrawal amount, submit the two together, then poll for completion.
| Step | Endpoint | Purpose |
|---|---|---|
| 1. Discover | GET /api/v1/bridge/withdrawal-info |
Supported routes and the bridge funding address to sign toward. |
| 2. Quote | POST /api/v1/bridge/withdrawal-quote |
Returns a quote_id with a fixed fee and receive amount. |
| 3. Sign | (local) | Sign an EIP-712 Transfer message for the amount → bridge funding address. |
| 4. Execute | POST /api/v1/bridge/withdraw |
Submit quote_id + signature. Returns 202 Accepted. |
| 5. Track | GET /api/v1/bridge/withdrawal-status |
Poll until COMPLETED (or FAILED / REFUNDED). |
The quote_id is the withdrawal's identifier across steps 2–5.
Authentication
All bridge withdrawal endpoints require a session cookie, obtained from the API Key Login endpoint. Send the resulting gravity=... cookie and the X-Grvt-Account-Id header on every request.
Session cookie required
Unlike the deposit endpoints, an X-API-Key header alone is not sufficient here — executing a withdrawal authenticates the underlying transfer against the trading engine using the session cookie. Log in first via API Key Login and reuse the returned cookie for all four endpoints.
Prerequisites
- The destination address must already be registered in the account's withdrawal address book (a one-time setup step via the GRVT UI or API, which may require 2FA).
- The signing wallet must be a main-account signer with the External Transfer or Funding Admin permission. For programmatic use this is typically the signer wallet of an account-level API key created with the External Transfer permission; the account owner's own wallet also works. Sub-account (trading) API keys and session keys cannot sign withdrawals.
- All destination addresses are screened (KYA) at execution time; withdrawals to flagged addresses are rejected.
Withdrawal Info
GET /api/v1/bridge/withdrawal-info
Returns the bridge provider's funding address (the transfer destination when signing) and the available withdrawal routes per source token. Takes no parameters.
Response Fields
| Field | Type | Description |
|---|---|---|
| provider | string | Identifier of the bridge provider serving the payout. |
| funding_address | string | Bridge funding address — the toAccount of the signed Transfer message (see Signing the Transfer Message). |
| route | object | Available routes, keyed by source token (token_in). A source token with no available route is present with an empty array. |
| route.<token_in>[].chain | string | Destination chain name; use as chain in the quote request. |
| route.<token_in>[].chain_id | string | Chain id of the destination network (derived from chain). |
| route.<token_in>[].chain_type | string | evm | solana | tron — must match the destination address type. |
| route.<token_in>[].tokens_out | string[] | Payout tokens available on that chain for this token_in. |
Example Response
{
"provider": "RHINO",
"funding_address": "0xYourBridgeFundingAddress",
"route": {
"USDT": [
{ "chain": "ARBITRUM", "chain_id": "42161", "chain_type": "evm", "tokens_out": ["USDT", "USDC"] },
{ "chain": "BSC", "chain_id": "56", "chain_type": "evm", "tokens_out": ["USDT"] },
{ "chain": "SOLANA", "chain_id": "solana","chain_type": "solana", "tokens_out": ["USDT", "USDC"] }
],
"USDC": []
}
}
Source token
token_in is currently USDT only. The USDC key is present with an empty array so the response shape does not change when it is enabled.
Create Withdrawal Quote
POST /api/v1/bridge/withdrawal-quote
Creates a firm quote. The fee and receive amount are locked to the quote until it expires (~1 minute). Creating a quote has no side effects — unused quotes simply expire.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
| to_address | string | True | Destination wallet. Must be in the caller's withdrawal address book. |
| chain | string | True | Destination chain, from Withdrawal Info (e.g. ARBITRUM). |
| token_in | string | True | Token debited from the funding account. Currently USDT. |
| token_out | string | True | Token received on the destination chain. May differ from token_in (conversion handled by the bridge). |
| amount | string | True | Amount of token_in to withdraw, as a decimal string. |
Example Request
{
"to_address": "0xA1b2C3d4E5f6A7b8C9d0E1f2A3b4C5d6E7f8A9b0",
"chain": "ARBITRUM",
"token_in": "USDT",
"token_out": "USDT",
"amount": "1000"
}
Response Fields
| Field | Type | Description |
|---|---|---|
| quote_id | string | Quote identifier; used in withdraw and for status polling. |
| pay_amount | string | Amount of token_in debited from the funding account. |
| receive_amount | string | Amount of token_out paid out on the destination chain. |
| fee | string | Bridge fee, denominated in token_in. |
| fee_usd | string | Fee expressed in USD (informational). |
| speed | string | fast (normal) or slow (bridge temporarily congested; longer settlement). |
| expires_at | string | Quote expiry, ISO-8601. Execute before this time. |
Example Response
{
"quote_id": "6a7d8c6f61855ec660c84db8",
"pay_amount": "1000",
"receive_amount": "997.8",
"fee": "2.2",
"fee_usd": "2.2",
"speed": "fast",
"expires_at": "2026-08-13T09:23:47Z"
}
Quote failures
A quote is rejected (4xx) when the route is not supported, the amount is below the bridge fee, or a withdrawal limit has been reached.
Execute Withdrawal
POST /api/v1/bridge/withdraw
Executes a previously created quote. The request carries the quote_id and an EIP-712 signature over a GRVT Transfer message (see Signing the Transfer Message). The signature object has the same shape as the standard GRVT transfer API, so existing signing code can be reused as-is. On success the funds leave the funding account immediately and the bridge payout begins.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
| quote_id | string | True | From the quote. Must not be expired. |
| signature.signer | string | True | Wallet address signing the message. Must be a main-account signer with External Transfer or Funding Admin permission. |
| signature.r | string | True | Signature R (hex). |
| signature.s | string | True | Signature S (hex). |
| signature.v | integer | True | Signature V (27 or 28). |
| signature.expiration | string | True | Signature expiry, unix nanoseconds (string). Capped at 30 days. |
| signature.nonce | integer | True | Random uint32 used in the signed message. See the idempotency note below. |
| signature.chain_id | integer | False | EIP-712 domain chain id. Omit or 0 to use the GRVT chain id for the environment. |
Example Request
{
"quote_id": "6a7d8c6f61855ec660c84db8",
"signature": {
"signer": "0x9fE4000000000000000000000000000000007b21",
"r": "0xb788d96f...",
"s": "0x3d2c1e9a...",
"v": 28,
"expiration": "1754971200000000000",
"nonce": 828283
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| quote_id | string | Withdrawal identifier (same value as the executed quote); use for status polling. |
| tx_id | string | GRVT transfer id, for cross-referencing transfer history. |
| state | string | Always SUBMITTED on acceptance. |
| receive_amount | string | Locked receive amount of token_out, echoed from the quote. |
| fee | string | Locked fee in token_in, echoed from the quote. |
| fee_usd | string | Fee in USD (informational). |
Example Response — 202 Accepted
{
"quote_id": "6a7d8c6f61855ec660c84db8",
"tx_id": "214491152",
"state": "SUBMITTED",
"receive_amount": "997.8",
"fee": "2.2",
"fee_usd": "2.2"
}
Common failures
An expired quote must be re-quoted and retried. Execution is also rejected when the destination is not in the address book, KYA screening flags the address, the balance is insufficient, a daily limit is exceeded, or the signature does not match the quoted amount/account.
Withdrawal Status
GET /api/v1/bridge/withdrawal-status?quote_id=<quote_id>
Returns the current state of a withdrawal, addressed by the same quote_id used across the flow. Poll every few seconds until a terminal state.
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| quote_id | string | True | The withdrawal identifier returned by the quote / withdraw calls. |
Response Fields
| Field | Type | Description |
|---|---|---|
| quote_id | string | Withdrawal identifier. |
| tx_id | string | GRVT transfer id. |
| state | string | CREATED | SUBMITTED | SENDING | COMPLETED | FAILED | REFUNDED (see lifecycle below). |
| chain | string | Destination chain. |
| token_out | string | Payout token. |
| receive_amount | string | Locked receive amount of token_out. |
| withdraw_tx_hash | string | Destination-chain payout transaction hash; set once COMPLETED. |
| updated_at | string | Last state change, ISO-8601. |
Example Response
{
"quote_id": "6a7d8c6f61855ec660c84db8",
"tx_id": "214491152",
"state": "SENDING",
"chain": "ARBITRUM",
"token_out": "USDT",
"receive_amount": "997.8",
"withdraw_tx_hash": "0xYourPayoutTxHash",
"updated_at": "2026-08-13T09:24:05Z"
}
State lifecycle
CREATED— quote created but not yet executed.SUBMITTED— funds debited on GRVT; bridge processing the withdrawal.SENDING— bridge accepted the withdrawal; payout transaction in flight.COMPLETED— payout confirmed on the destination chain (withdraw_tx_hashset).FAILED/REFUNDED— terminal failure states; contact support with thequote_idif unclear.
Unknown identifiers
An unknown quote_id, or one belonging to another account, returns 404.
Signing the Transfer Message
The withdrawal is authorized by the same EIP-712 Transfer message and Signature object used by the standard GRVT transfer API — existing transfer signing code works unchanged. The destination (toAccount) is the funding_address from Withdrawal Info. The signature does not reference the quote — it authorizes the movement of funds; the quote fixes the price.
This withdrawal is executed as an external transfer, so the External Transfer permission applies (not the Withdraw permission used by native on-chain withdrawals).
Transfer message fields
| Field | Type | Description |
|---|---|---|
| fromAccount | address | Your main (funding) account address. |
| fromSubAccount | uint | 0 — withdrawals are from the funding account. |
| toAccount | address | funding_address from Withdrawal Info. |
| toSubAccount | uint | 0. |
| tokenCurrency | uint | GRVT currency enum id of token_in (e.g. 3 for USDT). |
| numTokens | uint | Amount in raw units (USDT/USDC: 6 decimals). Must equal the quote's pay_amount. |
| expiration | int64 | Signature deadline, unix-nanosecond timestamp (e.g. now + 24h). |
| nonce | uint32 | Random 32-bit value, unique per withdrawal. |
Chain ID
The EIP-712 domain.chainId must be the GRVT L2 Chain ID for your target environment (the same value used elsewhere in this guide). Refer to the Chain IDs table.
Integration Notes
- Amounts: all amounts (token and USD) are decimal strings to preserve precision — do not parse them as floating-point numbers.
- Quote lifetime: quotes expire in roughly one minute. Sign and call
withdrawpromptly after quoting; on an expired quote simply create a new one (pricing may change slightly) and retry. - Idempotency: reuse the same
signature/noncewhen retrying a timed-outwithdrawcall for the samequote_id; resubmitting with the same nonce is treated as a duplicate and ignored, whereas a new nonce would be treated as a new withdrawal. - Timing: payouts normally complete in under 5 minutes. A quote with
speed = "slow"indicates temporary congestion and longer settlement. - Limits: withdrawals are subject to per-account daily limits and bridge liquidity limits, enforced at both quote and execution time.
Appendix: Supported Chains
The chains GRVT supports per environment. Pass these names as chain wherever an API takes one.
Query at runtime
This list is configuration and can change without a doc update. Treat Get Deposit Addresses and Withdrawal Info as the authoritative source for what an account can use right now, including the tokens available on each chain.
Production
| Chain | Note |
|---|---|
| ARBITRUM | |
| BASE | |
| BSC | |
| KAIA | |
| POLYGON | |
| TRON | |
| SOLANA | No deposit address is generated by Get Deposit Addresses — call Generate Deposit Address to provision one. |
Testnet
| Chain | Note |
|---|---|
| BASE_SEPOLIA | |
| BSC_TEST | |
| KAIA_TEST | |
| POLYGON_TEST | |
| TRON_TEST | |
| SOLANA_TEST | No deposit address is generated by Get Deposit Addresses — call Generate Deposit Address to provision one. |