Quick Start
This guide walks through placing your first order on dreamDEX, describing how you can interact with both the HTTP API and on-chain smart contracts to trade tokens.
The guide shows three paths: the dreamDEX CLI for the fastest experience, the HTTP API with curl for full control, and Foundry's cast for direct contract interaction.
Building an automated bot or agent? Use the default wallet funding flow — there is no vault deposit step, just a one-time ERC-20 approval to the pool. See Choose a Funding Source.
Prerequisites
Before you can perform any trades, you need:
- An EVM wallet connected to Somnia mainnet (chain ID
5031). - Tokens to trade (the base token of the market you want to trade on).
- Your private key is assumed to be in your environment as
$PRIVATE_KEY. - An HTTP client that can call REST endpoints; we will assume
curlis on your path. - The dreamDEX CLI for the simplest workflow (
go install github.com/somnia-chain/somnia-dex-cli/cmd/dreamdex@latest), and/or Foundry (cast) for direct contract interaction.
Choose an Environment
Set BASE_URL once and every curl example below targets the right environment. The /v0 path segment is part of the base URL on both environments - omitting it returns a 404.
# Mainnet (Somnia, chain ID 5031)
BASE_URL="https://api.dreamdex.io/v0"
# Testnet (Somnia Shannon, chain ID 50312) - uncomment to use instead
# BASE_URL="https://stg.api.dreamdex.io/v0"
This guide uses mainnet addresses, chain ID, and RPC throughout. To run it against testnet, switch BASE_URL above and substitute the testnet contract addresses, chain ID, and RPC. See the HTTP API base URLs for the full per-environment reference.
Getting testnet funds. Trading on Somnia Shannon testnet (chain ID
50312) needs test funds - no mainnet capital required:
- STT (gas): claim from the Somnia testnet faucet (or the Google Cloud Web3 faucet). You need STT to pay gas for any transaction.
- Test trading tokens (SOMI, WBTC, WETH): mint from the testnet token faucet contract
0x89Ebc05dE83aB9752B95030218BB10A542b96B7CviarequestTokens(address[] tokens, uint256[] amounts)(all 18 decimals).- USDso (the quote token): acquire by swapping from a token you hold on a live testnet market (e.g. sell SOMI on
SOMI:USDso) or via Simple Swap. Testnet books can be thin - if a market is empty, post a resting order and wait, or start from the most active pair.
1. Discover Markets
Fetch the available trading pairs via the Market Data endpoint. This step is required regardless of which path you use - it is the simplest way to obtain the contract and token addresses for a market.
curl $BASE_URL/markets
{
"markets": [
{
"symbol": "WETH:USDso",
"contract": "0xa936da11B57b50A344e1293AAaE5232885ea2bDE",
"base": "0x936Ab8C674bcb567CD5dEB85D8A216494704E9D8",
"quote": "0x00000022dA000002656c64D9eA6011ea952D008A",
"baseDecimals": 18,
"quoteDecimals": 18,
"tickSize": "0.01",
"lotSize": "0.0001",
"minQuantity": "0.001"
}
]
}
Note the contract (Pool address), base and quote (token addresses), decimal counts, and the tickSize, lotSize, and minQuantity constraints — your order parameters must respect these. The values above are illustrative for one pair; each market sets its own, and they can change. Always read them per-market from GET /v0/markets or on-chain getPoolParams() at runtime rather than hard-coding. See Spot Contract Specifications for details on each field.
Respect
minQuantity,lotSize, andtickSize. An order belowminQuantity, or whosequantity/priceis not a whole multiple oflotSize/tickSize, is rejected on-chain.minQuantityis the most common cause of a rejected first order — check it before sizing.
If you are using the dreamDEX CLI, no manual setup is needed - it fetches market metadata automatically:
dreamdex markets
If you are using cast directly, save these values:
POOL="0xa936da11B57b50A344e1293AAaE5232885ea2bDE" # SpotPool (WETH:USDso, Somnia mainnet)
BASE_TOKEN="0x936Ab8C674bcb567CD5dEB85D8A216494704E9D8" # WETH
QUOTE_TOKEN="0x00000022dA000002656c64D9eA6011ea952D008A" # USDso
BASE_DECIMALS=18
QUOTE_DECIMALS=18
RPC="https://api.infra.mainnet.somnia.network"
Native-token markets (SOMI/USDso). The SOMI/USDso pool uses SOMI as the chain's native token. Under the default auto-pull flow,
placeOrderispayableand pulls input frommsg.valuerather than an ERC-20 allowance; for manual vault funding, deposit withdepositNative()andmsg.valueinstead ofapprove+deposit(token, amount). The rest of this guide assumes an ERC-20 base (e.g. WETH); swap in the SOMI/USDso pool address and use the native variants when trading SOMI.
2. Authenticate (HTTP API only)
Skip this step if you are using the dreamDEX CLI or cast - both sign transactions directly with your private key. The CLI handles SIWE authentication automatically; run dreamdex login to import your key on first use, or set DREAMDEX_PRIVATE_KEY in your environment for headless/CI workflows.
If you want to use the HTTP API to construct transactions on your behalf, you will need to authenticate first, to ensure the returned transactions reference your wallet correctly. This process does not cede any control to your wallet; you remain in full control.
dreamDEX supports Sign-In with Ethereum (ERC-4361). First request a nonce, then sign a SIWE message with your wallet and submit it to receive a JWT bearer token. See Authentication for full details.
Request a nonce:
curl $BASE_URL/auth/nonce
{ "nonce": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" }
Sign in:
Construct an ERC-4361 message containing the nonce, sign it with your wallet, and POST both to the login endpoint:
curl -X POST $BASE_URL/auth/login \
-H 'Content-Type: application/json' \
-d '{
"message": "api.dreamdex.io wants you to sign in with your Ethereum account:\n0xYourAddress\n\nSign in to dreamDEX\n\nURI: https://api.dreamdex.io\nVersion: 1\nChain ID: 5031\nNonce: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6\nIssued At: 2026-01-01T00:00:00.000Z",
"signature": "0x..."
}'
{
"token": "eyJhbGciOiJFUzI1NiIs...",
"expiresAt": 1765537769841
}
Include this token in all subsequent HTTP API requests to private endpoints:
TOKEN="eyJhbGciOiJFUzI1NiIs..."
3. Choose a Funding Source
dreamDEX supports two ways to fund orders:
Option A: Wallet Funding (default)
Tokens are pulled directly from your wallet at execution time and proceeds are delivered straight back to it. This is the simplest path - no deposit step needed - but if performing many trades, may cost more in gas fees overall. It supports all order types, including resting limit orders (GTC, PostOnly).
Requirements:
- You must grant the SpotPool contract an ERC-20 allowance to spend your tokens before submitting the order - without this the on-chain transaction will revert. (On native-token markets the input is taken from
msg.valueinstead of an allowance.)
Approve the SpotPool contract to spend your tokens:
Using the HTTP API:
curl -X POST $BASE_URL/markets/WETH:USDso/vault/approve \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"walletAddress": "0xYourAddress",
"currency": "WETH",
"amount": "1"
}'
This returns an unsigned approve(spender, amount) transaction targeting the token contract, signalling that you grant permission for the contract to spend this token on your behalf. You need to sign and broadcast it.
To do this using cast:
# Approve the pool to spend 1 WETH (18 decimals)
cast send $BASE_TOKEN \
"approve(address,uint256)" \
$POOL $(cast to-wei 1) \
--rpc-url $RPC --private-key $PRIVATE_KEY
If you are using the dreamDEX CLI, approval is handled automatically when placing an order (step 4) - skip ahead.
Once confirmed, the SpotPool contract can transfer up to that amount from your wallet when your order executes. Then proceed to step 4.
Option B: Vault Funding
Pre-deposit tokens into the market's on-chain vault and trade against that balance — useful if you keep a working balance in the pool (auto-pull then only tops up any shortfall from your wallet). Market makers and HFT integrators can additionally call setManualVaultMode(true) to settle fills to the vault rather than auto-delivering them to the wallet.
Step 1 - Approve (same as Option A above).
Step 2 - Deposit:
Using the HTTP API:
curl -X POST $BASE_URL/markets/WETH:USDso/vault/deposit \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"walletAddress": "0xYourAddress",
"currency": "WETH",
"amount": "1"
}'
Sign and broadcast the returned transaction, e.g. using cast:
# Deposit 1 WETH into the vault
cast send $POOL \
"deposit(address,uint256)" \
$BASE_TOKEN $(cast to-wei 1) \
--rpc-url $RPC --private-key $PRIVATE_KEY
Using the dreamDEX CLI:
dreamdex vault approve WETH:USDso --currency WETH --amount 1
dreamdex vault deposit WETH:USDso --currency WETH --amount 1
Then proceed to step 4 with vault funding.
4. Place an Order
Option A: Using the HTTP API
Call the prepare order endpoint to get an unsigned transaction:
curl -X POST $BASE_URL/markets/WETH:USDso/orders \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"type": "limit",
"side": "buy",
"price": "2500.00",
"amount": "1",
"walletAddress": "0xYourAddress",
"fundingSource": "wallet",
"orderType": "immediateOrCancel"
}'
The server returns an unsigned EVM transaction:
{
"to": "0xPoolContract",
"data": "0xabcdef...",
"value": "0",
"chainId": "5031"
}
Sign and broadcast it to the Somnia network, e.g. using cast:
cast send \
--to "0xPoolContract" \
--data "0xabcdef..." \
--rpc-url $RPC --private-key $PRIVATE_KEY
Option B: Using the dreamDEX CLI
The CLI handles transaction construction, signing, and broadcasting in a single command:
Wallet funding (default):
dreamdex order place WETH:USDso --side buy --type limit --amount 1 --price 2500
Vault funding:
dreamdex order place WETH:USDso --side buy --type limit --amount 1 --price 2500 \
--funding-source vault --order-type postOnly
The CLI auto-detects whether token approval is needed and submits an approval transaction first if required. Market orders are also supported:
dreamdex order place WETH:USDso --side buy --amount 1 --slippage 0.5
Option C: Using cast
When calling the contract directly, prices and quantities must be in raw on-chain units - the human-readable value multiplied by 10^decimals:
# Price: 2500.00 USDso (18 decimals) → 2500 × 10^18
export PRICE=$(cast to-wei 2500)
# Quantity: 1 WETH (18 decimals) → 1 × 10^18
export QUANTITY=$(cast to-wei 1)
# Expiration: 24 hours from now, in nanoseconds
export EXPIRE_NS=$(( ($(date +%s) + 86400) * 1000000000 ))
Both funding sources use the same placeOrder entrypoint. Under the default auto-pull flow it pulls the input from your wallet; in manual vault mode it draws from your pre-deposited vault balance instead:
cast send $POOL \
"placeOrder(bool,uint64,uint256,uint256,uint64,uint8,uint8,address,uint96)" \
true 0 $PRICE $QUANTITY $EXPIRE_NS 2 0 0x0000000000000000000000000000000000000000 0 \
--rpc-url $RPC --private-key $PRIVATE_KEY
The orderType of 2 (IOC) above is just an example — placeOrder accepts any order type, including resting limit orders (0 = GTC, 3 = PostOnly). On a native-token market add --value $(cast to-wei <amount>) so the pool can auto-pull the input from msg.value.
The parameters are:
| Parameter | Description |
|---|---|
isBid | true for buy, false for sell |
userData | Arbitrary 64-bit tag (use 0) |
price | Limit price in raw units (value × 10^quoteDecimals) |
quantity | Order size in raw units (value × 10^baseDecimals) |
expireTimestampNs | Expiration in nanoseconds since Unix epoch (must be a future timestamp) |
orderType | 0 = Normal (GTC), 1 = Fill-or-Kill, 2 = IOC, 3 = PostOnly |
selfMatchingOption | 0 = cancel taker on self-match, 1 = cancel maker |
builder | Optional builder address — see Builder Codes. Pass 0x0000...0000 if unused. |
builderFeeBpsTimes1k | Per-order builder fee rate (BPS_TIMES_1K). Must be 0 when builder is the zero address. |
Builder codes are live on mainnet. The protocol cap
getMaxBuilderFeeBpsTimes1k()is currently100000(1%) on mainnet and0on testnet; when the cap is0, a non-zerobuilderreverts withBuilderCodesNotSupported. Leave both trailing arguments at the zero values shown above for an untagged order, or approve a builder first to tag one.
Taker orders must cross the book. An IOC/FOK buy has to price at or above the best ask (a sell at or below the best bid) to fill; a
priceof0never crosses and produces no fill. Price your limit to cross, bounded by your slippage tolerance.
Recommended workflow
- Simulate first. Call the transaction via
eth_call(orcast call). The place-order functions return(bool success, uint128 orderId). Ifsuccessisfalse, check your transaction for potential issues. - Sign and broadcast. If the simulation succeeds, sign the transaction and send it to the Somnia network.
- Verify after confirmation - a
status: 1receipt does not prove a fill. The transaction can succeed while your order does nothing. Decode the receipt logs:OrderPlaced- the order was accepted. An empty logs array means it was silently rejected (re-check funding, quantization, and expiry).OrderFilled(one per fill leg) - the order actually executed. A taker order (IOC/FOK) that never crosses producesOrderPlacedbut noOrderFilled- it was accepted and then immediately cancelled with zero fill, and nativemsg.valueis refunded. Sum theOrderFilledquantities (or diff your balances) to learn how much filled; do not infer a fill fromstatus: 1alone.
5. Track Your Order
Option A: Poll via REST
curl -H "Authorization: Bearer $TOKEN" \
$BASE_URL/markets/WETH:USDso/orders/<orderId>
Option B: Using the dreamDEX CLI
# List open orders
dreamdex order list WETH:USDso --status open
# Get a specific order
dreamdex order get WETH:USDso <orderId>
# Stream live updates
dreamdex watch order <orderId>
# Cancel an order
dreamdex order cancel WETH:USDso <orderId>
Option C: Stream via WebSocket
Connect to the WebSocket API at wss://api.dreamdex.io/v0/ws/public (testnet: wss://stg.api.dreamdex.io/v0/ws/public) and subscribe to order updates:
{
"operation": "subscribe",
"channel": "order",
"params": { "orderId": "0xYourOrderId" }
}
You will receive a snapshot of the current order state followed by real-time updates as the order fills or is cancelled.
Option D: Query via cast
# Get order details by ID (OrderId is a uint128 on-chain)
cast call $POOL \
"getOrder(uint128)" \
$ORDER_ID \
--rpc-url $RPC
To cancel an order:
cast send $POOL \
"cancelOrder(uint128)" \
$ORDER_ID \
--rpc-url $RPC --private-key $PRIVATE_KEY
Summary
| Step | HTTP API | dreamDEX CLI | cast |
|---|---|---|---|
| Discover markets | GET /v0/markets | dreamdex markets | Same (HTTP API required) |
| Authenticate | POST /v0/auth/login | dreamdex login | Not needed |
| Approve token | POST .../vault/approve | Automatic | cast send <token> "approve(...)" |
| Deposit (vault only) | POST .../vault/deposit | dreamdex vault deposit ... | cast send <pool> "deposit(...)" |
| Place order (wallet) | POST .../orders | dreamdex order place ... | cast send <pool> "placeOrder(...)" |
| Place order (vault) | POST .../orders | dreamdex order place ... --funding-source vault | cast send <pool> "placeOrder(...)" |
| Check order | GET .../orders/{id} | dreamdex order get ... | cast call <pool> "getOrder(...)" |
| Cancel order | - | dreamdex order cancel ... | cast send <pool> "cancelOrder(...)" |
Useful view functions: Call
getPoolParams()on any SpotPool to discover its token addresses, fee rates, tick size, lot size, and min quantity. CallgetWithdrawableBalance(address, token)to check your available balance before withdrawing. CallgetOwnOpenOrders()to list your active orders.
Next Steps
- Order Types - Learn about all supported order types and time-in-force options
- Stop Orders - Set up automated stop-loss and take-profit orders
- Contracts - Full contract API reference
- HTTP API - Full REST API reference
- WebSocket API - Real-time market data and order tracking