Trading APIs
Perpetual Account
Each user wallet that interacts with the Foundation has a unique id called account_id
and that id is 32 bytes with:
First 20 bytes is wallet address
The next 4 bytes is BROKER_ID (default: 1)
The next 5 bytes is reserve (0)
Next byte is product type
Perpetual: 1
Final 2 bytes is account index
One wallet have max 15 perpetual accounts (start from 1)
Account format
export const getAccountId = (address: Address, productType: PRODUCT_TYPE, accountIndex: number) => {
return bytesToHex(
Uint8Array.from([
...hexToBytes(address, {
size: 20,
}),
...numberToBytes(BROKER_ID, {
size: 4,
}),
...numberToBytes(0, {
size: 5,
}),
...numberToBytes(productType, {
size: 1,
}),
...numberToBytes(accountIndex, {
size: 2,
}),
]),
);
};
Perpetual Order
Supported order types
Market
Limit
Post-Only
Take Profit Market
Stop Market
Take Profit Limit
Stop Limit
Strategy (Place order with TP/SL)
Order structure
Name
Type
Description
account_id
string
See account format
market_id
number
Get market_id
from Market Info API
side
string
set to bid
or ask
price
string
order price
amount
string
order amount
time_in_force
string
set to default
, immediate_or_cancel
, fill_or_kill
, post_only
reduce_only
boolean
set to true or
false
expires_at
number
as GTD (good til date) not yet implemented, leave this field with default value 0
is_market_order
boolean
if order is market
or stop_market
set to true
, otherwise set to false
self_trade_behavior
string
set to cancel_provide
(default), decrease_take
, expire_both
trigger_condition
See below
nonce
string
See below
Order nonce
Generated from expired_time << 20 | random_number
expired_time = now + gap (gap should set to equal or above 30s)
Order signature
Place or cancel order require sign order by user wallet or linked signer wallet
Time in Force Option
GTC (default): Order will continue to work until filled or canceled.
IOC (immediate_or_cancel): Order will execute all or partial immediately and cancel any unfilled portion of the order.
FOK (fill_or_kill): Order must be filled immediately in its entirety or not executed at all.
Post Only (post_only): orders will be added to the order book and not execute with a pre-existing order.
Reduce-Only Option
Order serves to strictly reduce your open position
Only supported on FOK or IOC
Close Position will cancel all Trigger Order with Reduce-Only = True
Self Trading Prevention
none:
This mode exempts the order from self-trade prevention. Accounts will not be compared, no orders will be revoked, and the trade will occur.cancel_provide:
This mode prevents a trade by immediately revoking the potential maker order's remaining quantitydecrease_take:
This mode prevents a trade by immediately revoking the taker order's remaining quantityexpire_both:
This mode prevents a trade by immediately revoking both the taker and the potential maker orders' remaining quantities
Place market order
Market order is immediately matched to the best available market price
Time in Force: IOC (default), FOK
Reduce Only: Optional
Request & Response
Request
Endpoint: Perpetual RPC
Method:
ob_place_limit
is_market_order: true
Params:
[order_object, order_signature]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.010",
"price": "98556.3",
"time_in_force": "fill_or_kill",
"reduce_only": false,
"expires_at": null,
"is_market_order": true,
"nonce": "1820392312872633083",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0xf24c7c878421f64387cb04b1c1b0e02b9e2c310c0c4bf2aed5847ca437a05872621d189d355595fdd3ee84e9bb3788ce5b84c9e388a305e93906ae47073744641c"
]
}
]'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.010",
"price": "98556.3",
"time_in_force": "fill_or_kill",
"reduce_only": false,
"expires_at": null,
"is_market_order": true,
"nonce": "1820392312872633083",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0xf24c7c878421f64387cb04b1c1b0e02b9e2c310c0c4bf2aed5847ca437a05872621d189d355595fdd3ee84e9bb3788ce5b84c9e388a305e93906ae47073744641c"
]
}
]);
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.010",
"price": "98556.3",
"time_in_force": "fill_or_kill",
"reduce_only": False,
"expires_at": None,
"is_market_order": True,
"nonce": "1820392312872633083",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": None
},
"0xf24c7c878421f64387cb04b1c1b0e02b9e2c310c0c4bf2aed5847ca437a05872621d189d355595fdd3ee84e9bb3788ce5b84c9e388a305e93906ae47073744641c"
]
}
])
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.010",
"price": "98556.3",
"time_in_force": "fill_or_kill",
"reduce_only": false,
"expires_at": null,
"is_market_order": true,
"nonce": "1820392312872633083",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0xf24c7c878421f64387cb04b1c1b0e02b9e2c310c0c4bf2aed5847ca437a05872621d189d355595fdd3ee84e9bb3788ce5b84c9e388a305e93906ae47073744641c"
]
}
]"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
order_id
[
{
"jsonrpc": "2.0",
"id": "1",
"result": 841787
}
]
Place limit order
A limit order is an order to buy or sell at a specific price or better. Limit orders are not guaranteed to execute
Time in Force: GTC (default), IOC, FOK
Reduce Only: Only supported on FOK or IOC
Request & Response
Request
Endpoint: Perpetual RPC
Method:
ob_place_limit
is_market_order: false
Params:
[order_object, order_signature]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "98000",
"time_in_force": "default",
"reduce_only": false,
"expires_at": null,
"is_market_order": false,
"nonce": "1820392919896425329",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0x16787c587b1e336372600a2cc09a29a51b504462a47210918a648efc8629b34520ff3898786e3c40ab84a24289d8578be44f9c06961c650ee8214f3401d9c6351b"
]
}
]'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "98000",
"time_in_force": "default",
"reduce_only": false,
"expires_at": null,
"is_market_order": false,
"nonce": "1820392919896425329",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0x16787c587b1e336372600a2cc09a29a51b504462a47210918a648efc8629b34520ff3898786e3c40ab84a24289d8578be44f9c06961c650ee8214f3401d9c6351b"
]
}
]);
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "98000",
"time_in_force": "default",
"reduce_only": False,
"expires_at": None,
"is_market_order": False,
"nonce": "1820392919896425329",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": None
},
"0x16787c587b1e336372600a2cc09a29a51b504462a47210918a648efc8629b34520ff3898786e3c40ab84a24289d8578be44f9c06961c650ee8214f3401d9c6351b"
]
}
])
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "98000",
"time_in_force": "default",
"reduce_only": false,
"expires_at": null,
"is_market_order": false,
"nonce": "1820392919896425329",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0x16787c587b1e336372600a2cc09a29a51b504462a47210918a648efc8629b34520ff3898786e3c40ab84a24289d8578be44f9c06961c650ee8214f3401d9c6351b"
]
}
]"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
order_id
[
{
"jsonrpc": "2.0",
"id": "1",
"result": 842648
}
]
Place stop market order
Stop Market Orders become market orders when a specified price is reached. Specify a trigger price to activate the order
Trigger price can be Mark Price or Last Price
Time in Force: IOC (default), FOK
Reduce Only: Optional
Request & Response
Request
Endpoint: Perpetual RPC
Method:
ob_place_limit
is_market_order: true
add trigger_condition
Params:
[order_object, order_signature]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.050",
"price": "98196",
"time_in_force": "fill_or_kill",
"reduce_only": false,
"expires_at": null,
"is_market_order": true,
"nonce": "1820393289179726727",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": {
"mark_price": {
"below": "98000"
}
}
},
"0x3ded0f6deb9318fc267ffe0b5f25e76fd945b5d86ce9e4a316b63346c507add005f1892b4e5eb6e5584e65194e70415844789f872abb7852fa455851408c8a561b"
]
}
]'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.050",
"price": "98196",
"time_in_force": "fill_or_kill",
"reduce_only": false,
"expires_at": null,
"is_market_order": true,
"nonce": "1820393289179726727",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": {
"mark_price": {
"below": "98000"
}
}
},
"0x3ded0f6deb9318fc267ffe0b5f25e76fd945b5d86ce9e4a316b63346c507add005f1892b4e5eb6e5584e65194e70415844789f872abb7852fa455851408c8a561b"
]
}
]);
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.050",
"price": "98196",
"time_in_force": "fill_or_kill",
"reduce_only": False,
"expires_at": None,
"is_market_order": True,
"nonce": "1820393289179726727",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": {
"mark_price": {
"below": "98000"
}
}
},
"0x3ded0f6deb9318fc267ffe0b5f25e76fd945b5d86ce9e4a316b63346c507add005f1892b4e5eb6e5584e65194e70415844789f872abb7852fa455851408c8a561b"
]
}
])
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.050",
"price": "98196",
"time_in_force": "fill_or_kill",
"reduce_only": false,
"expires_at": null,
"is_market_order": true,
"nonce": "1820393289179726727",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": {
"mark_price": {
"below": "98000"
}
}
},
"0x3ded0f6deb9318fc267ffe0b5f25e76fd945b5d86ce9e4a316b63346c507add005f1892b4e5eb6e5584e65194e70415844789f872abb7852fa455851408c8a561b"
]
}
]"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
order_id
[
{
"jsonrpc": "2.0",
"id": "1",
"result": 843252
}
]
Place stop limit order
Stop Limit Orders become limit orders when a specified stop price is reached. Specify a trigger price to activate the order
Trigger price can be Mark Price or Last Price
Time in Force: GTC (default), IOC, FOK
Reduce Only: Only supported on FOK or IOC
Request & Response
Request
Endpoint: Perpetual RPC
Method:
ob_place_limit
is_market_order: false
add trigger_condition
Params:
[order_object, order_signature]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "97500",
"time_in_force": "default",
"reduce_only": false,
"expires_at": null,
"is_market_order": false,
"nonce": "1820393522228888309",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": {
"mark_price": {
"below": "98000"
}
}
},
"0xb2e284d112cbbed092a9cb64b35cd21434d92b181bc848f2383dd7e6621dc36b5c402b6a3b2f66212f07392e25b3ed58afe12fc6a6221e1adecba251df86a65f1b"
]
}
]'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "97500",
"time_in_force": "default",
"reduce_only": false,
"expires_at": null,
"is_market_order": false,
"nonce": "1820393522228888309",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": {
"mark_price": {
"below": "98000"
}
}
},
"0xb2e284d112cbbed092a9cb64b35cd21434d92b181bc848f2383dd7e6621dc36b5c402b6a3b2f66212f07392e25b3ed58afe12fc6a6221e1adecba251df86a65f1b"
]
}
]);
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "97500",
"time_in_force": "default",
"reduce_only": False,
"expires_at": None,
"is_market_order": False,
"nonce": "1820393522228888309",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": {
"mark_price": {
"below": "98000"
}
}
},
"0xb2e284d112cbbed092a9cb64b35cd21434d92b181bc848f2383dd7e6621dc36b5c402b6a3b2f66212f07392e25b3ed58afe12fc6a6221e1adecba251df86a65f1b"
]
}
])
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "97500",
"time_in_force": "default",
"reduce_only": false,
"expires_at": null,
"is_market_order": false,
"nonce": "1820393522228888309",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": {
"mark_price": {
"below": "98000"
}
}
},
"0xb2e284d112cbbed092a9cb64b35cd21434d92b181bc848f2383dd7e6621dc36b5c402b6a3b2f66212f07392e25b3ed58afe12fc6a6221e1adecba251df86a65f1b"
]
}
]"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
order_id
[
{
"jsonrpc": "2.0",
"id": "1",
"result": 843573
}
]
Place post-only order
Post Only orders will be added to the order book and not execute with a pre-existing order.
Reduce Only: not supported
Request & Response
Request
Endpoint: Perpetual RPC
Method:
ob_place_limit
is_market_order: false
time_in_force: post_only
Params:
[order_object, order_signature]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "98000",
"time_in_force": "post_only",
"reduce_only": false,
"expires_at": null,
"is_market_order": false,
"nonce": "1820393736968864321",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0x1fba81235330776eefa23104d44a929093667b5e99e46853b78cfed4757249046d324db887f70ecce7c8154ed4efd002a6e2c22f7038cb7c40d34bb9c2ba355e1c"
]
}
]'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "98000",
"time_in_force": "post_only",
"reduce_only": false,
"expires_at": null,
"is_market_order": false,
"nonce": "1820393736968864321",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0x1fba81235330776eefa23104d44a929093667b5e99e46853b78cfed4757249046d324db887f70ecce7c8154ed4efd002a6e2c22f7038cb7c40d34bb9c2ba355e1c"
]
}
]);
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "98000",
"time_in_force": "post_only",
"reduce_only": False,
"expires_at": None,
"is_market_order": False,
"nonce": "1820393736968864321",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": None
},
"0x1fba81235330776eefa23104d44a929093667b5e99e46853b78cfed4757249046d324db887f70ecce7c8154ed4efd002a6e2c22f7038cb7c40d34bb9c2ba355e1c"
]
}
])
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.051",
"price": "98000",
"time_in_force": "post_only",
"reduce_only": false,
"expires_at": null,
"is_market_order": false,
"nonce": "1820393736968864321",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "bid",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0x1fba81235330776eefa23104d44a929093667b5e99e46853b78cfed4757249046d324db887f70ecce7c8154ed4efd002a6e2c22f7038cb7c40d34bb9c2ba355e1c"
]
}
]"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
order_id
[
{
"jsonrpc": "2.0",
"id": "1",
"result": 843902
}
]
Place reduce-only order
Request
Endpoint: Perpetual RPC
Method:
ob_place_limit
reduce-only: true
Params:
[order_object, order_signature]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.050",
"price": "98122.1",
"time_in_force": "immediate_or_cancel",
"reduce_only": true,
"expires_at": null,
"is_market_order": true,
"nonce": "1820394153563914700",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "ask",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0xd6751325eafe91f7ef80bc09118d897903d437f9bc2d7571020f93269083ff0a3701ce93d0d8b229f3f536074b2e3820c768a23b0e5df3d66151da0225bc54331c"
]
}
]'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.050",
"price": "98122.1",
"time_in_force": "immediate_or_cancel",
"reduce_only": true,
"expires_at": null,
"is_market_order": true,
"nonce": "1820394153563914700",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "ask",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0xd6751325eafe91f7ef80bc09118d897903d437f9bc2d7571020f93269083ff0a3701ce93d0d8b229f3f536074b2e3820c768a23b0e5df3d66151da0225bc54331c"
]
}
]);
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.050",
"price": "98122.1",
"time_in_force": "immediate_or_cancel",
"reduce_only": True,
"expires_at": None,
"is_market_order": True,
"nonce": "1820394153563914700",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "ask",
"self_trade_behavior": "cancel_provide",
"trigger_condition": None
},
"0xd6751325eafe91f7ef80bc09118d897903d437f9bc2d7571020f93269083ff0a3701ce93d0d8b229f3f536074b2e3820c768a23b0e5df3d66151da0225bc54331c"
]
}
])
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_limit",
"params": [
{
"market_id": 1,
"amount": "0.050",
"price": "98122.1",
"time_in_force": "immediate_or_cancel",
"reduce_only": true,
"expires_at": null,
"is_market_order": true,
"nonce": "1820394153563914700",
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"side": "ask",
"self_trade_behavior": "cancel_provide",
"trigger_condition": null
},
"0xd6751325eafe91f7ef80bc09118d897903d437f9bc2d7571020f93269083ff0a3701ce93d0d8b229f3f536074b2e3820c768a23b0e5df3d66151da0225bc54331c"
]
}
]"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
order_id
[
{
"jsonrpc": "2.0",
"id": "1",
"result": 844496
}
]
Place order with TP/SL
When place Market Order, Limit Order, Post-Only Order with TP/SL option, Order can be called as Strategy Order.
Common Rule
Whenever Origin Order partially or fully executed, Trigger Order (TP/SL) should be created
Trigger price of TP/SL can be Mark Price or Last Price
Origin Order → follow Market Order, Limit Order, Post-Only Order
Trigger Order → follow Stop Order
Request & Response
Request
Endpoint: Perpetual RPC
Method:
ob_place_strategy
Params:
[account_id, market_id, strategy_type, orders_object]
orders_object: [{order_object, order_signature},{tp_order_object, tp_order_singature},{sl_order_object, sl_order_signature]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_strategy",
"params": [
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"strategy_type": "tpsl",
"orders": [
{
"strategy_sub_id": 1,
"driven_id": 0,
"amount": "0.051",
"price": "98000",
"expiration": {
"reduce_only": false,
"time_in_force": "default",
"expires_at": null,
"is_market_order": false,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": null,
"side": "bid",
"signature": "0xa8d9251351e61be723cd3591208e0c29a715b320550f8592003030a4f67f3191517874912b76c919b1d637df8c6689c371e2e253c4a6ce791d057ccb43f9ebad1c",
"nonce": "1820394395800699743"
},
{
"strategy_sub_id": 2,
"driven_id": 1,
"amount": "0.051",
"price": "98802",
"expiration": {
"reduce_only": true,
"time_in_force": "immediate_or_cancel",
"expires_at": null,
"is_market_order": true,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": {
"mark_price": {
"above": "99000"
}
},
"side": "ask",
"signature": "0xf6c4c1999aadb47a8f32ef9f5505c3f384d0f8d7a31196c6abcec8d618afeded321100de38d876b33ceb3e86c941070c4b2662d7de631526cdc021eeb28c774e1c",
"nonce": "1820394395823768547"
},
{
"strategy_sub_id": 3,
"driven_id": 1,
"amount": "0.051",
"price": "96806",
"expiration": {
"reduce_only": true,
"time_in_force": "immediate_or_cancel",
"expires_at": null,
"is_market_order": true,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": {
"mark_price": {
"below": "97000"
}
},
"side": "ask",
"signature": "0x14842eef7da4efbf5cbed9b0c480d272c6037a7828b6f47943d22c7d859100642c82b2fe6d17d23c13fdf50c13352f91959db30921a7607ce1a6f5d91f2b51fe1c",
"nonce": "1820394395824816323"
}
]
},
"0xE76658E1015AEe26DE26D1c32C8712792659cBC0"
]
}
]'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_strategy",
"params": [
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"strategy_type": "tpsl",
"orders": [
{
"strategy_sub_id": 1,
"driven_id": 0,
"amount": "0.051",
"price": "98000",
"expiration": {
"reduce_only": false,
"time_in_force": "default",
"expires_at": null,
"is_market_order": false,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": null,
"side": "bid",
"signature": "0xa8d9251351e61be723cd3591208e0c29a715b320550f8592003030a4f67f3191517874912b76c919b1d637df8c6689c371e2e253c4a6ce791d057ccb43f9ebad1c",
"nonce": "1820394395800699743"
},
{
"strategy_sub_id": 2,
"driven_id": 1,
"amount": "0.051",
"price": "98802",
"expiration": {
"reduce_only": true,
"time_in_force": "immediate_or_cancel",
"expires_at": null,
"is_market_order": true,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": {
"mark_price": {
"above": "99000"
}
},
"side": "ask",
"signature": "0xf6c4c1999aadb47a8f32ef9f5505c3f384d0f8d7a31196c6abcec8d618afeded321100de38d876b33ceb3e86c941070c4b2662d7de631526cdc021eeb28c774e1c",
"nonce": "1820394395823768547"
},
{
"strategy_sub_id": 3,
"driven_id": 1,
"amount": "0.051",
"price": "96806",
"expiration": {
"reduce_only": true,
"time_in_force": "immediate_or_cancel",
"expires_at": null,
"is_market_order": true,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": {
"mark_price": {
"below": "97000"
}
},
"side": "ask",
"signature": "0x14842eef7da4efbf5cbed9b0c480d272c6037a7828b6f47943d22c7d859100642c82b2fe6d17d23c13fdf50c13352f91959db30921a7607ce1a6f5d91f2b51fe1c",
"nonce": "1820394395824816323"
}
]
},
"0xE76658E1015AEe26DE26D1c32C8712792659cBC0"
]
}
]);
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_strategy",
"params": [
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"strategy_type": "tpsl",
"orders": [
{
"strategy_sub_id": 1,
"driven_id": 0,
"amount": "0.051",
"price": "98000",
"expiration": {
"reduce_only": False,
"time_in_force": "default",
"expires_at": None,
"is_market_order": False,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": None,
"side": "bid",
"signature": "0xa8d9251351e61be723cd3591208e0c29a715b320550f8592003030a4f67f3191517874912b76c919b1d637df8c6689c371e2e253c4a6ce791d057ccb43f9ebad1c",
"nonce": "1820394395800699743"
},
{
"strategy_sub_id": 2,
"driven_id": 1,
"amount": "0.051",
"price": "98802",
"expiration": {
"reduce_only": True,
"time_in_force": "immediate_or_cancel",
"expires_at": None,
"is_market_order": True,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": {
"mark_price": {
"above": "99000"
}
},
"side": "ask",
"signature": "0xf6c4c1999aadb47a8f32ef9f5505c3f384d0f8d7a31196c6abcec8d618afeded321100de38d876b33ceb3e86c941070c4b2662d7de631526cdc021eeb28c774e1c",
"nonce": "1820394395823768547"
},
{
"strategy_sub_id": 3,
"driven_id": 1,
"amount": "0.051",
"price": "96806",
"expiration": {
"reduce_only": True,
"time_in_force": "immediate_or_cancel",
"expires_at": None,
"is_market_order": True,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": {
"mark_price": {
"below": "97000"
}
},
"side": "ask",
"signature": "0x14842eef7da4efbf5cbed9b0c480d272c6037a7828b6f47943d22c7d859100642c82b2fe6d17d23c13fdf50c13352f91959db30921a7607ce1a6f5d91f2b51fe1c",
"nonce": "1820394395824816323"
}
]
},
"0xE76658E1015AEe26DE26D1c32C8712792659cBC0"
]
}
])
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_place_strategy",
"params": [
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"strategy_type": "tpsl",
"orders": [
{
"strategy_sub_id": 1,
"driven_id": 0,
"amount": "0.051",
"price": "98000",
"expiration": {
"reduce_only": false,
"time_in_force": "default",
"expires_at": null,
"is_market_order": false,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": null,
"side": "bid",
"signature": "0xa8d9251351e61be723cd3591208e0c29a715b320550f8592003030a4f67f3191517874912b76c919b1d637df8c6689c371e2e253c4a6ce791d057ccb43f9ebad1c",
"nonce": "1820394395800699743"
},
{
"strategy_sub_id": 2,
"driven_id": 1,
"amount": "0.051",
"price": "98802",
"expiration": {
"reduce_only": true,
"time_in_force": "immediate_or_cancel",
"expires_at": null,
"is_market_order": true,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": {
"mark_price": {
"above": "99000"
}
},
"side": "ask",
"signature": "0xf6c4c1999aadb47a8f32ef9f5505c3f384d0f8d7a31196c6abcec8d618afeded321100de38d876b33ceb3e86c941070c4b2662d7de631526cdc021eeb28c774e1c",
"nonce": "1820394395823768547"
},
{
"strategy_sub_id": 3,
"driven_id": 1,
"amount": "0.051",
"price": "96806",
"expiration": {
"reduce_only": true,
"time_in_force": "immediate_or_cancel",
"expires_at": null,
"is_market_order": true,
"self_trade_behavior": "cancel_provide"
},
"trigger_condition": {
"mark_price": {
"below": "97000"
}
},
"side": "ask",
"signature": "0x14842eef7da4efbf5cbed9b0c480d272c6037a7828b6f47943d22c7d859100642c82b2fe6d17d23c13fdf50c13352f91959db30921a7607ce1a6f5d91f2b51fe1c",
"nonce": "1820394395824816323"
}
]
},
"0xE76658E1015AEe26DE26D1c32C8712792659cBC0"
]
}
]"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
order_id
[
{
"jsonrpc": "2.0",
"id": "1",
"result": 844873
}
]
Get account info
To query account info and open positions
Request
Endpoint: Perpetual RPC
Method:
core_query_account
Params:
[account_id]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '{
"id": "1",
"jsonrpc": "2.0",
"method": "core_query_account",
"params": [
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000"
]
}'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Cookie", "__cf_bm=H8.fc.44_06n.i3By3Ymg_P7h6svzD6wvdRFYhWhvqY-1736050486-1.0.1.1-o7Hv.SA0ocdEwvNyWkZDtg.WMk_huxF1j7GLeDsD8GK_XM92KQWTePUSzWKwrFAM.p0VV69Ilgaft40gRWxy_A");
const raw = JSON.stringify({
"id": "1",
"jsonrpc": "2.0",
"method": "core_query_account",
"params": [
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000"
]
});
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps({
"id": "1",
"jsonrpc": "2.0",
"method": "core_query_account",
"params": [
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000"
]
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"{
"id": "1",
"jsonrpc": "2.0",
"method": "core_query_account",
"params": [
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000"
]
}"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
[
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"positions": {
"1": {
"base_amount": "2.721",
"quote_amount": "-266045.73920000",
"last_cumulative_funding": "26.96182176",
"frozen_in_bid_order": "185952.0000",
"frozen_in_ask_order": "0.0000",
"unsettled_pnl": "0.00000000",
"is_settle_pending": false
},
"2": {
"base_amount": "-2.44",
"quote_amount": "8879.69710000",
"last_cumulative_funding": "0.90739023",
"frozen_in_bid_order": "0.0000",
"frozen_in_ask_order": "27153.1424",
"unsettled_pnl": "0.00000000",
"is_settle_pending": false
}
},
"collateral": "11400.57733820",
"is_in_liquidation_queue": false
}
}
]
Get all accounts state
To get all accounts info and open positions
Request
Endpoint: Rest API
Method:
POST/asset/v1/account-states
Params:
[accounts]
curl --location 'https://testnet-api.foundation.network/asset/v1/account-states' \
--header 'Content-Type: application/json' \
--data '{
"accounts": [
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010001",
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010002"
]
}'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify({
"accounts": [
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010001",
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010002"
]
});
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-api.foundation.network/asset/v1/account-states", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-api.foundation.network/asset/v1/account-states"
payload = json.dumps({
"accounts": [
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010001",
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010002"
]
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"{
"accounts": [
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010001",
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010002"
]
}"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-api.foundation.network/asset/v1/account-states")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
[
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"collateral": "11397.86202396",
"positions": {
"1": {
"base_amount": "2.721",
"quote_amount": "-266045.73920000",
"last_cumulative_funding": "26.96182176",
"frozen_in_bid_order": "185952.0000",
"frozen_in_ask_order": "0.0000",
"unsettled_pnl": "0.00000000",
"is_settle_pending": false
},
"2": {
"base_amount": "-9.90",
"quote_amount": "36032.83950000",
"last_cumulative_funding": "0.90739023",
"frozen_in_bid_order": "0.0000",
"frozen_in_ask_order": "0.0000",
"unsettled_pnl": "0.00000000",
"is_settle_pending": false
}
},
"lp_shares": [],
"spot_balance": {},
"bonding_balance": {},
"fund_balance": {}
},
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010001",
"collateral": "2804.21495328",
"positions": {
"1": {
"base_amount": "-1.431",
"quote_amount": "140454.47620000",
"last_cumulative_funding": "25.73416851",
"frozen_in_bid_order": "0.000",
"frozen_in_ask_order": "0.0000",
"unsettled_pnl": "0.00000000",
"is_settle_pending": false
}
},
"lp_shares": [],
"spot_balance": {},
"bonding_balance": {},
"fund_balance": {}
},
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010002",
"collateral": "6118.52559873",
"positions": {
"1": {
"base_amount": "3.147",
"quote_amount": "-308936.59410000",
"last_cumulative_funding": "25.73416851",
"frozen_in_bid_order": "0.0000",
"frozen_in_ask_order": "0.000",
"unsettled_pnl": "0.00000000",
"is_settle_pending": false
}
},
"lp_shares": [],
"spot_balance": {},
"bonding_balance": {},
"fund_balance": {}
}
]
Get orders by account
Request
Endpoint: Perpetual RPC
Method:
ob_query_user_orders
Params:
[market_id, account_id]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_query_user_orders",
"params": [
1,
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000"
]
}'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify({
"id": "1",
"jsonrpc": "2.0",
"method": "ob_query_user_orders",
"params": [
1,
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000"
]
});
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps({
"id": "1",
"jsonrpc": "2.0",
"method": "ob_query_user_orders",
"params": [
1,
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000"
]
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_query_user_orders",
"params": [
1,
"0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000"
]
}"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
{
"jsonrpc": "2.0",
"id": "1",
"result": [
{
"order_id": 713917,
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"side": "bid",
"create_timestamp": 1735998838448,
"amount": "1.937",
"price": "96000",
"status": "placed",
"matched_quote_amount": "0",
"matched_base_amount": "0",
"quote_fee": "0",
"nonce": 1820326748339830949,
"expiration": {
"time_in_force": "default",
"reduce_only": false,
"self_trade_behavior": "cancel_provide",
"expires_at": null,
"is_market_order": false
},
"trigger_condition": null,
"is_triggered": true,
"signature": "1455886ba08019ee73dee2b44b919cda48db59dfddc8c81dbb18a7c39c5e71fe4bd9e2810895db3ee1fcb00ffb7a5df34345b8425e8246af491177e24367e5e01b",
"signer": "0xe76658e1015aee26de26d1c32c8712792659cbc0",
"has_dependency": false,
"source": "Trade",
"system_fee_tier": {
"maker_fee": "0.00010000",
"taker_fee": "0.00035000"
},
"broker_fee_tier": {
"maker_fee": "0",
"taker_fee": "0"
},
"tag": "limit"
},
{
"order_id": 823030,
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"side": "ask",
"create_timestamp": 1736051023508,
"amount": "2.721",
"price": "98802",
"status": "placed",
"matched_quote_amount": "0",
"matched_base_amount": "0",
"quote_fee": "0",
"nonce": 1820381468082308038,
"expiration": {
"time_in_force": "immediate_or_cancel",
"reduce_only": true,
"self_trade_behavior": "cancel_provide",
"expires_at": null,
"is_market_order": true
},
"trigger_condition": {
"mark_price": {
"above": "99000"
}
},
"is_triggered": false,
"signature": "23aa84250edd7dd2757db4150f3899e597732056404f3ec196e199437c27a4cc69a721626e6467155f263d47735f54204322992f387407d83ffce798651557af1c",
"signer": "0xe76658e1015aee26de26d1c32c8712792659cbc0",
"has_dependency": false,
"source": "Trade",
"system_fee_tier": {
"maker_fee": "0.00010000",
"taker_fee": "0.00035000"
},
"broker_fee_tier": {
"maker_fee": "0",
"taker_fee": "0"
},
"tag": "take_profit_market"
}
]
}
Get order by id
Request
Endpoint: Perpetual RPC
Method:
ob_query_order
Params:
[market_id, order_id]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_query_order",
"params": [
1,
713917
]
}'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify({
"id": "1",
"jsonrpc": "2.0",
"method": "ob_query_order",
"params": [
1,
713917
]
});
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps({
"id": "1",
"jsonrpc": "2.0",
"method": "ob_query_order",
"params": [
1,
713917
]
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_query_order",
"params": [
1,
713917
]
}"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"order_id": 713917,
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"side": "bid",
"create_timestamp": 1735998838448,
"amount": "1.937",
"price": "96000",
"status": "placed",
"matched_quote_amount": "0",
"matched_base_amount": "0",
"quote_fee": "0",
"nonce": 1820326748339830949,
"expiration": {
"time_in_force": "default",
"reduce_only": false,
"self_trade_behavior": "cancel_provide",
"expires_at": null,
"is_market_order": false
},
"trigger_condition": null,
"is_triggered": true,
"signature": "1455886ba08019ee73dee2b44b919cda48db59dfddc8c81dbb18a7c39c5e71fe4bd9e2810895db3ee1fcb00ffb7a5df34345b8425e8246af491177e24367e5e01b",
"signer": "0xe76658e1015aee26de26d1c32c8712792659cbc0",
"has_dependency": false,
"source": "Trade",
"system_fee_tier": {
"maker_fee": "0.00010000",
"taker_fee": "0.00035000"
},
"broker_fee_tier": {
"maker_fee": "0",
"taker_fee": "0"
},
"tag": "limit"
}
}
Cancel order
Request
Endpoint: Perpetual RPC
Method:
ob_cancel
Params:
[account_id, market_id, order_id]
curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_cancel",
"params": [
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"order_id": "844873",
"nonce": "1820395302902825280"
},
"0xdc33df753909092f334390a0591b57e8a57ac1c694e4b76b64129d6114cb1a762d205bf6cff1eeec7f4974b4afdf1216ebab27cd194f3139e4d66f4b295b1e841c"
]
}
]'
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_cancel",
"params": [
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"order_id": "844873",
"nonce": "1820395302902825280"
},
"0xdc33df753909092f334390a0591b57e8a57ac1c694e4b76b64129d6114cb1a762d205bf6cff1eeec7f4974b4afdf1216ebab27cd194f3139e4d66f4b295b1e841c"
]
}
]);
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://testnet-rpc.foundation.network/perpetual", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
import requests
import json
url = "https://testnet-rpc.foundation.network/perpetual"
payload = json.dumps([
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_cancel",
"params": [
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"order_id": "844873",
"nonce": "1820395302902825280"
},
"0xdc33df753909092f334390a0591b57e8a57ac1c694e4b76b64129d6114cb1a762d205bf6cff1eeec7f4974b4afdf1216ebab27cd194f3139e4d66f4b295b1e841c"
]
}
])
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.build()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
let data = r#"[
{
"id": "1",
"jsonrpc": "2.0",
"method": "ob_cancel",
"params": [
{
"account_id": "0xb0477aa910d2a70647782afb91ba3477b8963a2e000000010000000000010000",
"market_id": 1,
"order_id": "844873",
"nonce": "1820395302902825280"
},
"0xdc33df753909092f334390a0591b57e8a57ac1c694e4b76b64129d6114cb1a762d205bf6cff1eeec7f4974b4afdf1216ebab27cd194f3139e4d66f4b295b1e841c"
]
}
]"#;
let json: serde_json::Value = serde_json::from_str(&data)?;
let request = client.request(reqwest::Method::POST, "https://testnet-rpc.foundation.network/perpetual")
.headers(headers)
.json(&json);
let response = request.send().await?;
let body = response.text().await?;
println!("{}", body);
Ok(())
}
Response
order_id
[
{
"jsonrpc": "2.0",
"id": "1",
"result": 844873
}
]
Take profit & Stop loss
To taker profit or stop loss for opening position, place trigger order with reduce-only order
Take profit & Stop loss market -> Place stop market order with reduce-only option
Take profit & Stop loss limit -> Place stop limit order with reduce-only option
Close position
To close opening position, use reduce-only order
Close market -> Place market order with reduce-only option
Close limit -> Place limit order with reduce-only option
Signing Structure
Order signature using EIP712 as standard for signing, that mean user must sign the structured data not the plaint text message
Order Placement Signature
{
domain: domain,
message: signing_order,
primaryType: "Order",
types: {
Order: [
{ name: 'subaccount', type: 'bytes32' },
{ name: 'market', type: 'uint64' },
{ name: 'price', type: 'int128' },
{ name: 'amount', type: 'int128' },
{ name: 'nonce', type: 'uint64' },
{ name: 'expiration', type: 'uint64' },
{ name: 'triggerCondition', type: 'uint128' }
]
}
}
Order Cancellation Signature
{
domain: domain, // same as place an order
message: canceling_order, // below schema
primaryType: "Cancel",
types: {
Cancel: [
{ name: 'subaccount', type: 'bytes32' },
{ name: 'market', type: 'uint64' },
{ name: 'nonce', type: 'uint64' },
{ name: 'orderId', type: 'uint64' },
]
}
}
Domain schema
{
name: "FOUNDATION",
chainId: 1,
version: "0.1.0"
verifyingContract: "0xfe85512651accf738e072a24d2e1a7448b7461be"
}
Code example
import { encodePacked, parseEther, parseUnits, toBytes, toHex } from 'viem'
import { Address, generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'
const getOrderNonce = () => {
const expireTime = Date.now() + 30_000
const randomNumber = Math.floor(Math.random() * 1001)
return (BigInt(expireTime) << BigInt(20)) | BigInt(randomNumber)
}
const getAccountId = (address: Address, subAccount: number) => {
const brokerId = 1
const productType = 1
return encodePacked(
['address', 'bytes4', 'bytes5', 'bytes1', 'bytes2'],
[
address,
toHex(toBytes(brokerId, { size: 4 })),
toHex(0n, { size: 5 }),
toHex(toBytes(productType, { size: 1 })),
toHex(toBytes(subAccount, { size: 2 })),
]
)
}
const pk = '0x0' // replace this
const account = privateKeyToAccount(pk)
const accountId = getAccountId(account.address, 1) // first sub account
const endpoint = 'https://testnet-rpc.foundation.network/perpetual/'
const getConfig = async () => {
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: '1',
jsonrpc: '2.0',
method: 'core_get_config',
params: [],
}),
})
const data = await res.json()
return data.result as Config
}
const place = async (marketId: number, offchainBook: string) => {
// ASSUMPTION YOUR ACCOUNT HAS FUND
const amount = '0.01' // always round to step size
const price = '65000.1' // always round to tick size
const nonce = getOrderNonce()
const isLong = false
const timeInForce = TimeInForce.IOC
const reduceOnly = false
const isMarket = false
const expiresAt = 0
const selfTradeBehavior = SelfTradeBehavior.CancelProvide
// sign message
const signature = await account.signTypedData({
domain: {
name: 'FOUNDATION',
chainId: 1,
version: '0.1.0',
verifyingContract: offchainBook as Address,
},
message: {
subaccount: accountId,
market: BigInt(marketId),
nonce: nonce,
price: parseEther(price),
amount: isLong ? parseEther(amount) : -parseEther(amount),
expiration: getExpiration({
expiresAt,
timeInForce,
reduceOnly,
isMarket,
selfTradeBehavior,
}),
triggerCondition: 0n,
},
primaryType: 'Order',
types: {
Order: [
{ name: 'subaccount', type: 'bytes32' },
{ name: 'market', type: 'uint64' },
{ name: 'price', type: 'int128' },
{ name: 'amount', type: 'int128' },
{ name: 'nonce', type: 'uint64' },
{ name: 'expiration', type: 'uint64' },
{ name: 'triggerCondition', type: 'uint128' },
],
},
})
//
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: '1',
jsonrpc: '2.0',
method: 'ob_place_limit',
params: [
{
market_id: marketId,
amount: amount.toString(),
price: price.toString(),
time_in_force: timeInForce,
reduce_only: reduceOnly,
expires_at: expiresAt || null,
is_market_order: isMarket,
nonce: nonce.toString(),
account_id: accountId,
side: isLong ? 'bid' : 'ask',
self_trade_behavior: selfTradeBehavior,
trigger_condition: null,
},
signature,
],
}),
})
const response = await res.json()
console.log(`placed order: ${JSON.stringify(response)}`)
}
enum TimeInForce {
GTC = 'default',
IOC = 'immediate_or_cancel',
FOK = 'fill_or_kill',
PostOnly = 'post_only',
}
enum SelfTradeBehavior {
CancelProvide = 'cancel_provide',
DecreaseTake = 'decrease_take',
ExpireBoth = 'expire_bot',
}
type GetExpirationProps = {
timeInForce: TimeInForce
reduceOnly: boolean
isMarket: boolean
expiresAt: number
selfTradeBehavior: SelfTradeBehavior
}
const getExpiration = ({
expiresAt,
reduceOnly,
timeInForce,
isMarket,
selfTradeBehavior,
}: GetExpirationProps) => {
return (
(BigInt(Object.values(TimeInForce).indexOf(timeInForce)) << 62n) |
((reduceOnly ? 1n : 0n) << 61n) |
((isMarket ? 1n : 0n) << 60n) |
(BigInt(Object.values(SelfTradeBehavior).indexOf(selfTradeBehavior)) <<
58n) |
BigInt(expiresAt)
)
}
type Config = {
addresses: SigningConfig
system_fee_tiers: FeeTier[]
}
type SigningConfig = {
endpoint: string
offchain_book: string
cr_manager: string
}
type FeeTier = {
maker_fee: string
taker_fee: string
}
const main = async () => {
const config: Config = await getConfig()
const marketId = 1 //get from get_open_markets
place(marketId, config.addresses.offchain_book)
}
main()
Last updated