> ## Documentation Index
> Fetch the complete documentation index at: https://docs.olympex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Order signatures and allowances

> How a maker authorizes Olympex to execute limit orders and DCA strategies: the pair signature, what it does and doesn't cover, the allowance that limits it, and the order contract on each chain.

Limit orders and DCA strategies execute after you create them: Olympex runs each swap from the maker's wallet when the order's conditions are met. Two authorizations from the maker's wallet make that possible. A signature, made once per token pair, lets the Olympex order contract swap that pair for the maker. An ERC-20 allowance to the order contract sets how much of the token it can spend. This page shows how to produce the signature, what it covers, and how to manage the allowance, which is the limit the chain enforces.

## Two authorizations

| | Order signature | Allowance |
| - | - | - |
| What it is | An EIP-191 signature by the maker over the token pair | An ERC-20 `approve` from the maker to the Olympex order contract |
| What it covers | Which pair the order contract may swap for the maker | How much of the token sold the order contract can spend |
| How you provide it | In `signature` on `POST /limit-order` and `POST /dca-order/strategies` | On-chain, in a transaction the maker sends and pays gas for |
| When it changes | Never: it stays valid after you cancel orders | Every execution spends from it, and you set it again as your orders change |
| How to stop it | You can't withdraw it. Set the allowance to `0` instead. | Set it to `0` |

Both are per maker. The signature is also per token pair and direction: selling WETH for USDC and selling USDC for WETH need separate signatures. The allowance is per token and chain, and every order that sells that token on that chain draws on it.

## The order signature

The maker signs the keccak-256 hash of four addresses, packed as 20 bytes each:

```text theme={null}
inner     = keccak256(abi.encodePacked(maker, maker, tokenIn, tokenOut))
signature = personal_sign(inner)    // EIP-191 over the 32 raw bytes: 65 bytes, as 0x-prefixed hex
```

The maker appears twice. The addresses map to request fields like this:

| In the hash | Limit order | DCA strategy |
| - | - | - |
| `maker` (twice) | `accountTo` | `accountTo` |
| `tokenIn`, the token sold | `inTokenAddress` | `tokenAddressFrom` |
| `tokenOut`, the token bought | `outTokenAddress` | `tokenAddressTo` |

The same signature works for limit orders and DCA strategies on the same pair. Address casing doesn't change the hash, because each address is packed as its 20 raw bytes. A mixed-case address whose EIP-55 checksum is wrong is usually a typo. The helpers below reject it before they sign, and so does the API. Send `accountTo` in checksummed form.

<Warning>
  Sign the 32 bytes of `inner`, not its hex string. `signMessage("0x5320…")` signs the 66 characters as text and returns a signature that looks valid but fails when Olympex executes the order. Pass `getBytes(inner)` in ethers, `{ raw: inner }` in viem, or `encode_defunct(primitive=inner)` in Python.
</Warning>

### Sign a pair

Each function signs the pair, checks the result against the maker and returns the two fields a create request needs. Save the one you use as `sign-order-pair.ts` or `sign_order_pair.py`. The limit order and DCA guides import `signOrderPair` from `sign-order-pair.ts`.

