User guides

Signing

Signing is handled internally when you instantiate the NadoClient (nado_protocol.client.NadoClient) with a signer. Alternatively, you can construct the requisite signatures for each execute using a set utils provided by the SDK (see nado_protocol.contracts.eip712 for details).

Note

Check out our docs to learn more about signing requests in Nado.

EIP-712

Nado executes are signed using EIP-712 signatures. The following components are needed:

  • types: The solidity object name and field types of the message being signed.

  • primaryType: The name of the solidity object being signed.

  • domain: A protocol-specific object that includes the verifying contract and chain-id of the network.

  • message: The actual message being signed.

You can build the expected EIP-712 typed data for each execute via nado_protocol.contracts.eip712.build_eip712_typed_data()

Place Order Example:

>>> import time
>>> from nado_protocol.contracts.types import NadoExecuteType
>>> from nado_protocol.engine_client.types import OrderParams, SubaccountParams
>>> from nado_protocol.utils import subaccount_to_bytes32, to_x18, to_pow_10, get_expiration_timestamp, gen_order_nonce, OrderType
>>> from nado_protocol.utils.order import build_appendix, gen_order_verifying_contract
>>> from nado_protocol.contracts.eip712 import build_eip712_typed_data
>>>
>>> # For place orders, use product-specific verifying contract
>>> product_id = 1
>>> verifying_contract = gen_order_verifying_contract(product_id)  # "0x0000000000000000000000000000000000000001"
>>> chain_id = 421613
>>> sender = SubaccountParams(subaccount_owner="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", subaccount_name="default")
>>> order_nonce = gen_order_nonce()
>>> order_expiration = get_expiration_timestamp(40)
>>> appendix = build_appendix(OrderType.POST_ONLY)
>>> order = OrderParams(amount=to_x18(20000), priceX18=to_pow_10(1, 17), expiration=order_expiration, nonce=order_nonce, sender=sender, appendix=appendix)
>>> order_typed_data = build_eip712_typed_data(NadoExecuteType.PLACE_ORDER, order.dict(), verifying_contract, chain_id)

Other Execute Types Example:

>>> from nado_protocol.contracts.types import NadoExecuteType
>>> from nado_protocol.engine_client.types import CancelOrdersParams
>>> from nado_protocol.contracts.eip712 import build_eip712_typed_data
>>>
>>> # For non-place-order executes, use main endpoint verifying contract
>>> endpoint_verifying_contract = "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"  # from get_contracts()
>>> chain_id = 421613
>>> cancel_params = CancelOrdersParams(sender=sender, productIds=[1], digests=["0x..."], nonce=1)
>>> cancel_typed_data = build_eip712_typed_data(NadoExecuteType.CANCEL_ORDERS, cancel_params.dict(), endpoint_verifying_contract, chain_id)

The following object is generated and can be signed via nado_protocol.contracts.eip712.sign_eip712_typed_data():

{
    'types': {
        'EIP712Domain': [
            {'name': 'name', 'type': 'string'},
            {'name': 'version', 'type': 'string'},
            {'name': 'chainId', 'type': 'uint256'},
            {'name': 'verifyingContract', 'type': 'address'}
        ],
        'Order': [
            {'name': 'sender', 'type': 'bytes32'},
            {'name': 'priceX18', 'type': 'int128'},
            {'name': 'amount', 'type': 'int128'},
            {'name': 'expiration', 'type': 'uint64'},
            {'name': 'nonce', 'type': 'uint64'},
            {'name': 'appendix', 'type': 'uint128'}
        ]
    },
    'primaryType': 'Order',
    'domain': {
        'name': 'Nado',
        'version': '0.0.1',
        'chainId': 421613,
        'verifyingContract': '0x0000000000000000000000000000000000000001'  # Product-specific for place orders
    },
    'message': {
        'sender': b'\xf3\x9f\xd6\xe5\x1a\xad\x88\xf6\xf4\xcej\xb8\x82ry\xcf\xff\xb9"fdefault\x00\x00\x00\x00\x00',
        'nonce': 1768628938411606731,
        'priceX18': 100000000000000000,
        'amount': 20000000000000000000000,
        'expiration': 1686695965,
        'appendix': 0
    }
}

