Appearance
Signing Transactions
Every state-changing action on Blink is submitted as a signed transaction to the sequencer: placing and cancelling orders, moving margin, managing delegations. A transaction wraps a Borsh-encoded RuntimeCall in a signed envelope that carries the signature, a replay-protection value, and fee metadata.
The blink-tx-client crate builds, signs, and submits transactions. The wire format below specifies the bytes a custom client must produce.
Quick start
rust
use blink_tx_client::{TxClient, TxConfig};
use perps_module::CallMessage;
// Derives the address, chain id, and fee defaults from a 32-byte hex key.
let config = TxConfig::default_with_key("<private-key-hex>".to_string());
// Connects to the sequencer over TCP.
let client = TxClient::new_connected("<sequencer-host>", <port>, config).await?;
// Build a call. Fields are documented per call in the API reference.
let call = CallMessage::PlaceOrder { /* ... */ };
// Signs, stamps the uniqueness value, and submits.
let response = client.send_perps_call(call).await?;TxClient exposes send_perps_call, send_spot_call, send_accounts_call, and cancel_all_orders / cancel_all_spot_orders. Each signs and submits in one call.
Transaction format
A signed transaction is Borsh-encoded and submitted as raw bytes. Its fields, in Borsh order:
| Field | Type | Description |
|---|---|---|
signature | secp256k1 signature (64 bytes) | Signature over the signing payload below |
pub_key | secp256k1 public key (65 bytes) | The signer's uncompressed public key |
runtime_call | RuntimeCall | The Borsh-encoded call (accounts, spot, or perps) |
uniqueness | Nonce(u64) or Generation(u64) | See Replay protection |
details | fee and chain metadata | See below |
details:
| Field | Type | Description |
|---|---|---|
max_priority_fee_bips | u64 | Unused. |
max_fee | u128 | Unused. |
gas_limit | Gas or null | Unused. |
chain_id | u64 | Rollup chain id (4321). Used for replay protection. |
Only chain_id is enforced. The fee and gas fields are part of the signed envelope but are not currently used; leave them at the client defaults.
What gets signed
The signature covers the unsigned transaction concatenated with the chain's domain separator:
signing_payload = borsh(unsigned_tx) || CHAIN_HASHunsigned_tx is { runtime_call, uniqueness, details }. Two values provide replay protection:
chain_id(4321), a field insidedetails.CHAIN_HASH, a 32-byte domain separator derived at build time from the rollup's modules and DA configuration. It binds every signature to this exact rollup, so a transaction cannot be replayed on another deployment.
The payload is signed with the account's secp256k1 key.
Keys and addresses
- Curve: secp256k1 (ECDSA), the same scheme as Ethereum.
- Private key: 32 bytes, hex-encoded. A leading
0xis accepted. - Address: a 20-byte Ethereum-style address, hex-encoded (for example
0x9b08ce57a93751ae790698a2c9ebc76a78f23e25). It is the last 20 bytes ofkeccak256over the 64-byte public key, the uncompressed key without its leading0x04byte:keccak256(pub_key[1:])[12:]. This address is used for balances, subaccounts, and delegations.
Replay protection
Every transaction carries a uniqueness value that prevents replay. Two modes exist:
Nonce(u64): a per-account counter that increments by one per transaction.Generation(u64): a timestamp-style value.
Blink uses Generation, set to the current Unix time in microseconds; the client stamps it automatically. This avoids fetching a sequential nonce and prevents contention between concurrent submissions.
A generation must be unique for the account and recent enough to fall within the rollup's generation window, which tolerates minor clock skew. Duplicate or stale values are rejected. A custom client should use a microsecond clock value and retry with a fresh one if a transaction is rejected as stale.
Submitting
TxClient submits over a persistent TCP connection to the sequencer. A custom client sends the raw Borsh-encoded bytes (not hex or base64) to the sequencer's transaction endpoint.
API trading keys
For automated trading, authorize a separate delegate key with an expiry, scoped to trading actions: placing and cancelling orders and managing leverage. Delegate keys cannot withdraw. Sign with the delegate key and set the call's owner field to the account being traded on behalf of. See Sub-Accounts and Delegated Accounts.