<CodeGroup>
  ```ts ethers v6 theme={null}
  // sign-order-pair.ts: signs the order authorization for one token pair. npm install ethers
  // signer: a Wallet on your server, or `await new BrowserProvider(window.ethereum).getSigner()` in a dApp.
  import { getAddress, getBytes, solidityPackedKeccak256, verifyMessage, type Signer } from "ethers";

  export const signOrderPair = async (signer: Signer, tokenIn: string, tokenOut: string) => {
    const maker = getAddress(await signer.getAddress());
    const inner = solidityPackedKeccak256(
      ["address", "address", "address", "address"],
      [maker, maker, getAddress(tokenIn), getAddress(tokenOut)],
    );
    const signature = await signer.signMessage(getBytes(inner)); // the 32 bytes, not the hex string
    if (verifyMessage(getBytes(inner), signature) !== maker) throw new Error("signature check failed");
    return { accountTo: maker, signature };
  };
  ```

  ```ts viem theme={null}
  // sign-order-pair.ts: signs the order authorization for one token pair. npm install viem
  // wallet: createWalletClient({ account, chain, transport }), with a local account or custom(window.ethereum).
  import { encodePacked, getAddress, isAddress, keccak256, recoverMessageAddress, type Address, type WalletClient } from "viem";

  export const signOrderPair = async (wallet: WalletClient, tokenIn: Address, tokenOut: Address) => {
    // isAddress checks the EIP-55 checksum of a mixed-case address; getAddress alone doesn't.
    for (const token of [tokenIn, tokenOut]) if (!isAddress(token)) throw new Error(`invalid address or EIP-55 checksum: ${token}`);
    const account = wallet.account;
    if (!account) throw new Error("create the wallet client with an account");
    const maker = getAddress(account.address);
    const inner = keccak256(
      encodePacked(["address", "address", "address", "address"], [maker, maker, getAddress(tokenIn), getAddress(tokenOut)]),
    );
    const signature = await wallet.signMessage({ account, message: { raw: inner } }); // the 32 bytes, not the hex string
    if ((await recoverMessageAddress({ message: { raw: inner }, signature })) !== maker) throw new Error("signature check failed");
    return { accountTo: maker, signature };
  };
  ```

  ```python Python theme={null}
  # sign_order_pair.py: signs the order authorization for one token pair. pip install eth-account
  import re

  from eth_account import Account
  from eth_account.messages import encode_defunct
  from eth_utils import is_checksum_address, keccak, to_canonical_address


  def check_address(address: str) -> None:
      """0x and 40 hex digits, in lowercase or with a valid EIP-55 checksum, as the API requires."""
      if not re.fullmatch(r"0x[0-9a-fA-F]{40}", address) or (address != address.lower() and not is_checksum_address(address)):
          raise ValueError(f"invalid address or EIP-55 checksum: {address}")


  def sign_order_pair(private_key: str, token_in: str, token_out: str) -> dict:
      maker = Account.from_key(private_key).address  # EIP-55 checksummed
      for token in (token_in, token_out):
          check_address(token)
      inner = keccak(b"".join(to_canonical_address(a) for a in (maker, maker, token_in, token_out)))
      message = encode_defunct(primitive=inner)  # the 32 bytes, not the hex string
      signature = "0x" + bytes(Account.sign_message(message, private_key=private_key).signature).hex()
      if Account.recover_message(message, signature=signature) != maker:
          raise ValueError("signature check failed")
      return {"accountTo": maker, "signature": signature}
  ```
</CodeGroup>

To authorize selling WETH for USDC on Polygon with ethers:

```ts theme={null}
import { Wallet } from "ethers";
import { signOrderPair } from "./sign-order-pair.ts"; // /concepts/order-authorization

const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); // the maker
const { accountTo, signature } = await signOrderPair(
  wallet,
  "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", // WETH on Polygon: the token sold
  "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // USDC on Polygon: the token bought
);
```

### Check the signature before you send it

Olympex doesn't check the signature when you create an order or a strategy. A wrong signature is accepted, and the order fails later, when Olympex tries to execute it. Each function above recovers the signer from the signature and compares it with the maker before it returns. Keep that check, and add it to any signing code you write yourself.

To test your packing, hash this input and compare the result:

