Daejin Developers

Build Transaction

POST
/tx/{kind}

Solana only: build an unsigned transaction (deposit, withdraw, add-liquidity, remove-liquidity, claim, claim-lp-fees) or broadcast a signed one (submit). These move funds rather than read them, so the wallet must sign something: the bridge builds an unsigned transaction, your wallet signs it, and you submit it — with /tx/submit, or straight to your own RPC. The bridge never sees the key and cannot broadcast anything the user has not signed. Requires write. On EVM there is no /tx/* builder: call ClobExchange directly from the user's wallet/RPC, or use examples/market-maker/mm-client.mjs in prediction-backend for testing.

EndpointBodyBuilds the instruction
POST /tx/depositmarketId, user, amountdeposit()
POST /tx/withdrawmarketId, user, amountwithdraw()
POST /tx/add-liquiditymarketId, user, amountaddLiquidity()
POST /tx/remove-liquiditymarketId, userremoveLiquidity()
POST /tx/claimmarketId, userclaim()
POST /tx/claim-lp-feesmarketId, userclaimLpFees()
POST /tx/submitsignedTransactionbroadcasting it yourself

amount is raw units in the payment mint's own decimals — USDC has 6, so 100000000 is $100. Same scale an order's size uses; no floats anywhere. A wallet that has never held the mint gets createsTokenAccount: true and the token account is opened inside the same transaction.

Do not add chainId to these calls; a body with chainId is routed to the EVM adapter, which does not serve /tx/*. /tx/submit waits for confirmation before it answers. Reading an account straight after a bare send is how a working instruction looks like it did nothing.

Build → sign → submit

import { Transaction } from "/web3.js";// wallet: a /web3.js Keypair (testing) or any signer with publicKey + sign// API_KEY: an API key with the write scope, from the portal's keys page// 1. ask the bridge to assemble the Solana transactionconst res = await fetch("https://app-staging.daejin.xyz/clob-engine/tx/deposit", {  method: "POST",  headers: { "content-type": "application/json", "X-API-Key": process.env.API_KEY },  body: JSON.stringify({ marketId: 880101, user: wallet.publicKey.toBase58(), amount: "100000000" }),}).then(r => r.json());// { ok: true, transaction: "<base64>", blockhash, lastValidBlockHeight, createsTokenAccount }// 2. sign it yourself — the only step that touches your keyconst tx = Transaction.from(Buffer.from(res.transaction, "base64"));tx.sign(wallet);// 3. send it, here or straight to your own RPCawait fetch("https://app-staging.daejin.xyz/clob-engine/tx/submit", {  method: "POST",  headers: { "content-type": "application/json", "X-API-Key": process.env.API_KEY },  body: JSON.stringify({ signedTransaction: tx.serialize().toString("base64") }),});// { ok: true, signature: "…", confirmed: true }

The same thing from a terminal

sign.mjs is the whole of your side of this. It ships as scripts/sign.mjs in tokenplay-clob-bridge, and it is short enough to paste:

import { Transaction, Keypair } from "@solana/web3.js";import bs58 from "bs58";const wallet = Keypair.fromSecretKey(bs58.decode(process.env.USER_KEY));let b64 = "";process.stdin.setEncoding("utf8");for await (const chunk of process.stdin) b64 += chunk;b64 = b64.trim();if (!b64 || b64 === "null") {  // jq prints "null" when the build step returned an error instead.  console.error("no transaction on stdin — run the /tx/... call alone and read its error");  process.exit(1);}const tx = Transaction.from(Buffer.from(b64, "base64"));tx.sign(wallet);process.stdout.write(JSON.stringify({ signedTransaction: tx.serialize().toString("base64") }));

Run it as USER_KEY=<base58 secret key> node sign.mjs. This is a testing convenience: a real integration signs with the user's wallet — Phantom, a Ledger, a custodial signer — and the code is the same three lines, Transaction.from, sign, serialize. Nothing about the transaction is secret; only the signing is.

# build — see what comes back before piping it anywherecurl -s -X POST https://app-staging.daejin.xyz/clob-engine/tx/deposit -H "X-API-Key: <your API key>" -H 'content-type: application/json' \  -d '{"marketId":880201,"user":"<pubkey>","amount":"50000000"}'# {"ok":true,"transaction":"<base64>","blockhash":"…","createsTokenAccount":false}# build → sign → submit, in one pipecurl -s -X POST https://app-staging.daejin.xyz/clob-engine/tx/deposit -H "X-API-Key: <your API key>" -H 'content-type: application/json' \  -d '{"marketId":880201,"user":"<pubkey>","amount":"50000000"}' \  | jq -r .transaction | node sign.mjs \  | curl -s -X POST https://app-staging.daejin.xyz/clob-engine/tx/submit -H "X-API-Key: <your API key>" -H 'content-type: application/json' --data-binary @-# {"ok":true,"signature":"…","confirmed":true}

Leaving a placeholder in amount makes the build return an error, jq print null, and the signer choke on nothing. If a pipe dies oddly, run the /tx/ call alone and read its error.

Claim is not withdraw

claim turns winning shares into free balance inside the exchange. withdraw is a second transaction that moves tokens to the wallet. Stopping after the first is the most common "where is my money". remove-liquidity additionally needs the market resolved and the AMM pool to have claimed its own shares, or it reverts AmmNotSettled.

Helper scripts

For trying it by hand before writing a client. EVM uses direct contract transactions through mm-client.mjs; Solana uses the transaction builders above or the local helper below.

# EVM, in prediction-backendnpm run mm -- balancenpm run mm -- deposit --amount 25npm run mm -- add-liquidity --amount 50npm run mm -- claim-lp-feesnpm run mm -- remove-liquiditynpm run mm -- withdraw --amount <clobBalance># Solana, in tokenplay-clob-bridgenode scripts/solana-collateral.mjs balance 880101node scripts/solana-collateral.mjs deposit 880101 100 --executenode scripts/solana-collateral.mjs add-liquidity 880101 100 --executenode scripts/solana-collateral.mjs claim 880101 --executenode scripts/solana-collateral.mjs withdraw 880101 25 --execute

For EVM, claim is winnings only. LP exit is remove-liquidity, then withdraw. For Solana, solana-collateral.mjs is dry-run unless you pass --execute.

Authorization

X-API-Key<token>

Developer-portal API key (tp_live_… / tp_test_…), also accepted as Authorization: Bearer tp_….

In: header

Path Parameters

kind*string

Value in

  • "deposit"
  • "withdraw"
  • "add-liquidity"
  • "remove-liquidity"
  • "claim"
  • "claim-lp-fees"
  • "submit"

Response Body

curl -X POST "https://example.com/tx/deposit"
Empty