Verifying Contracts

Important: Different execute types use different verifying contracts for signatures:

  • Place Order (`PLACE_ORDER`): Uses a product-specific verifying contract generated via nado_protocol.utils.order.gen_order_verifying_contract(product_id)

    from nado_protocol.utils.order import gen_order_verifying_contract
    verifying_contract = gen_order_verifying_contract(1)  # "0x0000000000000000000000000000000000000001"
    
  • All other executes (CANCEL_ORDERS, WITHDRAW_COLLATERAL, etc.): Use the main endpoint verifying contract from nado_protocol.engine_client.EngineQueryClient.get_contracts()

    contracts = client.context.engine_client.get_contracts()
    verifying_contract = contracts.endpoint_addr
    

Note

  • You can retrieve the main endpoint verifying contracts using nado_protocol.engine_client.EngineQueryClient.get_contracts(). Provided via client.context.engine_client.get_contracts() on a NadoClient instance.

  • You can also just use the engine client’s sign utility nado_protocol.engine_client.EngineExecuteClient.sign(). Provided via client.context.engine_client.sign() on a NadoClient instance.

TWAP and Trigger Orders

The SDK provides comprehensive support for Time-Weighted Average Price (TWAP) orders and conditional price trigger orders through the nado_protocol.trigger_client module.

TWAP Orders

TWAP (Time-Weighted Average Price) orders allow you to execute large trades over time with controlled slippage and timing. This is particularly useful for:

  • Dollar Cost Averaging (DCA): Building positions gradually over time

  • Large Order Execution: Minimizing market impact when trading large amounts

  • Automated Trading: Setting up systematic trading strategies

Basic TWAP Order:

from nado_protocol.trigger_client import TriggerClient
from nado_protocol.trigger_client.types import TriggerClientOpts
from nado_protocol.utils.math import to_x18

# Create trigger client
trigger_client = TriggerClient(
    opts=TriggerClientOpts(url=TRIGGER_BACKEND_URL, signer=private_key)
)

# Place a TWAP order to buy 5 BTC over 10 hours
# Uses smart defaults: expiration auto-calculated, nonce auto-generated,
# sender defaults to client's signer address + "default" subaccount
twap_result = trigger_client.place_twap_order(
    product_id=1,
    price_x18=str(to_x18(50_000)),
    total_amount_x18=str(to_x18(5)),
    times=10,
    slippage_frac=0.005,
    interval_seconds=3600,
)

Flexible Sender Parameters:

The SDK provides three ways to specify the sender/subaccount for orders:

# Option 1: Use defaults (simplest)
# Defaults to client's signer address + "default" subaccount
trigger_client.place_twap_order(
    product_id=1,
    price_x18=str(to_x18(50_000)),
    total_amount_x18=str(to_x18(5)),
    times=10,
    slippage_frac=0.005,
    interval_seconds=3600,
)

# Option 2: Specify subaccount parameters
# Allows custom subaccount_owner and subaccount_name
trigger_client.place_twap_order(
    product_id=1,
    price_x18=str(to_x18(50_000)),
    total_amount_x18=str(to_x18(5)),
    times=10,
    slippage_frac=0.005,
    interval_seconds=3600,
    subaccount_owner="0x123...",
    subaccount_name="trading",
)

# Option 3: Provide sender directly (for advanced use cases)
# Sender can be a hex string or SubaccountParams
trigger_client.place_twap_order(
    product_id=1,
    sender="0xabcd...",  # 32-byte hex sender
    price_x18=str(to_x18(50_000)),
    total_amount_x18=str(to_x18(5)),
    times=10,
    slippage_frac=0.005,
    interval_seconds=3600,
)

TWAP with Custom Amounts:

