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.

Hosted accounts sign inside a secure enclave. The enclave generates the hosted key and does not return its plaintext to application servers. Key export requires an authenticated browser session and is blocked for API keys. See authentication.

Agent-key clients hold their own private keys and sign locally. They submit the signed envelope directly to the validator. See agent keys.

Submitting a transaction

Hosted accounts have two ways to authorize the enclave to sign:

  1. Trading UI. Log in at 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 and scope, and request limits for the exact throttles.

Funded account creation

Verify the chain ID through GET /v1/node/info and read account_activation_fee_usdc from GET /v1/treasury-config before depositing. Check GET /v1/accounts/by-owner/{owner}/list for an existing personal account. The owner is the 32-byte signing public key encoded as 64 lowercase hex characters.

Deposit at least 5,000,000 USDC base units through the matching Solana bridge. Read GET /v1/bridge/state?owner={owner} until unclaimed_usdc covers both 5,000,000 base units and the activation fee. Then sign this action with the same owner key, optionally using an eligible personal account ID as referred_by:

{
  "CreateFundedAccount": {
    "mode": "Cross",
    "referred_by": 123
  }
}

Replace 123 with the resolved referrer account ID. Use the signed-envelope format below and submit to the validator. This path requires no email account. The retired CreateReferredAccount action cannot create an account. Omit referred_by to create independently without referral attribution.

Creation consumes the owner's confirmed balance, charges the configured activation fee, and credits the remainder to the new account. A failed attempt leaves the funds available for a corrected request. Before retrying, check GET /v1/accounts/by-owner/{owner}/list for an entry with isVault: false. Read that entry through GET /v1/accounts/{id}. If pendingActivation is false, creation has completed. Otherwise, recheck funding and fee terms before continuing.

Hosted signup applies a separate referral requirement. Email invitations can waive that hosted requirement. Hosted account-creation requests must use the saved referral selection. No cohort proof or membership transaction is part of the signed action.

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.
nonceYouA u64, normally unique Unix milliseconds. Every envelope includes it. See Nonces.
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.

Direct signed envelope

Agent-key clients submit a signed envelope to the validator. This example uses devnet. Mainnet uses parcl-v4:mainnet:chain-1 and devnet uses parcl-v4:devnet:chain-2.

{
  "transaction": { "CancelOrder": { "order_id": 12345 } },
  "chain_id": "parcl-v4:devnet:chain-2",
  "signer": [0, 0, 0],
  "signature": [0, 0, 0],
  "nonce": 1777725953635,
  "timestamp": 1777725953
}

The displayed byte arrays are abbreviated. signer must contain 32 bytes, and signature must contain 64 bytes. Sign these UTF-8 bytes:

JSON.stringify([chainId, transaction, nonce, timestamp])

Pin the expected chain ID in client configuration. Compare it with GET /v1/node/info on the selected validator host before signing. The validator rejects a missing chain ID or a signature for another chain with HTTP 400.

The signed message binds the exact transaction JSON substring. Changing a key, value, field order, or whitespace after signing invalidates the signature. Build the transaction bytes once, sign them, and place those same bytes in the submitted envelope.

One action per transaction

The transaction field holds one variant. To take two actions, submit two transactions. They may land in the same block. The chain processes each action sequentially.

Field shape

The validator authenticates the exact transaction JSON bytes from the signed envelope. It parses and executes the value from those same bytes. A direct client must sign and submit the same representation.

Fields with a null type in the tables below can be null or omitted. Fields that use a protocol default, such as margin_mode and leverage, must be omitted when unset. Sending null for a defaulted non-null field returns HTTP 400 because the transaction cannot be parsed.

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 | null, optionalSlippage cap for market orders, in basis points. null and omission both parse as unset.
margin_mode"Cross" | "Isolated", optionalDefaults to "Cross". Omit the key when using the default. An explicit null returns HTTP 400 during transaction parsing. See Margin.
leverageu16, optionalUser-selected leverage. 0 or omission means market maximum, capped at floor(10000 / IMR_bps). An explicit null returns HTTP 400 during transaction parsing.

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 a required key. Attached exits support order_type: "StopMarket" with limit_price: null. Do not submit an attached StopLimit expecting a resting limit exit. The activation path creates a StopMarket conditional order. Standalone StopLimit orders remain supported through the top-level PlaceOrder.order_type field.

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. Set at least one of them.

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.

The validator rejects the transaction if there is no isolated position on the market, or if a positive delta exceeds free cross collateral. It also rejects a negative delta that would drop the bucket below the position's initial-margin requirement at its selected leverage. See Margin.

Trading transaction types

The trading API uses these five transaction types:

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.

The trading UI and API keys can submit these variants. The UI also uses owner-only and account-management variants for other features. The REST layer enforces API-key scope before signing. See Authentication.

Deposits and withdrawals

Bridge deposits use Solana transactions. Sending USDC to the address shown by the app funds the linked Solana wallet; the user must then submit the bridge deposit instruction before Parcl Chain can credit collateral. See Solana bridge for the app and direct integration flows.

Withdrawals back to Solana go through the trading UI rather than API-key submission. RequestWithdrawal records the token, amount, and immutable Solana destination. USDC is debited from eligible margin collateral. PRCL is debited from your available PRCL balance. The withdrawal window is 24 hours. The request can be cancelled while it is Pending, which restores the exact debit. It cannot be cancelled after it becomes ReadyToClaim. The claim can then be submitted on Solana. The chain rejects any bridge token other than USDC or PRCL.

Nonces

Every signed envelope carries a nonce. Use the current Unix time in milliseconds and keep it unique for the signing key.

For user actions other than CancelOrder, the validator tracks nonces per signing key and accepts one only when all three conditions hold:

  • The nonce falls within (T - 2 days, T + 1 day), where T is the block timestamp in milliseconds. The validator rejects a value far in the past or future.
  • The signer has not used the nonce before.
  • The nonce is greater than the smallest of the signer's 100 most recent nonces (once the signer has sent at least 100). Below that, the validator treats it as stale.

CancelOrder is the user-facing exception. The validator does not commit or check its nonce. Clients must still send the field and should use the same unique time-based allocation.

Set each nonce to the current millisecond. When you send more than one transaction inside the same millisecond, increment the last allocated value so each nonce stays unique and ordered.

Do not seed a counter once at startup and then increment it forever. A transaction-count-based counter can drift outside the accepted window. Read the clock for each allocation.

Each signing key keeps its own nonce window. Agent keys are separate from the account owner's key. Hosted API-key requests use the owner signer and share its window with browser submissions. If several clients share one signer, route them through one allocator or use separate approved agent keys.

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.