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 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

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