Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/cache
/out
rust-toolchain.toml
.DS_Store

**/soljson-latest.js

2 changes: 2 additions & 0 deletions doc/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

* [Welcome to Pod](README.md)
* [Trading Competition](trading-competition/README.md)
* [Support](support/README.md)
* [Reset your wallet after a testnet reset](support/reset-wallet.md)
2 changes: 2 additions & 0 deletions doc/api-reference/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

* [Place a spot order](guides/place-a-spot-order.md)
* [Place a perpetual order](guides/place-a-perpetual-order.md)
* [Submit a batch order](guides/submit-a-batch-order.md)
* [Read market data](guides/read-market-data.md)
* [Bridge to Pod](guides/bridge-to-pod.md)
* [Bridge from Pod](guides/bridge-from-pod.md)
Expand All @@ -27,6 +28,7 @@
kind: openapi
spec: pod-docs
```
* [JSON-RPC Errors](json-rpc-errors.md)
* [Precompiles](applications-precompiles/README.md)
* [Orderbook](applications-precompiles/orderbook.md)
* [Optimistic Auctions](applications-precompiles/wip-optimistic-auctions.md)
Expand Down
21 changes: 21 additions & 0 deletions doc/api-reference/applications-precompiles/orderbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ deadline = ceil((now + LAG) / auction_interval) * auction_interval
See [Batch Deadline](../../protocol/orderbook.md#batch-deadline) in the protocol reference for the full discussion of `deadline` semantics and the trade-offs around `LAG`.
{% endhint %}

### Batch envelope

`submitBatch` packs several single-intent calls (1–64) into a single signed transaction that lands atomically in one auction tick. Each entry in `inner` is the full ABI-encoded calldata of a single-intent function (`submitOrder`, `cancel`, `update`, `submitTrigger`, `deposit`, …) — encoded exactly as a standalone call, including its 4-byte selector. Every sub-intent **must carry the same `deadline`** (the uniform-deadline invariant), and nested batches are rejected. For the full rules and a worked example, see [Submit a batch order](../guides/submit-a-batch-order.md).

### Solidity interface (ABI)

```solidity
Expand Down Expand Up @@ -178,5 +182,22 @@ contract Orderbook {
uint256 amount,
uint128 deadline
) public {}

// --- Batch envelope ---

/**
* @notice Carries multiple single-intent calls in one signed transaction.
* @dev Each `inner[i]` is the full ABI-encoded calldata of one of the other
* single-intent functions on this contract — `submitOrder`, `cancel`,
* `update`, `submitTrigger`, `cancelTrigger`, `updateTrigger`,
* `deposit`, or `withdraw`. The whole envelope is atomic: it lands in a
* single auction tick, so every sub-intent must carry the **same**
* `deadline`. Constraints (enforced at validation):
* - 1 to 64 sub-intents (the cap is configurable by the operator).
* - All sub-intents share one `deadline` (uniform-deadline invariant).
* - Nested batches are rejected — `inner[i]` may not itself be a `submitBatch`.
* @param inner The ABI-encoded calldata of each sub-intent, in order.
*/
function submitBatch(bytes[] calldata inner) public {}
}
```
150 changes: 150 additions & 0 deletions doc/api-reference/guides/submit-a-batch-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Submit a batch order

This guide shows how to send several intents in a single signed transaction using the orderbook's `submitBatch` envelope. A common use is to place an entry order together with its take-profit and stop-loss triggers atomically, so they all target the same auction tick.

For the envelope's rules and constraints, see [Batch envelope](../applications-precompiles/orderbook.md#batch-envelope) in the Orderbook precompile reference. See the [Orderbook precompile reference](../applications-precompiles/orderbook.md) for the timestamp unit, deadline-alignment, and TTL rules that apply to every call below.

Each entry in `inner` is the full ABI-encoded calldata of a single-intent call (`submitOrder`, `submitTrigger`, etc.), and **every sub-intent must carry the same `deadline`** so the whole envelope lands in one tick.

## Steps

1. ABI-encode each single-intent call (entry order, take-profit trigger, stop-loss trigger).
2. Pass them as the `inner` array to `submitBatch`.

The example opens a 5 NVDA long at $140 and arms two position-grouped, reduce-only triggers — a take-profit that sells at $160 and a stop-loss that sells at $120.

{% tabs %}
{% tab title="TypeScript (ethers.js)" %}
```typescript
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://rpc.podtestnet.dev");
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