For advanced strategies, you can specify custom amounts for each execution:

custom_amounts = [
    str(to_x18(2)),
    str(to_x18(1.5)),
    str(to_x18(1)),
    str(to_x18(0.5)),
]

custom_twap_result = trigger_client.place_twap_order(
    product_id=1,
    price_x18=str(to_x18(51_000)),
    total_amount_x18=str(to_x18(5)),
    times=4,
    slippage_frac=0.01,
    interval_seconds=1800,
    custom_amounts_x18=custom_amounts,
)

Price Trigger Orders

Price trigger orders are conditional orders that execute when specific price conditions are met. Common use cases include:

  • Stop-Loss Orders: Automatically close positions to limit losses

  • Take-Profit Orders: Automatically realize gains at target prices

  • Breakout Trading: Enter positions when price breaks key levels

  • Automated Risk Management: Set up protective orders

Supported Trigger Types:

  • "last_price_above": Trigger when last traded price goes above threshold

  • "last_price_below": Trigger when last traded price goes below threshold

  • "oracle_price_above": Trigger when oracle price goes above threshold

  • "oracle_price_below": Trigger when oracle price goes below threshold

  • "mid_price_above": Trigger when mid price (bid+ask)/2 goes above threshold

  • "mid_price_below": Trigger when mid price (bid+ask)/2 goes below threshold

Stop-Loss Example:

# Stop-loss order: sell if price drops below $45k
# Uses smart defaults: expiration defaults to 7 days, nonce auto-generated
stop_loss = trigger_client.place_price_trigger_order(
    product_id=1,
    price_x18=str(to_x18(44_000)),
    amount_x18=str(-to_x18(1)),
    trigger_price_x18=str(to_x18(45_000)),
    trigger_type="last_price_below",
    reduce_only=True,
)

Take-Profit Example:

# Take-profit order: sell if price rises above $55k
take_profit = trigger_client.place_price_trigger_order(
    product_id=1,
    price_x18=str(to_x18(56_000)),
    amount_x18=str(-to_x18(1)),
    trigger_price_x18=str(to_x18(55_000)),
    trigger_type="last_price_above",
    reduce_only=True,
)

Complete Trading Strategy

Here’s how to implement a complete automated trading strategy combining multiple order types:

# 1. Protective stop-loss
stop_loss = trigger_client.place_price_trigger_order(
    product_id=1,
    price_x18=str(to_x18(44_000)),
    amount_x18=str(-to_x18(2)),
    trigger_price_x18=str(to_x18(45_000)),
    trigger_type="last_price_below",
    reduce_only=True,
)

# 2. Profit-taking target
take_profit = trigger_client.place_price_trigger_order(
    product_id=1,
    price_x18=str(to_x18(58_000)),
    amount_x18=str(-to_x18(2)),
    trigger_price_x18=str(to_x18(57_000)),
    trigger_type="last_price_above",
    reduce_only=True,
)

# 3. Gradual position building with TWAP
dca_strategy = trigger_client.place_twap_order(
    product_id=1,
    price_x18=str(to_x18(52_000)),
    total_amount_x18=str(to_x18(10)),
    times=20,
    slippage_frac=0.005,
    interval_seconds=1800,
)

Note

Smart Defaults:

  • expiration: TWAP orders default to (times - 1) * interval_seconds + 24 hours. Price trigger orders default to 7 days.

  • nonce: Auto-generated using gen_order_nonce() if not provided.

  • sender: Defaults to client’s signer address + “default” subaccount. Can be customized via sender, subaccount_owner, or subaccount_name parameters.

  • reduce_only: Defaults to False.

Best Practices for TWAP and Trigger Orders:

  • Use reduce_only=True for risk management orders (stop-loss, take-profit)

  • Set appropriate slippage_frac values (0.5-1% is common for liquid markets)

  • Consider market hours and liquidity when setting interval_seconds

  • Override default expiration times if needed for specific strategies

  • Test strategies with small amounts before scaling up