✍️ Create Order Signature

Learn how to create and sign Forkast orders using EIP-712 typed data, including domain setup, order structure, and amount calculations.

Orders on Forkast use EIP-712 typed data signing. The signature proves the order was authorized by the wallet owner without exposing the private key.

1️⃣ Step 1: Construct the EIP-712 Domain

{
  "name": "CTF Exchange",
  "version": "1",
  "chainId": 42161,
  "verifyingContract": "0x2D7aa09fe8a9Af205aD6E0Fef1441834c4250cdc"
}

2️⃣ Step 2: Define the Order Type Structure

{
  "Order": [
    { "name": "salt",          "type": "uint256" },
    { "name": "maker",         "type": "address" },
    { "name": "signer",        "type": "address" },
    { "name": "taker",         "type": "address" },
    { "name": "tokenId",       "type": "uint256" },
    { "name": "makerAmount",   "type": "uint256" },
    { "name": "takerAmount",   "type": "uint256" },
    { "name": "expiration",    "type": "uint256" },
    { "name": "nonce",         "type": "uint256" },
    { "name": "feeRateBps",    "type": "uint256" },
    { "name": "side",          "type": "uint8" },
    { "name": "signatureType", "type": "uint8" }
  ]
}

3️⃣ Step 3: Build the Order Message

FieldTypeDescription
saltuint256A unique value per order. Generate by hashing order fields + random entropy + timestamp using SHA-256, then convert to a uint256integer.
makeraddressThe proxy wallet (Gnosis Safe) address — not the EOA.
signeraddressThe user's EOA wallet address (the key that signs).
takeraddress0x0000000000000000000000000000000000000000 (public order, anyone can fill).
tokenIduint256The CTF ERC-1155 token ID of the outcome being traded.
makerAmountuint256See calculation below. Scaled to 6 decimals (USDC precision).
takerAmountuint256See calculation below. Scaled to 6 decimals.
expirationuint256"0" (no expiration).
nonceuint256"0".
feeRateBpsuint256"0".
sideuint80 = Buy, 1= Sell.
signatureTypeuint83 (EIP-712 with Gnosis Safe proxy — signer is EOA, maker is proxy).

🧮 Calculating makerAmount and takerAmount

Amounts are scaled by 1e6(USDC has 6 decimals):

Buy order (side = 0)

makerAmount = floor(amount × price × 1,000,000)   // TC you pay
takerAmount = floor(amount × 1,000,000)            // outcome tokens you receive

Sell order (side = 1)

makerAmount = floor(amount × 1,000,000)            // outcome tokens you sell
takerAmount = floor(amount × price × 1,000,000)    // TC you receive

Example — buying 50 shares at a price of 0.80:

makerAmount=floor(50×0.80×1000000)=40000000takerAmount=floor(50×1000000)=50000000\text{makerAmount} = \text{floor}(50 \times 0.80 \times 1000000) = 40000000 \newline \text{takerAmount} = \text{floor}(50 \times 1000000) = 50000000

4️⃣ Step 4: Generate a Unique Salt

import crypto from "crypto";

const raw = [
  signerAddress.toLowerCase(),
  makerAddress.toLowerCase(),
  tokenId,
  side,
  type,
  price,
  amount,
  (BigInt(Date.now()) * BigInt(1_000_000)).toString(),
  crypto.randomBytes(16).toString("hex"),
].join("|");

const digest = crypto.createHash("sha256").update(raw).digest("hex");
const salt = BigInt("0x" + digest).toString();

5️⃣ Step 5: Sign with EIP-712

import { ethers } from "ethers";

const domain = {
  name: "CTF Exchange",
  version: "1",
  chainId: 42161,  // or 421614 for testnet
  verifyingContract: "0x2D7aa09fe8a9Af205aD6E0Fef1441834c4250cdc",
};

const types = {
  Order: [
    { name: "salt", type: "uint256" },
    { name: "maker", type: "address" },
    { name: "signer", type: "address" },
    { name: "taker", type: "address" },
    { name: "tokenId", type: "uint256" },
    { name: "makerAmount", type: "uint256" },
    { name: "takerAmount", type: "uint256" },
    { name: "expiration", type: "uint256" },
    { name: "nonce", type: "uint256" },
    { name: "feeRateBps", type: "uint256" },
    { name: "side", type: "uint8" },
    { name: "signatureType", type: "uint8" },
  ],
};

const message = {
  salt: salt,
  maker: proxyWalletAddress,
  signer: walletAddress,
  taker: ethers.ZeroAddress,
  tokenId: tokenId,
  makerAmount: "40000000",
  takerAmount: "50000000",
  expiration: "0",
  nonce: "0",
  feeRateBps: "0",
  side: 0,
  signatureType: 3,
};

const wallet = new ethers.Wallet(privateKey);
const signature = await wallet.signTypedData(domain, types, message);

✅ Validation Rules

FieldConstraint
sideMust be 0 (Buy) or 1 (Sell)
amountMust be > 0, max 2 decimal places
priceMust be > 0 and < 1, max 2 decimal places
signerMust match the private key used to sign
signatureTypeMust be 3 (EOA signer + Gnosis Safe maker)