const ORDERBOOK = "0x50d0000000000000000000000000000000000002";
const abi = [
"function submitOrder(bytes32 orderbookId, int256 size, uint256 price, uint8 orderType, uint128 deadline, uint128 ttl, bool reduceOnly, bool ioc)",
"function submitTrigger(bytes32 orderbookId, int256 size, uint256 limitPrice, uint256 triggerPrice, uint8 triggerType, uint8 grouping, uint128 deadline, uint128 ttl, bool reduceOnly, bool ioc)",
"function submitBatch(bytes[] inner)",
];
const orderbook = new ethers.Contract(ORDERBOOK, abi, wallet);

const nvdaPerpId = "0x0000000000000000000000000000000000000000000000000000000000000007"; // NVDA-USD perp
const now = BigInt(Date.now()) * 1000n; // microseconds

// One shared deadline for the whole envelope — required by submitBatch.
const deadline = now + 10_000_000n;
const ttl = 60n * 1_000_000n;
const size = ethers.parseEther("5"); // +5 NVDA long

// 1. Entry: 5 NVDA long at $140 limit
const entry = orderbook.interface.encodeFunctionData("submitOrder", [
nvdaPerpId, size, ethers.parseEther("140"), 0 /* Limit */, deadline, ttl, false, false,
]);

// 2. Take-profit: sell 5 NVDA when price reaches $160 (reduceOnly, tied to the position)
const takeProfit = orderbook.interface.encodeFunctionData("submitTrigger", [
nvdaPerpId, -size, ethers.parseEther("160"), ethers.parseEther("160"),
0 /* TakeProfit */, 1 /* Position */, deadline, ttl, true, false,
]);

// 3. Stop-loss: sell 5 NVDA when price drops to $120 (reduceOnly, tied to the position)
const stopLoss = orderbook.interface.encodeFunctionData("submitTrigger", [
nvdaPerpId, -size, ethers.parseEther("120"), ethers.parseEther("120"),
1 /* StopLoss */, 1 /* Position */, deadline, ttl, true, false,
]);

// Submit all three as one atomic envelope
const tx = await orderbook.submitBatch([entry, takeProfit, stopLoss]);
console.log("Batch tx:", tx.hash);
```
{% endtab %}

{% tab title="Rust (alloy)" %}
```rust
use alloy::providers::ProviderBuilder;
use alloy::signers::local::PrivateKeySigner;
use alloy::sol;
use alloy::sol_types::SolCall;
use alloy::primitives::{U256, I256, FixedBytes};

sol! {
#[sol(rpc)]
contract Orderbook {
enum OrderType { Limit, Market }
enum TriggerType { TakeProfit, StopLoss }
enum TriggerGrouping { None, Position }
function submitOrder(
bytes32 orderbookId, int256 size, uint256 price,
OrderType orderType, uint128 deadline, uint128 ttl,
bool reduceOnly, bool ioc
) public;
function submitTrigger(
bytes32 orderbookId, int256 size, uint256 limitPrice, uint256 triggerPrice,
TriggerType triggerType, TriggerGrouping grouping, uint128 deadline, uint128 ttl,
bool reduceOnly, bool ioc
) public;
function submitBatch(bytes[] inner) public;
}
}

let signer: PrivateKeySigner = PRIVATE_KEY.parse()?;
let provider = ProviderBuilder::new()
.wallet(signer.clone())
.on_http("https://rpc.podtestnet.dev".parse()?);

let orderbook = Orderbook::new(
"0x50d0000000000000000000000000000000000002".parse()?,
&provider,
);

