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,
      }),
    ]),
  );
};

Account Info

Measure

Unit

Calculation

Margin Balance

USDC

Balance + Unsettled PnL + Unrealized PnL + Pending Funding Fees

Unsettled PnL

USDC

Total(Position.Unsettled PnL)

Unrealized PnL

USDC

Total(Position.Unrealized PnL)

Pending Funding Fees

USDC

Total(Position.Pending Funding Fees)

Margin Usage

USDC

Total (Locked Margin by Market)

Locked Margin by Market

USDC

Max(BUY/LONG.Locked Margin, SELL/SHORT.Locked Margin)

Maintenance Margin

USDC

Total(Position.Notional Value * MMR)

Margin Ratio

%

Maintenance Margin / Margin Balance

Fund Available

USDC

Margin Balance - Margin Usage

Perpetual Postition

Item

Description

Note

Market

BTC-PERP

Base Token

BTC

Quote Token

USDC

Base Amount

Amount of BTC

Long: Base Amount > 0

Short: Base Amount < 0

Quote Amount

Amount of USDC

Long: Quote Amount < 0 Short: Quote Amount > 0

Avg Entry Price

(-1) * Quote Amount / Base Amount

Notional Size

Direction * Amount * Mark Price

Direction: Long (1) and Short (-1)

IMR

Notional Size * IMF

IMF: Initial Margin Fraction

MMR

Notional Size * MMF

MMF: Maintenance Margin Fraction

Unrealized PNL

(Mark Price - Entry Price) * Amount * Direction

Unsettled PnL

Realized PnL is added to Position.Unsettled PnL

(Exit Price - Entry Price) * Amount * Direction

Pending Funding Fees

(-1) * Amount * (Market.Cumulative Funding - Position.Last Cumulative Funding)

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 quantity

  • decrease_take: This mode prevents a trade by immediately revoking the taker order's remaining quantity

  • expire_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"
        ]
    }
]'
  • 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"
        ]
    }
]'
  • 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"
        ]
    }
]'
  • 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"
        ]
    }
]'
  • 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"
        ]
    }
]'
  • 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"
        ]
    }
]'
  • 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"
        ]
    }
]'
  • 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"
    ]
}'
  • 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"
	]
}'
  • 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"
    ]
}'
  • 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
    ]
}'
  • 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"
        ]
    }
]'
  • 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

Get Trading Config

  • Request

    • Endpoint: Perpetual RPC

    • Method: core_get_trading_config

    • Params: []

curl --location 'https://testnet-rpc.foundation.network/perpetual' \
--header 'Content-Type: application/json' \
--data '[
    {
        "id": "1",
        "jsonrpc": "2.0",
        "method": "core_get_trading_config",
        "params": []
    }
]'
  • Response

[
    {
        "jsonrpc": "2.0",
        "id": "1",
        "result": {
            "chain_id": 123420001197,
            "endpoint": "0x405e51e50c69c8479a90468004505c34928b391f",
            "offchain_book": "0xd0177370b57537779135ee02984a1c1962d5ec2b"
        }
    }
]

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: 123420001197,
  version: "0.1.0"
  verifyingContract: "0xd0177370b57537779135ee02984a1c1962d5ec2b"
}

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_trading_config',
      params: [],
    }),
  })
  const data = await res.json()
  return data.result as Config
}

const place = async (marketId: number, chainId: string, 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: chainId,
      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 = {
  chainId: string
  endpoint: string
  offchainBook: string
}

const main = async () => {
  const config: Config = await getConfig()
  const marketId = 1 //get from get_open_markets
  place(marketId, config.chainId, config.offchainBook)
}

main()

Last updated