For market makers and trading bots.
Network: Ethereum Mainnet
Address: 0x0000000000000000000000000000000000000000 (update
after deployment)
[
{
"type": "function",
"name": "createOrder",
"inputs": [
{ "name": "tokenA", "type": "address" },
{ "name": "amountA", "type": "uint256" },
{ "name": "tokenB", "type": "address" },
{ "name": "amountB", "type": "uint256" }
],
"outputs": [{ "name": "orderId", "type": "uint256" }],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "fillOrder",
"inputs": [{ "name": "orderId", "type": "uint256" }],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "cancelOrder",
"inputs": [{ "name": "orderId", "type": "uint256" }],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "getOrder",
"inputs": [{ "name": "orderId", "type": "uint256" }],
"outputs": [{
"name": "",
"type": "tuple",
"components": [
{ "name": "maker", "type": "address" },
{ "name": "tokenA", "type": "address" },
{ "name": "amountA", "type": "uint256" },
{ "name": "tokenB", "type": "address" },
{ "name": "amountB", "type": "uint256" },
{ "name": "active", "type": "bool" }
]
}],
"stateMutability": "view"
},
{
"type": "function",
"name": "getOrders",
"inputs": [{ "name": "orderIds", "type": "uint256[]" }],
"outputs": [{ "name": "result", "type": "tuple[]", "components": [...] }],
"stateMutability": "view"
},
{
"type": "function",
"name": "canFill",
"inputs": [{ "name": "orderId", "type": "uint256" }],
"outputs": [{ "name": "", "type": "bool" }],
"stateMutability": "view"
},
{
"type": "function",
"name": "nextOrderId",
"inputs": [],
"outputs": [{ "name": "", "type": "uint256" }],
"stateMutability": "view"
},
{
"type": "event",
"name": "OrderCreated",
"inputs": [
{ "name": "orderId", "type": "uint256", "indexed": true },
{ "name": "maker", "type": "address", "indexed": true },
{ "name": "tokenA", "type": "address", "indexed": false },
{ "name": "amountA", "type": "uint256", "indexed": false },
{ "name": "tokenB", "type": "address", "indexed": false },
{ "name": "amountB", "type": "uint256", "indexed": false }
]
},
{
"type": "event",
"name": "OrderFilled",
"inputs": [
{ "name": "orderId", "type": "uint256", "indexed": true },
{ "name": "taker", "type": "address", "indexed": true }
]
},
{
"type": "event",
"name": "OrderCanceled",
"inputs": [
{ "name": "orderId", "type": "uint256", "indexed": true }
]
}
]
Endpoint:
https://api.studio.thegraph.com/query/YOUR_ID/swapboard/version/latest
query OpenOrders($first: Int!, $skip: Int!) {
orders(
first: $first
skip: $skip
orderBy: orderId
orderDirection: desc
where: { active: true }
) {
orderId
maker
amountA
amountB
tokenA {
address
symbol
decimals
}
tokenB {
address
symbol
decimals
}
}
}
query OrdersByPair($tokenA: String!, $tokenB: String!) {
orders(
first: 100
orderBy: orderId
orderDirection: desc
where: {
active: true
tokenA_: { address: $tokenA }
tokenB_: { address: $tokenB }
}
) {
orderId
maker
amountA
amountB
}
}
query OrdersByMaker($maker: Bytes!) {
orders(
first: 100
orderBy: orderId
orderDirection: desc
where: { maker: $maker }
) {
orderId
amountA
amountB
active
taker
tokenA { symbol }
tokenB { symbol }
}
}
query Stats {
globalStats(id: "global") {
totalOrders
filledOrders
cancelledOrders
activeOrders
}
}
query TokenVolume {
tokens(first: 20, orderBy: volumeSold, orderDirection: desc) {
address
symbol
decimals
volumeSold
volumeBought
ordersSelling
ordersBuying
}
}
const { ethers } = require("ethers");
const CONTRACT_ADDRESS = "0x...";
const CONTRACT_ABI = [...]; // See above
async function createOrder(provider, signer, tokenA, amountA, tokenB, amountB) {
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer);
// Approve tokenA first
const tokenContract = new ethers.Contract(tokenA, [
"function approve(address spender, uint256 amount) returns (bool)"
], signer);
const approveTx = await tokenContract.approve(CONTRACT_ADDRESS, amountA);
await approveTx.wait();
// Create order
const tx = await contract.createOrder(tokenA, amountA, tokenB, amountB);
const receipt = await tx.wait();
// Get orderId from event
const event = receipt.logs.find(
log => log.topics[0] === ethers.id("OrderCreated(uint256,address,address,uint256,address,uint256)")
);
const orderId = BigInt(event.topics[1]);
return orderId;
}
async function fillOrder(provider, signer, orderId) {
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, signer);
// Get order details
const order = await contract.getOrder(orderId);
// Approve tokenB
const tokenContract = new ethers.Contract(order.tokenB, [
"function approve(address spender, uint256 amount) returns (bool)"
], signer);
const approveTx = await tokenContract.approve(CONTRACT_ADDRESS, order.amountB);
await approveTx.wait();
// Fill order
const tx = await contract.fillOrder(orderId);
await tx.wait();
}
async function monitorOrders(provider) {
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);
contract.on("OrderCreated", (orderId, maker, tokenA, amountA, tokenB, amountB) => {
console.log(`New order ${orderId}: ${amountA} ${tokenA} for ${amountB} ${tokenB}`);
});
contract.on("OrderFilled", (orderId, taker) => {
console.log(`Order ${orderId} filled by ${taker}`);
});
contract.on("OrderCanceled", (orderId) => {
console.log(`Order ${orderId} canceled`);
});
}
import requests
SUBGRAPH_URL = "https://api.studio.thegraph.com/query/YOUR_ID/swapboard/version/latest"
def get_open_orders(token_a=None, token_b=None, limit=100):
where = "active: true"
if token_a:
where += f', tokenA_: {{ address: "{token_a.lower()}" }}'
if token_b:
where += f', tokenB_: {{ address: "{token_b.lower()}" }}'
query = f"""
{{
orders(
first: {limit}
orderBy: orderId
orderDirection: desc
where: {{ {where} }}
) {{
orderId
maker
amountA
amountB
tokenA {{ address symbol decimals }}
tokenB {{ address symbol decimals }}
}}
}}
"""
response = requests.post(SUBGRAPH_URL, json={"query": query})
return response.json()["data"]["orders"]
// script/CreateOrder.s.sol
pragma solidity ^0.8.33;
import {Script} from "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface ISwapboard {
function createOrder(
address tokenA,
uint256 amountA,
address tokenB,
uint256 amountB
) external returns (uint256);
}
contract CreateOrder is Script {
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address board = vm.envAddress("BOARD_ADDRESS");
address tokenA = vm.envAddress("TOKEN_A");
address tokenB = vm.envAddress("TOKEN_B");
uint256 amountA = vm.envUint("AMOUNT_A");
uint256 amountB = vm.envUint("AMOUNT_B");
vm.startBroadcast(pk);
IERC20(tokenA).approve(board, amountA);
uint256 orderId = ISwapboard(board).createOrder(tokenA, amountA, tokenB, amountB);
vm.stopBroadcast();
}
}
Run with:
PRIVATE_KEY=0x... \
BOARD_ADDRESS=0x... \
TOKEN_A=0x... \
TOKEN_B=0x... \
AMOUNT_A=1000000000000000000 \
AMOUNT_B=3000000000 \
forge script script/CreateOrder.s.sol --rpc-url $RPC_URL --broadcast
| Error | Selector | Description |
|---|---|---|
ZeroAddress() |
0xd92e233d |
Token address is zero |
ZeroAmount() |
0x1f2a2005 |
Amount is zero |
SameToken() |
0x5c122a85 |
tokenA and tokenB are identical |
NotAContract(address) |
0x09ee12d5 |
Address has no code |
BalanceMismatch(uint256,uint256) |
0x2c5211c6 |
FOT token detected |
OrderNotFound(uint256) |
0x97d80e3a |
Order doesn't exist |
OrderNotActive(uint256) |
0x9bd8c2b6 |
Order already filled/cancelled |
NotMaker(uint256,address,address) |
0x3d693ada |
Caller is not order maker |