let nvda_perp_id = FixedBytes::left_padding_from(&[7]); // NVDA-USD perp
let now_us = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_micros() as u128;
let one_e18 = U256::from(10).pow(U256::from(18));

// One shared deadline for the whole envelope — required by submitBatch.
let deadline = now_us + 10_000_000;
let ttl = 60 * 1_000_000;
let size = I256::from_raw(U256::from(5) * one_e18); // +5 NVDA long

// 1. Entry: 5 NVDA long at $140 limit
let entry = Orderbook::submitOrderCall {
orderbookId: nvda_perp_id,
size,
price: U256::from(140) * one_e18,
orderType: Orderbook::OrderType::Limit,
deadline, ttl, reduceOnly: false, ioc: false,
}.abi_encode();

// 2. Take-profit: sell 5 NVDA when price reaches $160 (reduceOnly, tied to the position)
let take_profit = Orderbook::submitTriggerCall {
orderbookId: nvda_perp_id,
size: -size,
limitPrice: U256::from(160) * one_e18,
triggerPrice: U256::from(160) * one_e18,
triggerType: Orderbook::TriggerType::TakeProfit,
grouping: Orderbook::TriggerGrouping::Position,
deadline, ttl, reduceOnly: true, ioc: false,
}.abi_encode();

// 3. Stop-loss: sell 5 NVDA when price drops to $120 (reduceOnly, tied to the position)
let stop_loss = Orderbook::submitTriggerCall {
orderbookId: nvda_perp_id,
size: -size,
limitPrice: U256::from(120) * one_e18,
triggerPrice: U256::from(120) * one_e18,
triggerType: Orderbook::TriggerType::StopLoss,
grouping: Orderbook::TriggerGrouping::Position,
deadline, ttl, reduceOnly: true, ioc: false,
}.abi_encode();

