POST /action
Execute trading actions using protobuf-encoded messages. This endpoint handles all authenticated operations including session creation, order placement, and cancellation.
Request
Headers:
| Name | Value |
|---|---|
Content-Type | application/octet-stream |
Body:
The request body is a binary payload containing:
- Varint-encoded length of the Action message
- Serialized Action protobuf message
- Ed25519 signature (64 bytes)
┌─────────────────┬────────────────────────┬─────────────────┐
│ Length (varint) │ Action (protobuf) │ Signature (64B) │
└─────────────────┴────────────────────────┴─────────────────┘Protobuf Schema
Download the schema from https://zo-devnet.n1.xyz/schema.proto
Generate bindings:
# Python
curl https://zo-devnet.n1.xyz/schema.proto -o schema.proto
protoc --python_out=. schema.protoAction Types
CreateSession
Creates an authenticated session for trading. Must be signed with user wallet key.
message CreateSession {
bytes user_pubkey = 1; // 32-byte user public key
bytes session_pubkey = 2; // 32-byte session public key
int64 expiry_timestamp = 3; // Session expiration (Unix seconds)
}PlaceOrder
Places a new order. Must be signed with session key.
message PlaceOrder {
uint64 session_id = 1;
uint32 market_id = 2;
Side side = 3; // ASK = 0, BID = 1
FillMode fill_mode = 4; // LIMIT = 0, POST_ONLY = 1, IMMEDIATE_OR_CANCEL = 2, FILL_OR_KILL = 3
uint64 price = 5; // Price * 10^priceDecimals
uint64 size = 6; // Size * 10^sizeDecimals
}CancelOrder
Cancels an existing order. Must be signed with session key.
message CancelOrder {
uint64 session_id = 1;
uint64 order_id = 2;
}Signing
User Signing (for CreateSession)
Sign the hex-encoded message bytes:
import binascii
def user_sign(key, msg: bytes) -> bytes:
"""Sign using user wallet key (hex-encoded)."""
payload = binascii.hexlify(msg)
return key.sign(payload)Session Signing (for PlaceOrder, CancelOrder)
Sign the raw message bytes:
def session_sign(key, msg: bytes) -> bytes:
"""Sign using session key (raw bytes)."""
return key.sign(msg)Example: Python
Complete example for creating a session and placing an order:
import requests
import binascii
from google.protobuf.internal import encoder, decoder
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import schema_pb2 # Generated from schema.proto
API_URL = "https://zo-devnet.n1.xyz"
def get_varint_bytes(value):
return encoder._VarintBytes(value)
def read_varint(buffer, offset=0):
return decoder._DecodeVarint32(buffer, offset)
def execute_action(action, signing_key, sign_func):
"""Serialize, sign, and send an action."""
payload = action.SerializeToString()
length_prefix = get_varint_bytes(len(payload))
message = length_prefix + payload
signature = sign_func(signing_key, message)
resp = requests.post(
f"{API_URL}/action",
data=message + signature,
headers={"Content-Type": "application/octet-stream"}
)
resp.raise_for_status()
# Parse receipt
msg_len, pos = read_varint(resp.content, 0)
receipt = schema_pb2.Receipt()
receipt.ParseFromString(resp.content[pos:pos + msg_len])
return receipt
# Create session
def create_session(user_key, session_key, server_time):
action = schema_pb2.Action()
action.current_timestamp = server_time
action.nonce = 0
action.create_session.user_pubkey = user_key.public_key().public_bytes_raw()
action.create_session.session_pubkey = session_key.public_key().public_bytes_raw()
action.create_session.expiry_timestamp = server_time + 3600
def user_sign(key, msg):
return key.sign(binascii.hexlify(msg))
receipt = execute_action(action, user_key, user_sign)
return receipt.create_session_result.session_id
# Place order
def place_order(session_key, session_id, market_id, side, price, size):
action = schema_pb2.Action()
action.current_timestamp = int(requests.get(f"{API_URL}/timestamp").json())
action.place_order.session_id = session_id
action.place_order.market_id = market_id
action.place_order.side = side
action.place_order.fill_mode = schema_pb2.FillMode.LIMIT
action.place_order.price = price
action.place_order.size = size
receipt = execute_action(action, session_key, lambda k, m: k.sign(m))
return receiptResponse
Returns a protobuf Receipt message:
message Receipt {
uint64 action_id = 1;
oneof kind {
Error err = 32;
CreateSessionResult create_session_result = 33;
PlaceOrderResult place_order_result = 34;
CancelOrderResult cancel_order_result = 35;
// ... other results
}
}Error Handling
Check the err field for error codes:
if receipt.HasField("err"):
error_name = schema_pb2.Error.Name(receipt.err)
print(f"Action failed: {error_name}")Common errors:
INSUFFICIENT_FUNDS- Not enough balanceINVALID_SIGNATURE- Signature verification failedSESSION_EXPIRED- Session has expiredORDER_NOT_FOUND- Order doesn’t exist (for cancel)
Last updated on