# Create a limit order on Polygon: sell 0.5 WETH for USDC at 4200 USDC per WETH or better.
# Needs openssl, curl, python3 and OLYMPEX_API_KEY_ID, OLYMPEX_SECRET_KEY, OLYMPEX_PASSPHRASE, plus the maker's key in
# WALLET_PRIVATE_KEY and sign_order_pair.py from /concepts/order-authorization in this folder, with eth-account
# installed in an active virtual environment.
SIGNED="$(python3 -c 'import os, sign_order_pair as s; r = s.sign_order_pair(os.environ["WALLET_PRIVATE_KEY"], "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"); print(r["accountTo"], r["signature"])')"
MAKER="${SIGNED% *}" # the maker's EIP-55 address
SIGNATURE="${SIGNED#* }" # the maker's signature for the WETH to USDC pair
EXPIRED="$(( ($(date +%s) + 7 * 86400) * 1000 ))" # 7 days from now, as a Unix timestamp in milliseconds
METHOD=POST
ENDPOINT=/limit-order
QUERY=''
BODY="$(printf '{"accountTo":"%s","amount":"0.5","chainId":137,"expired":"%s","gasPrice":"35","inTokenAddress":"0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619","outTokenAddress":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359","price":"4200","priceTrigger":"4200","signature":"%s","slippage":"1","tokenASymbol":"WETH","tokenBSymbol":"USDC"}' "$MAKER" "$EXPIRED" "$SIGNATURE")"
TS="${OLYMPEX_TIMESTAMP:-$(date +%s)}"
NONCE="${OLYMPEX_NONCE:-$(openssl rand -hex 12)}"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"
VALUE_INFO="$(printf '%s\n%s\n%s' "$TS" "$NONCE" "$BODY_HASH")"
MESSAGE="$(printf 'OLPX-HMAC-SHA256-V2\n%s\n%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" "$METHOD" "/api/v1$ENDPOINT" "$QUERY" "$BODY_HASH")"
curl -sS -X "$METHOD" "https://api-rest.olympex.io/api/v1$ENDPOINT${QUERY:+?$QUERY}" \
-H "content-type: application/json" \
-H "x-api-key-id: $OLYMPEX_API_KEY_ID" \
-H "x-value-info: $(printf '%s' "$VALUE_INFO" | openssl base64 -A)" \
-H "x-passphrase: $OLYMPEX_PASSPHRASE" \
-H "x-signature: $(printf '%s' "$MESSAGE" | openssl dgst -sha256 -hmac "$OLYMPEX_SECRET_KEY" -binary | od -An -v -tx1 | tr -d ' \n')" \
--data-raw "$BODY"
// npm install ethers. Reads the maker's WALLET_PRIVATE_KEY from the environment.
import { Wallet } from "ethers";
import { olympexRequest } from "./sign-request.ts"; // /authentication/sign-requests
import { signOrderPair } from "./sign-order-pair.ts"; // /concepts/order-authorization
const WETH = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619"; // Polygon
const USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"; // Polygon
// An EOA that holds the WETH and has approved the Olympex order contract.
// In a dApp, pass the user's wallet signer instead.
const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!);
const { accountTo, signature } = await signOrderPair(wallet, WETH, USDC); // checks the signature before it returns
type LimitOrder = { id: string; status: string; tokenASymbol: string; tokenBSymbol: string };
// Every success creates a new order: after a timeout, list the orders before you retry.
const order = await olympexRequest<LimitOrder>("POST", "/limit-order", {
accountTo, // EIP-55 checksummed
chainId: 137,
inTokenAddress: WETH,
outTokenAddress: USDC,
tokenASymbol: "WETH", // real symbols: Olympex finds the reference price with them
tokenBSymbol: "USDC",
amount: "0.5", // human-readable WETH, not base units
priceTrigger: "4200", // USDC per 1 WETH
price: "4200", // the same value as priceTrigger
expired: String(Date.now() + 7 * 24 * 60 * 60 * 1000), // Unix timestamp in milliseconds, as a string
gasPrice: "35", // gwei
slippage: "1", // percent
signature,
});
console.log(order.id, order.status); // save the ID
// Olympex prices the order from the market of the symbols it returns, and WETH can come back as ETH.
// Cancel an order priced from another token's market.
if (!["WETH", "ETH"].includes(order.tokenASymbol) || order.tokenBSymbol !== "USDC") {
await olympexRequest("DELETE", `/limit-order/${order.id}`);
throw new Error(`Priced as ${order.tokenASymbol}/${order.tokenBSymbol}, not WETH/USDC: order cancelled`);
}
# In a virtual environment: python3 -m venv .venv && . .venv/bin/activate && python3 -m pip install eth-account requests
import os
import time
from sign_order_pair import sign_order_pair # /concepts/order-authorization
from sign_request import olympex_request # /authentication/sign-requests
WETH = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619" # Polygon
USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" # Polygon
# An EOA that holds the WETH and has approved the Olympex order contract.
pair = sign_order_pair(os.environ["WALLET_PRIVATE_KEY"], WETH, USDC) # checks the signature before it returns
# Every success creates a new order: after a timeout, list the orders before you retry.
order = olympex_request("POST", "/limit-order", {
"accountTo": pair["accountTo"], # EIP-55 checksummed
"chainId": 137,
"inTokenAddress": WETH,
"outTokenAddress": USDC,
"tokenASymbol": "WETH", # real symbols: Olympex finds the reference price with them
"tokenBSymbol": "USDC",
"amount": "0.5", # human-readable WETH, not base units
"priceTrigger": "4200", # USDC per 1 WETH
"price": "4200", # the same value as priceTrigger
"expired": str(int(time.time() * 1000) + 7 * 24 * 60 * 60 * 1000), # Unix timestamp in milliseconds, as a string
"gasPrice": "35", # gwei
"slippage": "1", # percent
"signature": pair["signature"],
})
print(order["id"], order["status"]) # save the ID
# Olympex prices the order from the market of the symbols it returns, and WETH can come back as ETH.
# Cancel an order priced from another token's market.
if order["tokenASymbol"] not in ("WETH", "ETH") or order["tokenBSymbol"] != "USDC":
olympex_request("DELETE", f'/limit-order/{order["id"]}')
raise RuntimeError(f'Priced as {order["tokenASymbol"]}/{order["tokenBSymbol"]}, not WETH/USDC: order cancelled')
{
"success": true,
"data": {
"id": "afc47108-d059-473c-b2e1-5f2ca7951466",
"accountTo": "0x1E67cb01969D79B2B895179e4A07D24a839dBb52",
"chainId": 137,
"gasPrice": "35",
"inTokenAddress": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
"outTokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"slippage": "1",
"tokenASymbol": "ETH",
"tokenBSymbol": "USDC",
"amount": 0.5,
"price": 4200,
"priceTrigger": 4200,
"expired": "1791302400000",
"txHash": "",
"status": "pending",
"reasonFail": [],
"attemptNumber": 0,
"allowance": "",
"estimateGas": "",
"effectivePriceGas": "",
"createdAt": "2026-09-29T16:01:22.237Z",
"updatedAt": "2026-09-29T16:01:22.237Z",
"deletedAt": ""
},
"meta": {
"requestId": "EeAQVh4coAMEJSw=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "chainId",
"message": "Invalid input: expected number, received string"
}
]
},
"meta": {
"requestId": "E7eiXi2AIAMEZXA=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "expired",
"message": "expired must be a Unix timestamp in milliseconds (13 digits), e.g. String(Date.now() + 86_400_000)"
}
]
},
"meta": {
"requestId": "E7eiLgFUoAMEZcQ=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Not exist reference price for this pair FOOZZ/BARZZ",
"details": [
{
"message": "Not exist pair for create limit order, you must be select a new pair (Not possible create limit order, you must be select a new pair | > Error => Could not recover data of contract)"
}
]
},
"meta": {
"requestId": "EdkfSjF0IAMEPEg=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"message": "Forbidden"
}
Create a limit order
Create an order that sells a token from the maker’s wallet when the market reaches your price.
# Create a limit order on Polygon: sell 0.5 WETH for USDC at 4200 USDC per WETH or better.
# Needs openssl, curl, python3 and OLYMPEX_API_KEY_ID, OLYMPEX_SECRET_KEY, OLYMPEX_PASSPHRASE, plus the maker's key in
# WALLET_PRIVATE_KEY and sign_order_pair.py from /concepts/order-authorization in this folder, with eth-account
# installed in an active virtual environment.
SIGNED="$(python3 -c 'import os, sign_order_pair as s; r = s.sign_order_pair(os.environ["WALLET_PRIVATE_KEY"], "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"); print(r["accountTo"], r["signature"])')"
MAKER="${SIGNED% *}" # the maker's EIP-55 address
SIGNATURE="${SIGNED#* }" # the maker's signature for the WETH to USDC pair
EXPIRED="$(( ($(date +%s) + 7 * 86400) * 1000 ))" # 7 days from now, as a Unix timestamp in milliseconds
METHOD=POST
ENDPOINT=/limit-order
QUERY=''
BODY="$(printf '{"accountTo":"%s","amount":"0.5","chainId":137,"expired":"%s","gasPrice":"35","inTokenAddress":"0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619","outTokenAddress":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359","price":"4200","priceTrigger":"4200","signature":"%s","slippage":"1","tokenASymbol":"WETH","tokenBSymbol":"USDC"}' "$MAKER" "$EXPIRED" "$SIGNATURE")"
TS="${OLYMPEX_TIMESTAMP:-$(date +%s)}"
NONCE="${OLYMPEX_NONCE:-$(openssl rand -hex 12)}"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"
VALUE_INFO="$(printf '%s\n%s\n%s' "$TS" "$NONCE" "$BODY_HASH")"
MESSAGE="$(printf 'OLPX-HMAC-SHA256-V2\n%s\n%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" "$METHOD" "/api/v1$ENDPOINT" "$QUERY" "$BODY_HASH")"
curl -sS -X "$METHOD" "https://api-rest.olympex.io/api/v1$ENDPOINT${QUERY:+?$QUERY}" \
-H "content-type: application/json" \
-H "x-api-key-id: $OLYMPEX_API_KEY_ID" \
-H "x-value-info: $(printf '%s' "$VALUE_INFO" | openssl base64 -A)" \
-H "x-passphrase: $OLYMPEX_PASSPHRASE" \
-H "x-signature: $(printf '%s' "$MESSAGE" | openssl dgst -sha256 -hmac "$OLYMPEX_SECRET_KEY" -binary | od -An -v -tx1 | tr -d ' \n')" \
--data-raw "$BODY"
// npm install ethers. Reads the maker's WALLET_PRIVATE_KEY from the environment.
import { Wallet } from "ethers";
import { olympexRequest } from "./sign-request.ts"; // /authentication/sign-requests
import { signOrderPair } from "./sign-order-pair.ts"; // /concepts/order-authorization
const WETH = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619"; // Polygon
const USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"; // Polygon
// An EOA that holds the WETH and has approved the Olympex order contract.
// In a dApp, pass the user's wallet signer instead.
const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!);
const { accountTo, signature } = await signOrderPair(wallet, WETH, USDC); // checks the signature before it returns
type LimitOrder = { id: string; status: string; tokenASymbol: string; tokenBSymbol: string };
// Every success creates a new order: after a timeout, list the orders before you retry.
const order = await olympexRequest<LimitOrder>("POST", "/limit-order", {
accountTo, // EIP-55 checksummed
chainId: 137,
inTokenAddress: WETH,
outTokenAddress: USDC,
tokenASymbol: "WETH", // real symbols: Olympex finds the reference price with them
tokenBSymbol: "USDC",
amount: "0.5", // human-readable WETH, not base units
priceTrigger: "4200", // USDC per 1 WETH
price: "4200", // the same value as priceTrigger
expired: String(Date.now() + 7 * 24 * 60 * 60 * 1000), // Unix timestamp in milliseconds, as a string
gasPrice: "35", // gwei
slippage: "1", // percent
signature,
});
console.log(order.id, order.status); // save the ID
// Olympex prices the order from the market of the symbols it returns, and WETH can come back as ETH.
// Cancel an order priced from another token's market.
if (!["WETH", "ETH"].includes(order.tokenASymbol) || order.tokenBSymbol !== "USDC") {
await olympexRequest("DELETE", `/limit-order/${order.id}`);
throw new Error(`Priced as ${order.tokenASymbol}/${order.tokenBSymbol}, not WETH/USDC: order cancelled`);
}
# In a virtual environment: python3 -m venv .venv && . .venv/bin/activate && python3 -m pip install eth-account requests
import os
import time
from sign_order_pair import sign_order_pair # /concepts/order-authorization
from sign_request import olympex_request # /authentication/sign-requests
WETH = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619" # Polygon
USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" # Polygon
# An EOA that holds the WETH and has approved the Olympex order contract.
pair = sign_order_pair(os.environ["WALLET_PRIVATE_KEY"], WETH, USDC) # checks the signature before it returns
# Every success creates a new order: after a timeout, list the orders before you retry.
order = olympex_request("POST", "/limit-order", {
"accountTo": pair["accountTo"], # EIP-55 checksummed
"chainId": 137,
"inTokenAddress": WETH,
"outTokenAddress": USDC,
"tokenASymbol": "WETH", # real symbols: Olympex finds the reference price with them
"tokenBSymbol": "USDC",
"amount": "0.5", # human-readable WETH, not base units
"priceTrigger": "4200", # USDC per 1 WETH
"price": "4200", # the same value as priceTrigger
"expired": str(int(time.time() * 1000) + 7 * 24 * 60 * 60 * 1000), # Unix timestamp in milliseconds, as a string
"gasPrice": "35", # gwei
"slippage": "1", # percent
"signature": pair["signature"],
})
print(order["id"], order["status"]) # save the ID
# Olympex prices the order from the market of the symbols it returns, and WETH can come back as ETH.
# Cancel an order priced from another token's market.
if order["tokenASymbol"] not in ("WETH", "ETH") or order["tokenBSymbol"] != "USDC":
olympex_request("DELETE", f'/limit-order/{order["id"]}')
raise RuntimeError(f'Priced as {order["tokenASymbol"]}/{order["tokenBSymbol"]}, not WETH/USDC: order cancelled')
{
"success": true,
"data": {
"id": "afc47108-d059-473c-b2e1-5f2ca7951466",
"accountTo": "0x1E67cb01969D79B2B895179e4A07D24a839dBb52",
"chainId": 137,
"gasPrice": "35",
"inTokenAddress": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
"outTokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"slippage": "1",
"tokenASymbol": "ETH",
"tokenBSymbol": "USDC",
"amount": 0.5,
"price": 4200,
"priceTrigger": 4200,
"expired": "1791302400000",
"txHash": "",
"status": "pending",
"reasonFail": [],
"attemptNumber": 0,
"allowance": "",
"estimateGas": "",
"effectivePriceGas": "",
"createdAt": "2026-09-29T16:01:22.237Z",
"updatedAt": "2026-09-29T16:01:22.237Z",
"deletedAt": ""
},
"meta": {
"requestId": "EeAQVh4coAMEJSw=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "chainId",
"message": "Invalid input: expected number, received string"
}
]
},
"meta": {
"requestId": "E7eiXi2AIAMEZXA=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "expired",
"message": "expired must be a Unix timestamp in milliseconds (13 digits), e.g. String(Date.now() + 86_400_000)"
}
]
},
"meta": {
"requestId": "E7eiLgFUoAMEZcQ=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Not exist reference price for this pair FOOZZ/BARZZ",
"details": [
{
"message": "Not exist pair for create limit order, you must be select a new pair (Not possible create limit order, you must be select a new pair | > Error => Could not recover data of contract)"
}
]
},
"meta": {
"requestId": "EdkfSjF0IAMEPEg=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"message": "Forbidden"
}
POST /limit-order creates an order to sell amount of inTokenAddress for outTokenAddress. The order executes when the market price of inTokenAddress, in outTokenAddress, reaches priceTrigger or better. Olympex stores the order as pending and returns it with its id. Nothing moves on-chain until Olympex executes the order from the maker’s wallet, which is why the maker signs the token pair and approves the Olympex order contract before you call this endpoint.
Before you call it
accountTo is the maker: it sells inTokenAddress, receives all the output and signs signature. It must be an externally owned account (EOA). Smart-contract wallets, such as Safe or ERC-4337 accounts, can’t sign orders, because only 65-byte ECDSA signatures are accepted. Olympex stores accountTo exactly as you send it, and the accountTo filter of GET /limit-order matches case-sensitively, so always send the EIP-55 checksummed form, which getAddress() returns in ethers and viem.
The maker wallet does two things before you call this endpoint. Neither one calls Olympex.
- Sign the token pair. The maker signs
keccak256(abi.encodePacked(accountTo, accountTo, inTokenAddress, outTokenAddress))withpersonal_sign, over the 32 raw bytes, not the hex string. One signature serves every limit order and DCA strategy for that maker and pair. Olympex doesn’t check the signature when you create the order, and a wrong one makes execution fail later, so verify it yourself before you send it, as the signing helpers in the examples do. Order signatures and allowances explains what the signature authorizes and how to produce it. - Approve the Olympex order contract for
amountplus the execution gas cost, both ininTokenAddress. The spender is the order contract forchainId, listed in The order contract. It isn’t thecontractToApprovethatPOST /swapreturns, which is for swaps.
amount of inTokenAddress from accountTo through the order contract, swaps it and sends all the output to accountTo. Olympex pays the execution gas and takes it back from accountTo in inTokenAddress, as a second transfer, which is why the allowance covers the gas cost too. Estimate that cost with a single-chain POST /quotes for the same pair and amount, with params.includeGasInfo set to true: dataFeeTransaction.transactionFeeInToken is the fee, and valueToApprove is amount plus the fee, both human-readable. Add a buffer: gas prices move, and the estimate leaves out the gas the Olympex contracts use. Keep the balance and the allowance in place while the order is open: Olympex can’t execute the order without them.
Units and formats
| Field | Format |
|---|---|
amount | Amount of inTokenAddress to sell, as a human-readable decimal string. "0.5" is 0.5 WETH, not 0.5 base units. |
priceTrigger | Units of outTokenAddress per 1 inTokenAddress, as a human-readable decimal string. "4200" on a WETH to USDC order is 4,200 USDC per WETH. |
price | Optional. A copy of the limit price that Olympex stores as sent and never syncs with priceTrigger. Send the same value as priceTrigger, and send both whenever you change one. Responses return 0 when you omit it. |
expired | A Unix timestamp in milliseconds, as a 13-digit string, in the future and at most 365 days ahead: String(Date.now() + 7 * 24 * 60 * 60 * 1000) is one week from now. A time in seconds, an ISO 8601 date or a past time returns 400 VALIDATION_ERROR. Don’t rely on it to stop the order: when you no longer want the order, cancel it and lower the allowance. |
gasPrice | The chain’s current gas price in gwei, as a decimal string. "35" is 35 gwei. Olympex stores it with the order. It isn’t a cap: neither the API nor the order contract enforces it, and the gas cost the maker reimburses is the execution transaction’s gas used times its actual gas price, converted to inTokenAddress. Size the allowance from the fee estimate plus a buffer, not from gasPrice. |
slippage | Maximum slippage at execution, in percent, as a string. "1" is 1%. |
chainId | An integer, such as 137. A string returns 400 VALIDATION_ERROR. Responses return an integer. Limit orders execute only on the chains listed in The order contract. |
amount, price and priceTrigger as JSON numbers. Olympex stores them as double-precision numbers, so send at most 15 significant digits: "0.123456789123456789" comes back as 0.12345678912345678. Fields that Olympex sets as it executes the order, such as status, txHash and reasonFail, aren’t accepted: sending one returns 400 VALIDATION_ERROR.
The token pair
Reference price. Olympex picks the market that prices the order when you create it. It reuses a recent lookup for the samechainId, inTokenAddress and outTokenAddress, written exactly the same way, and then ignores the symbols you send. Otherwise it looks for a market for tokenASymbol and tokenBSymbol, and if it finds none, it identifies the two tokens by address. Olympex doesn’t check the symbols against the token contracts. Set tokenASymbol to the real symbol of the token you sell and tokenBSymbol to the real symbol of the token you buy, for example WETH and USDC. Read them with symbol() from the token contracts, or take them from GET /tokens by address, trimmed and without a trailing _<number> (send ICE for ICE_3). When Olympex finds no price source for the pair, the call returns 400 VALIDATION_ERROR with the message "Not exist reference price for this pair A/B", where A/B are the symbols you sent, and no order is created. The error can be temporary, so retry later with backoff before you rule the pair out.
Normalized symbols. Responses can return normalized symbols: WETH comes back as ETH, WBNB as BNB and WPOL as POL. Identify orders by token address, never by symbol.
tokenASymbol and tokenBSymbol in the response. A real but wrong symbol, such as WBTC on a WETH order, is accepted, and Olympex then watches that token’s market. If a returned symbol is neither your token’s symbol nor its normalized form (WETH as ETH, WBNB as BNB, WPOL as POL), cancel the order.- A native token.
inTokenAddressmust be an ERC-20: wrap the native token first and sell WETH, WBNB or WPOL. List tokens gives their addresses on Ethereum, BNB Chain and Polygon. - An ERC-20 whose
transferandapprovedon’t return a boolean. USDT is the notable one: it isn’t supported as the token you sell in limit orders or DCA on Ethereum. - A fee-on-transfer token.
Don’t retry a create blindly
Every successful call creates a new order, and Olympex ignores anyid you send. If the call times out, or returns a 5xx or no response at all, the order may exist anyway. Before you send it again, list the maker’s orders with GET /limit-order and match on the fields you sent. A duplicate is a second order that Olympex can execute against the same allowance. The retry wrappers in Handle errors and retries never repeat this call on their own.
After you create the order, poll GET /limit-order/{id} to follow its status. Change a pending order with PATCH /limit-order/{id}, and cancel it with DELETE /limit-order/{id}. The Place a limit order guide walks through the whole flow.
# Create a limit order on Polygon: sell 0.5 WETH for USDC at 4200 USDC per WETH or better.
# Needs openssl, curl, python3 and OLYMPEX_API_KEY_ID, OLYMPEX_SECRET_KEY, OLYMPEX_PASSPHRASE, plus the maker's key in
# WALLET_PRIVATE_KEY and sign_order_pair.py from /concepts/order-authorization in this folder, with eth-account
# installed in an active virtual environment.
SIGNED="$(python3 -c 'import os, sign_order_pair as s; r = s.sign_order_pair(os.environ["WALLET_PRIVATE_KEY"], "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"); print(r["accountTo"], r["signature"])')"
MAKER="${SIGNED% *}" # the maker's EIP-55 address
SIGNATURE="${SIGNED#* }" # the maker's signature for the WETH to USDC pair
EXPIRED="$(( ($(date +%s) + 7 * 86400) * 1000 ))" # 7 days from now, as a Unix timestamp in milliseconds
METHOD=POST
ENDPOINT=/limit-order
QUERY=''
BODY="$(printf '{"accountTo":"%s","amount":"0.5","chainId":137,"expired":"%s","gasPrice":"35","inTokenAddress":"0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619","outTokenAddress":"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359","price":"4200","priceTrigger":"4200","signature":"%s","slippage":"1","tokenASymbol":"WETH","tokenBSymbol":"USDC"}' "$MAKER" "$EXPIRED" "$SIGNATURE")"
TS="${OLYMPEX_TIMESTAMP:-$(date +%s)}"
NONCE="${OLYMPEX_NONCE:-$(openssl rand -hex 12)}"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"
VALUE_INFO="$(printf '%s\n%s\n%s' "$TS" "$NONCE" "$BODY_HASH")"
MESSAGE="$(printf 'OLPX-HMAC-SHA256-V2\n%s\n%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" "$METHOD" "/api/v1$ENDPOINT" "$QUERY" "$BODY_HASH")"
curl -sS -X "$METHOD" "https://api-rest.olympex.io/api/v1$ENDPOINT${QUERY:+?$QUERY}" \
-H "content-type: application/json" \
-H "x-api-key-id: $OLYMPEX_API_KEY_ID" \
-H "x-value-info: $(printf '%s' "$VALUE_INFO" | openssl base64 -A)" \
-H "x-passphrase: $OLYMPEX_PASSPHRASE" \
-H "x-signature: $(printf '%s' "$MESSAGE" | openssl dgst -sha256 -hmac "$OLYMPEX_SECRET_KEY" -binary | od -An -v -tx1 | tr -d ' \n')" \
--data-raw "$BODY"
// npm install ethers. Reads the maker's WALLET_PRIVATE_KEY from the environment.
import { Wallet } from "ethers";
import { olympexRequest } from "./sign-request.ts"; // /authentication/sign-requests
import { signOrderPair } from "./sign-order-pair.ts"; // /concepts/order-authorization
const WETH = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619"; // Polygon
const USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"; // Polygon
// An EOA that holds the WETH and has approved the Olympex order contract.
// In a dApp, pass the user's wallet signer instead.
const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!);
const { accountTo, signature } = await signOrderPair(wallet, WETH, USDC); // checks the signature before it returns
type LimitOrder = { id: string; status: string; tokenASymbol: string; tokenBSymbol: string };
// Every success creates a new order: after a timeout, list the orders before you retry.
const order = await olympexRequest<LimitOrder>("POST", "/limit-order", {
accountTo, // EIP-55 checksummed
chainId: 137,
inTokenAddress: WETH,
outTokenAddress: USDC,
tokenASymbol: "WETH", // real symbols: Olympex finds the reference price with them
tokenBSymbol: "USDC",
amount: "0.5", // human-readable WETH, not base units
priceTrigger: "4200", // USDC per 1 WETH
price: "4200", // the same value as priceTrigger
expired: String(Date.now() + 7 * 24 * 60 * 60 * 1000), // Unix timestamp in milliseconds, as a string
gasPrice: "35", // gwei
slippage: "1", // percent
signature,
});
console.log(order.id, order.status); // save the ID
// Olympex prices the order from the market of the symbols it returns, and WETH can come back as ETH.
// Cancel an order priced from another token's market.
if (!["WETH", "ETH"].includes(order.tokenASymbol) || order.tokenBSymbol !== "USDC") {
await olympexRequest("DELETE", `/limit-order/${order.id}`);
throw new Error(`Priced as ${order.tokenASymbol}/${order.tokenBSymbol}, not WETH/USDC: order cancelled`);
}
# In a virtual environment: python3 -m venv .venv && . .venv/bin/activate && python3 -m pip install eth-account requests
import os
import time
from sign_order_pair import sign_order_pair # /concepts/order-authorization
from sign_request import olympex_request # /authentication/sign-requests
WETH = "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619" # Polygon
USDC = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" # Polygon
# An EOA that holds the WETH and has approved the Olympex order contract.
pair = sign_order_pair(os.environ["WALLET_PRIVATE_KEY"], WETH, USDC) # checks the signature before it returns
# Every success creates a new order: after a timeout, list the orders before you retry.
order = olympex_request("POST", "/limit-order", {
"accountTo": pair["accountTo"], # EIP-55 checksummed
"chainId": 137,
"inTokenAddress": WETH,
"outTokenAddress": USDC,
"tokenASymbol": "WETH", # real symbols: Olympex finds the reference price with them
"tokenBSymbol": "USDC",
"amount": "0.5", # human-readable WETH, not base units
"priceTrigger": "4200", # USDC per 1 WETH
"price": "4200", # the same value as priceTrigger
"expired": str(int(time.time() * 1000) + 7 * 24 * 60 * 60 * 1000), # Unix timestamp in milliseconds, as a string
"gasPrice": "35", # gwei
"slippage": "1", # percent
"signature": pair["signature"],
})
print(order["id"], order["status"]) # save the ID
# Olympex prices the order from the market of the symbols it returns, and WETH can come back as ETH.
# Cancel an order priced from another token's market.
if order["tokenASymbol"] not in ("WETH", "ETH") or order["tokenBSymbol"] != "USDC":
olympex_request("DELETE", f'/limit-order/{order["id"]}')
raise RuntimeError(f'Priced as {order["tokenASymbol"]}/{order["tokenBSymbol"]}, not WETH/USDC: order cancelled')
{
"success": true,
"data": {
"id": "afc47108-d059-473c-b2e1-5f2ca7951466",
"accountTo": "0x1E67cb01969D79B2B895179e4A07D24a839dBb52",
"chainId": 137,
"gasPrice": "35",
"inTokenAddress": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
"outTokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"slippage": "1",
"tokenASymbol": "ETH",
"tokenBSymbol": "USDC",
"amount": 0.5,
"price": 4200,
"priceTrigger": 4200,
"expired": "1791302400000",
"txHash": "",
"status": "pending",
"reasonFail": [],
"attemptNumber": 0,
"allowance": "",
"estimateGas": "",
"effectivePriceGas": "",
"createdAt": "2026-09-29T16:01:22.237Z",
"updatedAt": "2026-09-29T16:01:22.237Z",
"deletedAt": ""
},
"meta": {
"requestId": "EeAQVh4coAMEJSw=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "chainId",
"message": "Invalid input: expected number, received string"
}
]
},
"meta": {
"requestId": "E7eiXi2AIAMEZXA=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{
"field": "expired",
"message": "expired must be a Unix timestamp in milliseconds (13 digits), e.g. String(Date.now() + 86_400_000)"
}
]
},
"meta": {
"requestId": "E7eiLgFUoAMEZcQ=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Not exist reference price for this pair FOOZZ/BARZZ",
"details": [
{
"message": "Not exist pair for create limit order, you must be select a new pair (Not possible create limit order, you must be select a new pair | > Error => Could not recover data of contract)"
}
]
},
"meta": {
"requestId": "EdkfSjF0IAMEPEg=",
"version": "v1",
"accountType": "integrator",
"apiKeyId": "00000000-0000-4000-8000-000000000000"
}
}
{
"message": "Forbidden"
}
Authorizations
Your API key ID (UUID). See Sign requests.
Base64 of timestamp + "\n" + nonce + "\n" + bodyHash, where timestamp is Unix seconds, nonce is 24 new hexadecimal characters and bodyHash is the unpadded base64url SHA-256 of the canonical body.
Your account passphrase. Treat it like the secret key.
Lowercase hex HMAC-SHA256 (signature v2), keyed with your secret key string (UTF-8, not decoded), of seven lines joined with \n, with no trailing newline: OLPX-HMAC-SHA256-V2, timestamp, nonce, the uppercase method, path, canonicalQuery and bodyHash. path is the request path including /api/v1, without the query string, for example /api/v1/limit-order/<id>. canonicalQuery is the query parameters decoded (+ is a space), sorted by key and then by value, re-encoded per RFC 3986 and joined with &, or the empty string when there is no query. Headers signed for one request are rejected on any other. See Sign requests.
Body
Creates an order that Olympex executes when the market reaches priceTrigger. New orders always start pending. Fields marked read-only are accepted by the API but reserved for Olympex. A field the schema doesn't list, including the ones Olympex sets during execution (status, txHash, executorAddress, reasonFail, allowance, estimateGas, effectivePriceGas), returns 400 VALIDATION_ERROR.
The maker: the wallet that sells inTokenAddress, receives outTokenAddress and signs signature. It must be an externally owned account (EOA); smart-contract wallets can't sign orders. Stored exactly as sent. Send the EIP-55 checksummed form, and use the same form in list filters, which match case-sensitively. Must be a valid EVM address: 0x and 40 hexadecimal digits, in lowercase or EIP-55 checksummed form. A mixed-case address with a wrong checksum returns 400 VALIDATION_ERROR ("Must be a valid EVM address").
1Chain of both tokens, as an integer such as 137. Use a chain from GET /chains. A string returns 400 VALIDATION_ERROR.
0 < x <= 9007199254740991Gas price for the execution, in gwei, as a decimal string. Olympex stores it with the order, but execution uses the chain's gas price at that moment, so it isn't a cap: the maker reimburses the execution's actual gas cost.
1ERC-20 token to sell. Native tokens can't be sold: use the wrapped token (WETH, WBNB, WPOL). Must be a valid EVM address: 0x and 40 hexadecimal digits, in lowercase or EIP-55 checksummed form. A mixed-case address with a wrong checksum returns 400 VALIDATION_ERROR ("Must be a valid EVM address").
1Token to buy. Must be a valid EVM address: 0x and 40 hexadecimal digits, in lowercase or EIP-55 checksummed form. A mixed-case address with a wrong checksum returns 400 VALIDATION_ERROR ("Must be a valid EVM address").
1Maximum slippage at execution, in percent, as a decimal string ("1" is 1%).
1Symbol of inTokenAddress, for example WETH, as the token contract's symbol() returns it (token lists can add suffixes such as _1). Olympex can use it to find the pair's reference price and doesn't check it against the token contract, so send the real symbol and check the symbols the response returns. Responses can return a normalized symbol (WETH becomes ETH).
1Symbol of outTokenAddress, for example USDC.
1Amount of inTokenAddress to sell, human-readable ("0.5" is 0.5 WETH), not in base units. Send a decimal string. Responses return it as a number.
1Limit price: units of outTokenAddress per 1 inTokenAddress, human-readable. The order executes when the market reaches this price or better. Send a decimal string. Responses return it as a number.
1When the order expires: a Unix timestamp in milliseconds, as a 13-digit string, in the future and at most 365 days ahead. Compute it when you send the request, for example String(Date.now() + 7 * 86400000). A time in seconds, an ISO 8601 date or a past time returns 400 VALIDATION_ERROR.
The maker's signature authorizing Olympex to execute orders for this token pair: personal_sign over keccak256(abi.encodePacked(accountTo, accountTo, inTokenAddress, outTokenAddress)), 65 bytes as 0x-prefixed hex. Olympex doesn't check it when you create the order; a wrong signature makes execution fail later. See Order signatures and allowances.
1Optional copy of the limit price. Send the same value as priceTrigger. It's stored as sent and never synced with priceTrigger. Responses return 0 when you omit it.
Was this page helpful?