// Submit all three as one atomic envelope
let tx = orderbook
.submitBatch(vec![entry.into(), take_profit.into(), stop_loss.into()])
.send().await?;
println!("Batch tx: {:?}", tx.tx_hash());
```
{% endtab %}
{% endtabs %}
150 changes: 150 additions & 0 deletions doc/api-reference/json-rpc-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# JSON-RPC Errors

Errors returned by the Pod RPC server follow the standard [JSON-RPC 2.0](https://www.jsonrpc.org/specification#error_object) error format. In addition to the standard codes (`-32600`, `-32602`, `-32603`, etc.), Pod defines a small set of domain-specific codes for transaction validation, execution reverts, and account recovery.

## Error response format

Every error is returned as a JSON-RPC 2.0 error object:

```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "transaction validation failed",
"data": "Insufficient balance"
}
}
```

| Field | Type | Description |
| --------- | ---------------- | --------------------------------------------------------------------------- |
| `code` | integer | Numeric error code (see below). |
| `message` | string | Short, human-readable description of the error category. |
| `data` | string / object / array (optional) | Extra context. Its shape depends on the error code — see each entry below. |

## Pod error codes

| Code | Message | Meaning |
| -------- | ----------------------------- | ---------------------------------------------------------------------------------------- |
| `3` | `execution reverted` | A contract-level (enshrined application) execution failed. `data` is an ABI-encoded revert. |
| `-32000` | `transaction validation failed` | A protocol-level validation check failed (nonce, balance, chain ID, gas price, …). |
| `-32003` | `Transaction rejected: …` | A quorum of validators rejected the transaction. |
| `999` | `Account locked` | The account is locked pending recovery. `data` carries the recovery target. |

### `3` — execution reverted

Returned when an enshrined application (the order book, optimistic auctions, ERC-20 tokens, the bridge, or minting) rejects a transaction at execution time. The response mirrors the EIP-1474 / geth-style execution revert so that standard wallets and libraries (viem, ethers, alloy) decode the reason automatically.

`data` is a hex string: the 4-byte `Error(string)` selector (`0x08c379a0`) followed by the ABI-encoded reason string.

```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": 3,
"message": "execution reverted",
"data": "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000204..."
}
}
```

The decoded reason string is one of the contract-level [validation messages](#transaction-validation-messages) below (e.g. `CLOB validation failed: …`).

### `-32000` — transaction validation failed

Returned when a protocol-level check fails before the transaction is attested. `data` is a plain string holding the specific reason — one of the [validation messages](#transaction-validation-messages) below.

```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "transaction validation failed",
"data": "Future nonce: tx nonce 7, expected 5"
}
}
```

### `-32003` — transaction rejected

Returned when at least `f + 1` validators reject a transaction (the rejection quorum). The `message` summarizes the distinct reasons; `data` is an array of objects, one per rejection.

```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32003,
"message": "Transaction rejected: Insufficient balance, Invalid chain ID: 1",
"data": [
{ "error": "Insufficient balance" },
{ "error": "Invalid chain ID: 1" }
]
}
}
```

### `999` — account locked

Returned when you submit a transaction for an account that is locked due to a pending recovery. `data` identifies the recovery target — see [Recover a locked account](guides/recover-locked-account.md).

```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": 999,
"message": "Account locked",
"data": {
"recovery_target": "0x…",
"recovery_target_nonce": 12
}
}
}
```

| `data` field | Type | Description |
| ----------------------- | ------ | ---------------------------------------------------- |
| `recovery_target` | hash | Transaction hash of the recovery target. |
| `recovery_target_nonce` | number | Nonce of the recovery target. |

## Transaction validation messages

The reason carried in `data` (for code `-32000`) or encoded inside the revert (for code `3`) is one of the following. Messages with `{…}` placeholders are filled in with the offending values.

### Protocol-level (returned with code `-32000`)

| Message | When it occurs |
| --------------------------------------------------------------- | --------------------------------------------------------------------- |
| `Invalid chain ID: {chain_id}` | The transaction's chain ID does not match the network. |
| `Transaction is blacklisted` | The transaction is on the blocklist. |
| `Account has pending transaction` | The account already has an unexecuted (pending) transaction. |
| `Future nonce: tx nonce {tx_nonce}, expected {expected}` | The nonce is higher than the account's next expected nonce. |
| `Past nonce: tx nonce {tx_nonce}, expected {expected}` | The nonce is lower than expected (already used). |
| `Insufficient balance` | The account cannot cover the transaction. |
| `Underpriced tx: max_fee_per_gas {max_fee_per_gas} < base fee {base_fee}` | `maxFeePerGas` is below the current base fee. |
| `Recovery validation failed: {reason}` | A recovery transaction failed validation. |

### Contract-level (returned with code `3`, inside the revert)

| Message | When it occurs |
| ------------------------------------------------ | ----------------------------------------------- |
| `CLOB validation failed: {reason}` | Order book transaction validation failed. |
| `Optimistic auction validation failed: {reason}` | Optimistic auction validation failed. |
| `ERC20 validation failed: {reason}` | ERC-20 token operation validation failed. |
| `Bridge validation failed: {reason}` | Bridge / cross-chain operation validation failed. |
| `Mint validation failed: {reason}` | Mint operation validation failed. |

## Standard JSON-RPC codes

Pod also returns the standard codes defined by the JSON-RPC 2.0 specification:

| Code | Meaning | Typical cause |
| -------- | ---------------- | ------------------------------------------------------------------- |
| `-32600` | Invalid Request | Malformed request, or an unauthorized call to an admin-only method. |
| `-32602` | Invalid Params | Parameters could not be parsed (e.g. a malformed hash or raw transaction). |
| `-32603` | Internal Error | An internal server error while servicing the request. |
9 changes: 9 additions & 0 deletions doc/support/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Support

Help with common issues you may run into while using Pod.

{% cards %}
{% card title="Reset your wallet after a testnet reset" href="reset-wallet.md" %}
Fix nonce mismatches and stuck transactions after the Pod testnet is reset.
{% endcard %}
{% endcards %}
Loading
Loading