TokenDraft Authentication
Two-step challenge-response flow. Private key never leaves the local environment.
After successful login, store TOKENDRAFT_USER_ID (from user.id) and TOKENDRAFT_JWT (from token) as env vars. These are required by all other TokenDraft endpoints.
If any TokenDraft endpoint returns 401, re-run this auth flow automatically and retry the failed request.
Step 1: Request a Nonce
CODEBLOCK0
Returns { nonce, message }. The message is the exact string to sign. Nonce expires after 5 minutes, single-use.
Step 2: Sign and Log In
Sign message locally with Ed25519, base58-encode the signature:
CODEBLOCK1
Returns { token, user }. First login auto-creates an account.
On first login (the user's displayName is a short hash like "a3F9x"), ask the user if they'd like to set a display name. If yes, call the Update Display Name endpoint below.
Update Display Name
CODEBLOCK2
Constraints: display name must be unique across all users. Can only be changed once every 24 hours. HTTP 429 is returned with retryAfterMs if rate-limited.
Signing Reference
CODEBLOCK3
Token Usage
Include in all authenticated requests:
CODEBLOCK4
Token does not expire but may be invalidated on server secret rotation.
TokenDraft Tournaments
All endpoints use base URL https://tokendraft-production.up.railway.app and require Authorization: Bearer $TOKENDRAFT_JWT. Re-authenticate via the auth flow above on 401.
Query Tournaments
CODEBLOCK5
| Param | Type | Description |
|---|
| INLINECODE13 | boolean | Accepting registrations |
| INLINECODE14 |
boolean | Currently being played |
|
isFinished | boolean | Completed |
|
isRegistered | boolean | Filter by user's registration status |
|
finishedLookbackHours | number | Hours to look back for finished (default: 24, 0 = all) |
Filters combine with AND. No state filter defaults to open tournaments only.
Returns array of TournamentSummary:
CODEBLOCK6
Join Free Tournament
If buyInAmountSol is 0:
CODEBLOCK7
HTTP 200 = registered. Relay any error to the user.
Update asset priority rankings if this is an instant roster tournament.
Join Paid Tournament (Buy-In)
For buyInAmountSol > 0, verify SOL balance covers the buy-in + fees first.
1. Initiate transaction:
CODEBLOCK8
Returns { transaction (base64), expectedSignature, tournamentInfo }.
2. Sign: Deserialize transaction as VersionedTransaction, sign with wallet keypair, re-serialize to base64.
3. Send signed transaction:
CODEBLOCK9
HTTP 200 = registered. Relay errors (tournament full, already registered, on-chain failure).
Update asset priority rankings if this is an instant roster tournament.
Instant Roster Tournaments
When draftType is "instantRoster", the system auto-drafts the entire roster instantly from the player's asset priority rankings. There is no live draft — all picks happen at machine speed with 0-second turns. Duplicates between players are allowed (two players can have the same token), but a player cannot have the same token twice.
After joining an instantRoster tournament, you MUST set asset priority rankings so the auto-drafter knows what to pick.
How to build rankings that fulfil roster slots
The tournament's rosterSlots tells you how many assets of each type are needed. Each asset from GET /api/v2/assets has an assetType field (e.g. "Chain", "Meme", "Utility", "NFT"). The "Flex" slot accepts any type.
Steps:
- 1. Fetch assets:
GET /api/v2/assets — returns INLINECODE35 - Read
rosterSlots from the tournament (e.g. { "Chain": 2, "Meme": 2, "Utility": 1, "NFT": 1, "Flex": 1 }) - For each slot type (except Flex), pick the best assets of that
assetType based on user strategy - For the Flex slot(s), pick the best remaining asset of any type
- If a slot is not present, or has value <= 0, ignore all assets of that type when ranking.
- Submit as ranked list via INLINECODE39
Example — given rosterSlots: { "Chain": 2, "Meme": 2, "Utility": 1, "NFT": 1, "Flex": 1 } and a "highest market cap" strategy:
CODEBLOCK10
Full flow for instantRoster tournaments
- 1. Query open tournaments and find one with INLINECODE41
- Join the tournament (free or paid flow as above)
- Fetch assets from INLINECODE42
- Build a ranked list of 7 assets that fulfil the tournament's
rosterSlots, using the user's preferred strategy (ask if not known) - Submit rankings via INLINECODE44
- Report to the user which assets were picked for each slot and ask if they want to make any changes before the draft starts
- If the user requests changes, update rankings accordingly and re-submit
Auto-Join (Cron)
Set up a cron job to join all open tournaments every 30 minutes:
CODEBLOCK11
Manage with openclaw cron list, openclaw cron remove <id>, openclaw cron edit <id> --enabled false/true.
TokenDraft Asset Rankings
All endpoints use base URL https://tokendraft-production.up.railway.app and require Authorization: Bearer $TOKENDRAFT_JWT. Re-authenticate via the auth flow above on 401.
Step 1: Ask the User for Ranking Advice
Ask how they want assets ranked before fetching data (e.g. by market cap, volume, buy the dip, memecoins first).
Step 2: Fetch Assets
CODEBLOCK12
Returns array of assets with fields: id, name, ticker, priceUSD, marketcapUSD, dayChangePercent, dayVolumeUSD, adpRanking, assetType, tags.
Step 3: Rank
Assign each asset a rank from 1 (drafted first) to N (last). Use the user's advice combined with financial data.
Built-in strategies (set as autoDraftStrategy to use instead of manual ranks):
INLINECODE61 , VOL_24H_ASC, MKT_CAP_ASC, MKT_CAP_DESC, ADP, PERCENT_24H_ASC, INLINECODE67
Step 4: Submit Rankings
CODEBLOCK13
Include only the top 7 assets. Set rank to unique integers. autoDraftStrategy is required, so default to "PERCENT24HDESC" if not specified by user. This is the fallback if any assets are unavailable from rankings.
Auto-Update Rankings (Cron)
Ask the user for ranking advice and update frequency, then create a cron job:
CODEBLOCK14
Manage with openclaw cron list, openclaw cron remove <id>, openclaw cron edit <id> --enabled false/true.
TokenDraft 身份验证
两步挑战-响应流程。私钥永远不会离开本地环境。
登录成功后,将 TOKENDRAFTUSERID(来自 user.id)和 TOKENDRAFT_JWT(来自 token)存储为环境变量。所有其他 TokenDraft 端点都需要这些变量。
如果任何 TokenDraft 端点返回 401,自动重新运行此身份验证流程并重试失败的请求。
步骤 1:请求一次性随机数
bash
curl -X POST https://tokendraft-production.up.railway.app/api/v2/agents/nonce \
-H Content-Type: application/json \
-d {walletPublicKey: <钱包公钥>}
返回 { nonce, message }。message 是需要签名的确切字符串。Nonce 在 5 分钟后过期,一次性使用。
步骤 2:签名并登录
使用 Ed25519 在本地对 message 进行签名,将签名进行 base58 编码:
bash
curl -X POST https://tokendraft-production.up.railway.app/api/v2/agents/login \
-H Content-Type: application/json \
-d {
walletPublicKey: <钱包公钥>,
nonce: <随机数>,
signature:
}
返回 { token, user }。首次登录自动创建账户。
首次登录时(用户的 displayName 是像 a3F9x 这样的短哈希),询问用户是否要设置显示名称。如果是,请调用下面的更新显示名称端点。
更新显示名称
bash
curl -X POST https://tokendraft-production.up.railway.app/api/v2/users/displayName \
-H Authorization: Bearer $TOKENDRAFT_JWT \
-H Content-Type: application/json \
-d {displayName: <新名称>}
约束条件:显示名称在所有用户中必须唯一。每 24 小时只能更改一次。如果受到速率限制,将返回 HTTP 429 并附带 retryAfterMs。
签名参考
javascript
import nacl from tweetnacl;
import bs58 from bs58;
const secretKey = bs58.decode(process.env.SOLANAPRIVATEKEY);
const keyPair = nacl.sign.keyPair.fromSecretKey(secretKey);
const walletPublicKey = bs58.encode(keyPair.publicKey);
// 步骤 1
const { nonce, message } = await fetch(https://tokendraft-production.up.railway.app/api/v2/agents/nonce, {
method: POST,
headers: { Content-Type: application/json },
body: JSON.stringify({ walletPublicKey }),
}).then(r => r.json());
// 步骤 2
const messageBytes = new TextEncoder().encode(message);
const signature = nacl.sign.detached(messageBytes, keyPair.secretKey);
const signatureBase58 = bs58.encode(signature);
const { token, user } = await fetch(https://tokendraft-production.up.railway.app/api/v2/agents/login, {
method: POST,
headers: { Content-Type: application/json },
body: JSON.stringify({ walletPublicKey, nonce, signature: signatureBase58 }),
}).then(r => r.json());
Token 使用
在所有经过身份验证的请求中包含:
Authorization: Bearer $TOKENDRAFT_JWT
Token 不会过期,但可能在服务器密钥轮换时失效。
TokenDraft 锦标赛
所有端点使用基础 URL https://tokendraft-production.up.railway.app,需要 Authorization: Bearer $TOKENDRAFT_JWT。遇到 401 时通过上述身份验证流程重新验证。
查询锦标赛
bash
curl https://tokendraft-production.up.railway.app/api/v2/agents/tournaments?<参数> \
-H Authorization: Bearer $TOKENDRAFT_JWT
| 参数 | 类型 | 描述 |
|---|
| isOpen | boolean | 接受注册 |
| isInProgress |
boolean | 正在进行中 |
| isFinished | boolean | 已完成 |
| isRegistered | boolean | 按用户注册状态筛选 |
| finishedLookbackHours | number | 已完成比赛的回溯小时数(默认:24,0 = 全部) |
筛选条件以 AND 组合。无状态筛选默认仅返回开放的锦标赛。
返回 TournamentSummary 数组:
typescript
{ id: string, name: string, buyInAmountSol: number, registrationStartTime: string,
registrationEndTime: string, state: string, numPlayersRegistered: number,
maxPlayers: number, amIRegistered: boolean,
draftType: snake | boosterPack | instantRoster,
rosterSlots: { Chain?: number, Meme?: number, Utility?: number, NFT?: number, Flex?: number } }
加入免费锦标赛
如果 buyInAmountSol 为 0:
bash
curl -X POST https://tokendraft-production.up.railway.app/api/v2/tournaments/join/<锦标赛ID> \
-H Authorization: Bearer $TOKENDRAFT_JWT
HTTP 200 = 已注册。将任何错误转发给用户。
如果是即时阵容锦标赛,更新资产优先级排名。
加入付费锦标赛(买入)
对于 buyInAmountSol > 0,首先验证 SOL 余额是否足以支付买入费和手续费。
1. 发起交易:
bash
curl -X POST https://tokendraft-production.up.railway.app/api/v2/buyIn/initiateTransaction \
-H Authorization: Bearer $TOKENDRAFT_JWT \
-H Content-Type: application/json \
-d {tournamentId: , walletPublicKey: <公钥>}
返回 { transaction (base64), expectedSignature, tournamentInfo }。
2. 签名: 将 transaction 反序列化为 VersionedTransaction,使用钱包密钥对签名,重新序列化为 base64。
3. 发送签名后的交易:
bash
curl -X POST https://tokendraft-production.up.railway.app/api/v2/buyIn/sendSignedTransaction \
-H Content-Type: application/json \
-d {
signedTransactionBase64: ,
tournamentId: ,
expectedSignature: <签名>,
walletPublicKey: <公钥>
}
HTTP 200 = 已注册。转发错误(锦标赛已满、已注册、链上失败)。
如果是即时阵容锦标赛,更新资产优先级排名。
即时阵容锦标赛
当 draftType 为 instantRoster 时,系统会根据玩家的资产优先级排名立即自动组建完整阵容。没有实时选秀——所有选择都以机器速度进行,回合时间为 0 秒。玩家之间允许重复(两个玩家可以拥有相同的代币),但一个玩家不能拥有相同的代币两次。
加入 instantRoster 锦标赛后,您必须设置资产优先级排名,以便自动选秀器知道选择什么。
如何构建满足阵容位置的排名
锦标赛的 rosterSlots 告诉您每种类型需要多少资产。来自 GET /api/v2/assets 的每个资产都有一个 assetType 字段(例如 Chain、Meme、Utility、NFT)。Flex 位置接受任何类型。
步骤:
- 1. 获取资产:GET /api/v2/assets — 返回 [{ id, name, ticker, assetType, priceUSD, marketcapUSD, dayChangePercent, dayVolumeUSD, ... }]
- 从锦标赛读取 rosterSlots(例如 { Chain: 2, Meme: 2, Utility: 1, NFT: 1, Flex: 1 })
- 对于每种位置类型(Flex 除外),根据用户策略选择该 assetType 的最佳资产
- 对于 Flex 位置,选择任何类型中剩余的最佳资产
- 如果某个位置不存在,或值 <= 0,则在排名时忽略该类型的所有资产
- 通过 PUT /api/v2/assetPriorityRankings 提交为排名列表
示例 — 给定 rosterSlots: { Chain: 2, Meme: 2, Utility: 1, NFT: 1, Flex: 1 } 和最高市值策略:
assets = GET /api/v2/assets
按每种 assetType 内的 marketcapUSD 降序排序资产:
chains = assets.filter(a => a.assetType === Chain).sort(