AA Account Binding
Bind the caller's wallet account to an Account Abstraction (AA) smart account and session key. This is required for programmatic market-making clients that submit their own on-chain close-audit transactions. If the platform runs a backend close-settlement relayer, this binding is not required to close — see Who submits the on-chain close transaction.
The binding is stored per chain_id and user wallet address. After binding,
GET /api/v1/unified/account returns the current aa_smart_account_address
and aa_session_key_address.
Why this is required
Market makers usually trade through API keys and do not have a browser wallet available during normal order flow. The close-audit transaction still needs an AA smart account and session key, so the maker must register them once through the API.
The API key authenticates the request, but the binding itself must be approved
by the owner wallet through an EIP-712 BindAAAccount signature. This prevents
an API key alone from replacing the wallet's AA account.
Who submits the on-chain close transaction
Closing a position settles its collateral and realized PnL on the Vault. The backend always signs the settlement off-chain; the on-chain transaction can be submitted in one of two ways:
- Client-submitted (the flow on this page). The market maker binds an AA smart account + session key (below) and submits the close transaction itself using the session key. Choose this when you run your own submitter and want to control the on-chain step.
- Backend relayer (recommended for unattended bots). When the platform
enables its close-settlement relayer, the backend broadcasts the settlement
transaction itself, using its own signer. In this mode you just close through
the normal trading endpoints — a
reduceOnly/closePositionorder on/fapi/v1/order, or a TP/SL order that the keeper triggers — and the on-chain settlement is completed for you. No UserOp submission and no AA binding are needed for closing.
Whether the relayer is enabled is a platform-side setting that differs per environment; check with the operator. When it is enabled, market makers do not need the binding flow below in order to close positions — closes settle on-chain automatically after the matching engine fills them.
Flow
- Generate or derive the AA smart account and session key off-chain.
- Call
GET /api/v1/account/aa-binding/noncewith HMAC authentication. - Sign the returned
typed_datawith the owner wallet. - Call
POST /api/v1/account/aa-bindingwith the same addresses, nonce, deadline, and EIP-712 signature. - Confirm the binding with
GET /api/v1/account/aa-bindingorGET /api/v1/unified/account.
Get AA Binding Challenge
HTTP Request
GET /api/v1/account/aa-binding/nonce (HMAC SHA256)
Request Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| smart_account_address | STRING | YES | AA smart account address to bind |
| session_key_address | STRING | YES | Session key address used by the AA close-audit flow |
| timestamp | LONG | YES | Unix timestamp in milliseconds |
| signature | STRING | YES | HMAC SHA-256 signature over the query string without signature |
Response Example
{
"chain_id": 43113,
"user_address": "0xf0cb036c96a7118d879cf3a9e39bdc4ee1d1b4d8",
"smart_account_address": "0x0d1ab011cb5c270155127bb2280cc1ad0a0eea1b",
"session_key_address": "0x1111111111111111111111111111111111111111",
"nonce": 7,
"deadline": 1779999999,
"typed_data": {
"primaryType": "BindAAAccount",
"domain": {
"name": "Primit Vault AVAX Fuji",
"version": "1",
"chainId": 43113,
"verifyingContract": "0x0EBfBEA06D8658B76245F3408A1ECfE3f7d3C8d5"
},
"message": {
"user": "0xf0cb036c96a7118d879cf3a9e39bdc4ee1d1b4d8",
"smartAccount": "0x0d1ab011cb5c270155127bb2280cc1ad0a0eea1b",
"sessionKey": "0x1111111111111111111111111111111111111111",
"nonce": "7",
"deadline": "1779999999"
}
}
}
Bind AA Account
HTTP Request
POST /api/v1/account/aa-binding (HMAC SHA256)
For POST requests, the HMAC payload is the query string without signature
followed by the raw JSON request body.
Request Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| timestamp | LONG | YES | Unix timestamp in milliseconds |
| signature | STRING | YES | HMAC SHA-256 signature |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| chain_id | LONG | YES | Must match the API environment chain ID |
| smart_account_address | STRING | YES | AA smart account address |
| session_key_address | STRING | YES | Session key address |
| nonce | LONG | YES | Nonce returned by the challenge endpoint |
| deadline | LONG | YES | Unix seconds deadline returned by the challenge endpoint |
| signature | STRING | YES | EIP-712 BindAAAccount signature from the owner wallet |
Response Example
{
"chain_id": 43113,
"user_address": "0xf0cb036c96a7118d879cf3a9e39bdc4ee1d1b4d8",
"aa_smart_account_address": "0x0d1ab011cb5c270155127bb2280cc1ad0a0eea1b",
"aa_session_key_address": "0x1111111111111111111111111111111111111111"
}
Get Current Binding
HTTP Request
GET /api/v1/account/aa-binding (HMAC SHA256)
Response Example
{
"chain_id": 43113,
"user_address": "0xf0cb036c96a7118d879cf3a9e39bdc4ee1d1b4d8",
"aa_smart_account_address": "0x0d1ab011cb5c270155127bb2280cc1ad0a0eea1b",
"aa_session_key_address": "0x1111111111111111111111111111111111111111"
}
If the user has not bound an AA account, both AA fields are null.
EIP-712 Type
BindAAAccount(
address user,
address smartAccount,
address sessionKey,
uint256 nonce,
uint256 deadline
)
The signer must be the owner wallet represented by the API key. The user
field is the wallet address, not the AA smart account address.
Error Codes
| Code | Meaning |
|---|---|
| CHAIN_ID_MISMATCH | chain_id does not match the API environment |
| INVALID_SMART_ACCOUNT_ADDRESS | smart_account_address is not a valid EVM address |
| INVALID_SESSION_KEY_ADDRESS | session_key_address is not a valid EVM address |
| AA_BINDING_SIGNATURE_EXPIRED | deadline has passed |
| AA_BINDING_NONCE_INVALID | Nonce was not issued, already used, or superseded by a newer challenge |
| AA_BINDING_SIGNATURE_INVALID | EIP-712 signature is invalid or not signed by the owner wallet |
Code Example
import json
import time
import hmac
import hashlib
import requests
from eth_account import Account
from eth_account.messages import encode_typed_data
BASE_URL = "https://api.primit.xyz"
API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
OWNER_PRIVATE_KEY = "0x..."
SMART_ACCOUNT = "0x0d1Ab011CB5c270155127bb2280CC1AD0A0Eea1B"
SESSION_KEY = "0x1111111111111111111111111111111111111111"
def hmac_sign(payload: str) -> str:
return hmac.new(API_SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()
def signed_get(path: str, params: dict):
params["timestamp"] = int(time.time() * 1000)
query = "&".join(f"{k}={v}" for k, v in params.items())
return requests.get(
f"{BASE_URL}{path}?{query}&signature={hmac_sign(query)}",
headers={"X-MBX-APIKEY": API_KEY},
)
challenge = signed_get(
"/api/v1/account/aa-binding/nonce",
{
"smart_account_address": SMART_ACCOUNT,
"session_key_address": SESSION_KEY,
},
).json()
typed_data = challenge["typed_data"]
owner_sig = Account.sign_message(
encode_typed_data(full_message=typed_data),
OWNER_PRIVATE_KEY,
).signature.hex()
body = {
"chain_id": challenge["chain_id"],
"smart_account_address": SMART_ACCOUNT,
"session_key_address": SESSION_KEY,
"nonce": challenge["nonce"],
"deadline": challenge["deadline"],
"signature": owner_sig,
}
body_raw = json.dumps(body, separators=(",", ":"))
query = f"timestamp={int(time.time() * 1000)}"
resp = requests.post(
f"{BASE_URL}/api/v1/account/aa-binding?{query}&signature={hmac_sign(query + body_raw)}",
headers={"X-MBX-APIKEY": API_KEY, "Content-Type": "application/json"},
data=body_raw,
)
print(resp.json())