Skip to main content

Raw signing API

For integrations that build and broadcast their own transactions (perps venues, bridges, DEX order signing), ZenVault signs through /v1/signing with the MPC keys of one of your workspace accounts (accountId); you cannot sign for vaults you don't own.

All POSTs are asynchronous and honor Idempotency-Key. The response is a signing request in state pending; poll GET /v1/signing/{id} (~2s interval) until a terminal state. result carries the signature once state is completed.

pendingcompletedfailedcancelledtimed_out

(completed is the success terminal; failed / cancelled / timed_out are the failure terminals.)

EndpointSignsresult shape
POST /v1/signing/eth-signA 32-byte hash (hash){ hash, signature }
POST /v1/signing/personal-signEIP-191 message (data: UTF-8 or 0x-hex){ hash, signature }
POST /v1/signing/eth-sign-typed-dataEIP-712 payload (data: JSON string, version: v1/v3/v4){ hash, signature }
POST /v1/signing/eth-sign-transactionA full EVM tx (to, value, gasLimit, nonce, data?, gasPrice or maxFeePerGas+maxPriorityFeePerGas){ txHash, signedTransaction, signature }
POST /v1/signing/mpc-signRaw payloads (signAlg: secp256k1/ed25519, dataList: [{ data }]) — e.g. Solana tx messages{ signatures: [{ data, signature }] }

All EVM endpoints take chainId. ZenVault does not run an RPC client for this path: you supply gas, nonce and chainId, and you broadcast the returned signedTransaction.

:::warning You own the authorization ZenVault signs exactly what you submit — there is no amount/destination policy on raw signing the way there is on transfers. Gate calls behind your own allowlist (contract addresses, function selectors, max value) before they leave your service. Screening still applies to eth-sign-transaction. :::

Example — sign & broadcast a raw EVM tx

REQ=$(curl -s -X POST "$BASE/v1/signing/eth-sign-transaction" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"accountId":"<ACCOUNT_ID>","chainId":42161,"to":"0x...","value":"0",
"gasLimit":120000,"nonce":7,"data":"0x095ea7b3...",
"maxFeePerGas":"30000000000","maxPriorityFeePerGas":"1500000000"}')
ID=$(echo "$REQ" | jq -r .id)

# poll until completed, then broadcast result.signedTransaction via your own RPC
curl -s "$BASE/v1/signing/$ID" -H "Authorization: Bearer $KEY" | jq .result