| Input | Value |
| - | - |
| `maker` | `0x1E67cb01969D79B2B895179e4A07D24a839dBb52` |
| `tokenIn` (WETH on Polygon) | `0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619` |
| `tokenOut` (USDC on Polygon) | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` |
| `inner` | `0x532085f847d7024a252ebe4e1e270fce83dc4650f8ca3addb35830007b61eae9` |

The signature itself depends on the maker's private key, so it differs for every wallet. The recovery check covers it.

### The maker must be an EOA

Only 65-byte ECDSA signatures are accepted, so the maker must be an externally owned account (EOA). Smart-contract wallets, such as Safe or ERC-4337 accounts, can't sign orders.

Wallet libraries such as ethers, viem and eth-account produce the right format. If you sign with a key management service or a hardware security module, make sure the result is 65 bytes (`r`, `s`, `v`) with a `v` of 27 or 28 and a low `s` value, not a 64-byte compact signature.

## What the signature authorizes

This is the trust model for limit orders and DCA:

* **It authorizes swaps of one pair.** It lets the Olympex order contract execute swaps of `tokenIn` for `tokenOut` from the maker's wallet.
* **It doesn't bind the terms.** It covers no amount, price, expiry, chain or specific order. Olympex enforces the amounts, prices and expiry of your orders when it executes them.
* **It outlives your orders.** It stays valid after you cancel an order, and the same signature serves limit orders and DCA strategies for that pair.
* **The allowance is the on-chain limit.** The order contract can spend only up to the maker's allowance of the token sold. Approve only what your open orders need, never an unlimited amount.

The order contract is an upgradeable proxy operated by Olympex. Anyone who holds your API credentials can create, change and cancel orders under your API key, so the allowance also limits what a leaked credential can reach. [Security model](/concepts/security-model#order-signatures-and-allowances) covers both.

## The allowance is the on-chain limit

The maker approves the order contract for the chain (see [the table below](#the-order-contract)), in base units of the token sold. Each execution spends part of the allowance. Nothing is reserved when you create an order: at execution, the maker's wallet must still hold the tokens and the allowance, or the order can't execute.

### How much to approve

| Product | The allowance must cover | Why |
| - | - | - |
| Limit order | `amount`, plus the execution gas cost in `inTokenAddress`, plus a buffer | Olympex pays the gas and is reimbursed from `accountTo` in the token sold, as a second transfer. |
| DCA strategy | `totalAmount` | The execution gas is reimbursed out of the token bought, so no extra amount of the token sold is needed. |

To estimate a limit order's gas cost, request a single-chain [`POST /quotes`](/api-reference/quotes/get-quote) for the same pair and amount with `"includeGasInfo": true`. `dataFeeTransaction.transactionFeeInToken` is the estimate in the token sold, and `dataFeeTransaction.valueToApprove` is `amount` plus that estimate. Add a generous buffer on top: the estimate leaves out the Olympex contracts, and gas can cost more when the order executes than when you quoted. [Gas and fees](/concepts/gas-and-fees#limit-orders-and-dca) explains the fields.

Convert human-readable amounts to base units with the token's `decimals` from [`GET /tokens`](/api-reference/tokens/list-tokens), for example with `parseUnits`.

### One allowance per token and chain

Every open limit order and active DCA strategy that sells the same token on the same chain draws on one allowance, because they share the order contract as spender. `approve` sets the allowance to a new value; it doesn't add to it. So when you create a second order that sells the same token, approve the **sum**:

```text theme={null}
allowance = Σ open limit orders       (amount + gas estimate + buffer)
          + Σ active DCA strategies   (totalAmount minus what its successful orders have spent)
```

Recompute it whenever you create, update or cancel an order or a strategy, and set the allowance to the new total.

### Set the allowance

`setOrderAllowance` sets the order contract's allowance for one token to an exact value. Some tokens revert when one non-zero allowance replaces another, so it resets the allowance to `0` first when both values are non-zero.

```ts theme={null}
// order-allowance.ts: sets the order contract's allowance for one token (viem 2). npm install viem
// Reads WALLET_PRIVATE_KEY and POLYGON_RPC_URL from the environment.
import { createPublicClient, createWalletClient, erc20Abi, http, type Address, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { polygon } from "viem/chains";

const ORDER_CONTRACT: Address = "0x50186B03dc7315271FB58da0d3b9f2c65A51dA76"; // Polygon: see "The order contract"
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as Hex); // the maker
const publicClient = createPublicClient({ chain: polygon, transport: http(process.env.POLYGON_RPC_URL) });
const walletClient = createWalletClient({ account, chain: polygon, transport: http(process.env.POLYGON_RPC_URL) });

