Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Transactions

Every write to Parcl Chain is a signed transaction carrying exactly one action. There is no atomic multi-instruction batching.

Signing runs inside a secure enclave. Your private key is generated and held inside the enclave and cannot be exported. Each transaction is signed in response to an authenticated request from you: your trading UI session or your API key. Without that authentication nobody can produce a signature for your account, including Parcl.

Submitting a transaction

Two ways to authorize the enclave to sign:

  1. Trading UI. Log in at devnet.v4.parcl.co. Your authenticated session authorizes the enclave to sign the transactions you submit through the app. This is the default flow for interactive trading.
  2. API key. Send the unsigned transaction body to POST /tx/sign-and-submit with your X-API-Key header. The key is your authorization token. See Authentication for key generation, scope, and rate limits.

Request body

When submitting via POST /tx/sign-and-submit:

{
  "transaction": { "PlaceOrder": { ... } },
  "nonce": 1713456789000,
  "timestamp": 1713456789
}
FieldWho sets itDescription
transactionYouThe action. Exactly one variant from the list below.
nonceYouu64. Used for dedup. Any value as long as you do not reuse it for the same signer.
timestampYouUnix seconds. Rejected if too far from chain time.

When you submit the request, your authentication unlocks the enclave to sign. The enclave adds your signer pubkey and the Ed25519 signature to the payload, then the transaction reaches the validator.

One action per transaction

The transaction field holds a single variant, not an array. To perform two actions, submit two transactions. They may land in the same block but are processed sequentially, not atomically.

This is a deliberate design choice. It simplifies the mempool and matching order. It also means Solana-style atomic compound patterns (e.g., "place a buy and a sell in one signed payload") do not port over.

Field shape

The validator verifies signatures by re-serializing the transaction with serde_json and comparing bytes against the signed payload. This means the body you sign must include every field the variant declares. For optional fields you are not setting, send null rather than omitting the key. Omitting the key produces different bytes on the validator side after deserialize/re-serialize, and the signature will not verify (HTTP 400 "invalid signature").

The canonical bodies for each user-accessible variant are below. Copy these as a starting point.

PlaceOrder

{
  "transaction": {
    "PlaceOrder": {
      "account_id": 27,
      "market_id": 2,
      "side": "Long",
      "order_type": "Limit",
      "price": 28000000000,
      "size": 100000,
      "trigger_price": null,
      "reduce_only": false,
      "post_only": true,
      "time_in_force": "GTC",
      "take_profit": null,
      "stop_loss": null
    }
  },
  "nonce": 1777725953635,
  "timestamp": 1777725953
}
FieldTypeNotes
account_idu64Your trading account id.
market_idu16Target market.
side"Long" | "Short"
order_type"Limit" | "Market" | "StopLimit" | "StopMarket"
priceu64Price with PRICE_EXPO = -8 (i.e. 28000000000 = $280.00000000). For market orders, send 0.
sizeu64Size with SIZE_EXPO = -6 (i.e. 100000 = 0.1 units).
trigger_priceu64 | nullRequired for stop variants; send null otherwise.
reduce_onlybool
post_onlybool
time_in_force"GTC" | "IOC" | "FOK"
take_profitobject | nullAttached take-profit. Send null if not setting one. See attached order shape.
stop_lossobject | nullAttached stop-loss. Send null if not setting one. Same shape as take_profit.
max_slippage_bpsu16, optionalSlippage cap for market orders, in basis points. Omit the key entirely when unset; do not send null. Including null here will fail signature verification.
margin_mode"Cross" | "Isolated", optionalDefaults to "Cross". Omit the key when using the default; only include it when setting "Isolated". See Margin for the mode comparison.
leverageu16, optionalUser-selected leverage. 0 (or omitted) means "market max." Capped at floor(10000 / IMR_bps) per market. Omit when zero, same byte-symmetry rule as margin_mode.

Attached take-profit / stop-loss

When you set take_profit or stop_loss, use this shape:

{
  "trigger_price": 30000000000,
  "order_type": "StopMarket",
  "limit_price": null
}

limit_price is required and is null for StopMarket, set for StopLimit.

CancelOrder

{
  "transaction": {
    "CancelOrder": {
      "order_id": 12345
    }
  },
  "nonce": 1777725953636,
  "timestamp": 1777725953
}

CancelAllOrders

{
  "transaction": {
    "CancelAllOrders": {
      "market_id": null
    }
  },
  "nonce": 1777725953637,
  "timestamp": 1777725953
}

market_id filters cancellations to one market when set; null cancels every open order on the account. Send the field either way.

ModifyOrder

{
  "transaction": {
    "ModifyOrder": {
      "order_id": 12345,
      "new_price": 28100000000,
      "new_size": null
    }
  },
  "nonce": 1777725953638,
  "timestamp": 1777725953
}

new_price and new_size are independently optional. Send the keys you are not changing as null. At least one must be set.

AdjustIsolatedMargin

Add or remove collateral on an open isolated position's bucket. Positive delta moves cross collateral into the bucket (lowers effective leverage). Negative delta moves bucket collateral back to cross (raises effective leverage).

{
  "transaction": {
    "AdjustIsolatedMargin": {
      "account_id": 27,
      "market_id": 2,
      "delta": 50000000
    }
  },
  "nonce": 1777725953639,
  "timestamp": 1777725953
}
FieldTypeNotes
account_idu64The account holding the isolated position.
market_idu16The market the position is in.
deltai128Signed USDC lamports (scale 6). +50000000 adds $50 to the bucket; -50000000 removes $50.

Rejected if there's no isolated position on the market, if a positive delta exceeds free cross collateral, or if a negative delta would drop the bucket below the position's initial-margin requirement at its selected leverage. See Margin.

User transaction types

Five transaction types are available to signers today:

VariantPurpose
PlaceOrderSubmit a market, limit, stop market, or stop limit order.
CancelOrderCancel one order by ID.
CancelAllOrdersCancel all your open orders, optionally filtered by market.
ModifyOrderChange price or size of a resting order without losing queue priority when possible.
AdjustIsolatedMarginAdd or remove collateral on an open isolated position's bucket.

Both the trading UI and API keys submit the same five variants. API key scope is enforced at the REST layer; see Authentication.

Deposits and withdrawals

Bridge deposits are not user-signed transactions. You generate a deposit address in the trading UI, send USDC to it on Solana, and validators attest to the deposit on-chain. The credit appears on your account once attestations from a 2/3 quorum of the validator set agree.

Withdrawals back to Solana go through the trading UI rather than direct transaction submission.

Admin, oracle, bridge, and multisig variants

Other transaction variants exist in the validator for admin operations, oracle price updates, bridge attestations, and multisig coordination. These are restricted to authorized signers and are not accessible to normal accounts.

Nonces

Nonces prevent replay. Use any 64-bit value as long as you do not reuse it for the same signer. Unix-millis (Date.now()) is a fine default. If you run multiple bots against the same API key, coordinate so they do not collide on nonces.

Responses

POST /tx/sign-and-submit returns one of:

{ "status": "success", "hash": "0x...", "events": [ ... ] }
{ "status": "failed", "hash": "0x...", "events": [], "error": "..." }

events is always present (empty on failure). It contains all state changes the transaction produced: fills, order placements, cancellations, liquidations, funding updates, and so on. For a PlaceOrder, this is where you confirm what actually filled.