/** Sets the allowance to exactly `amount`, in base units of `token`. `0n` stops every order that sells it. */
export const setOrderAllowance = async (token: Address, amount: bigint) => {
  const current = await publicClient.readContract({
    address: token,
    abi: erc20Abi,
    functionName: "allowance",
    args: [account.address, ORDER_CONTRACT],
  });
  if (current === amount) return;
  const approve = async (value: bigint) => {
    const hash = await walletClient.writeContract({ address: token, abi: erc20Abi, functionName: "approve", args: [ORDER_CONTRACT, value] });
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    if (receipt.status !== "success") throw new Error(`approve(${value}) reverted: ${hash}`);
  };
  // Some tokens revert when one non-zero allowance replaces another, so reset to 0 first.
  if (current > 0n && amount > 0n) await approve(0n);
  await approve(amount); // approve sets the allowance; it doesn't add to it
};
```

<Warning>
  Never approve an unlimited amount to the order contract. The signature doesn't limit amounts, so an unlimited allowance lets orders for a signed pair spend the maker's whole balance of the token.
</Warning>

## Stop everything: set the allowance to 0

Cancelling an order or a strategy through the API doesn't change the allowance. To stop execution for a token with certainty, set the maker's allowance to the order contract to `0`, for example with `setOrderAllowance(token, 0n)`.

* It stops every limit order and every DCA strategy that sells that token on that chain for that maker, at once. It isn't a per-order switch.
* Cancel the orders and strategies through the API as well, so they don't stay `pending` or `active`.
* To resume, approve again for what your remaining orders need.

Set the allowance to `0` when you stop using limit orders or DCA for a token, and immediately if you suspect that your API credentials have leaked.

## The order contract

The order contract is the spender to approve for limit orders and DCA. It is production, per chain:

| Chain | Chain ID | Order contract |
| - | - | - |
| Ethereum | `1` | [`0xef636bca438D030Cead0AaBD74447Bdf85872F6B`](https://etherscan.io/address/0xef636bca438D030Cead0AaBD74447Bdf85872F6B) |
| Optimism | `10` | [`0xfc07731BE06D93afbfA66E2072B5188A35b93077`](https://optimistic.etherscan.io/address/0xfc07731BE06D93afbfA66E2072B5188A35b93077) |
| BNB Chain | `56` | [`0xA95ccc6f0A561206dd1D0260C1ceE279CB4245A2`](https://bscscan.com/address/0xA95ccc6f0A561206dd1D0260C1ceE279CB4245A2) |
| Polygon | `137` | [`0x50186B03dc7315271FB58da0d3b9f2c65A51dA76`](https://polygonscan.com/address/0x50186B03dc7315271FB58da0d3b9f2c65A51dA76) |
| Base | `8453` | [`0x0eE148d16beB83B08905Db4ad3c9c62d569153f0`](https://basescan.org/address/0x0eE148d16beB83B08905Db4ad3c9c62d569153f0) |
| Arbitrum One | `42161` | [`0x31B15abf6F2c924919605d7ccbc5FB41C8A56Bc1`](https://arbiscan.io/address/0x31B15abf6F2c924919605d7ccbc5FB41C8A56Bc1) |
| Avalanche C-Chain | `43114` | [`0x1a362443D22a481867e1a20C16711f14102D3CB3`](https://snowtrace.io/address/0x1a362443D22a481867e1a20C16711f14102D3CB3) |
| Linea | `59144` | [`0x0eE148d16beB83B08905Db4ad3c9c62d569153f0`](https://lineascan.build/address/0x0eE148d16beB83B08905Db4ad3c9c62d569153f0) |

Limit orders and DCA execute only on chains in this table, and you create orders only on chains that [`GET /chains`](/api-reference/chains/list-chains) returns. Base and Linea use the same address: always pair an address with its chain.

Record the address for each chain you use. If a wallet asks the maker to approve a different spender for an order, stop and confirm with [partners@olympex.io](mailto:partners@olympex.io) before anyone signs.

## Not the swap spender

[`POST /swap`](/api-reference/swap/build-swap) returns `contractToApprove`: the spender for that one swap, which you approve for its exact input amount. The order contract is a different address with its own allowance. An allowance to one doesn't cover the other: approve `contractToApprove` for swaps, and the order contract in the table above for limit orders and DCA.

## What this means for your integration

* Sign each pair once per maker, from an EOA, over the 32 bytes of `inner`, and verify the signature before you send it.
* Keep the allowance to the order contract at the sum of what your open orders and active strategies still need, and never unlimited.
* Set the allowance to `0` to stop every order that sells a token. Cancelling through the API doesn't touch the allowance.
* Approve the order contract for orders and `contractToApprove` for swaps, never one for the other.

## Related

<CardGroup cols={2}>
  <Card title="Limit orders" icon="bullseye" href="/concepts/limit-orders">
    Price trigger, lifecycle and fees.
  </Card>

  <Card title="DCA strategies" icon="calendar-days" href="/concepts/dca">
    Strategies, orders, price bounds and fees.
  </Card>

  <Card title="Place a limit order" icon="bolt" href="/guides/create-a-limit-order">
    Sign, approve, create and track an order.
  </Card>

  <Card title="Security model" icon="shield-halved" href="/concepts/security-model">
    What Olympex can and can't do with funds and credentials.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.