references/claim.md
# Claim Flow — Reward Eligibility Check, Atomic Claim, Contact Collection
> Scope: claim phase — reward eligibility check, atomic claim, top-tier contact collection. Global rules in `../SKILL.md`.
## Check Participation Status
```bash
onchainos competition user-status # all activities (uses accountId)
onchainos competition user-status --activity-id <id> # single activity (uses accountId)
```
Display: join status, join time, reward status, reward amount.
- If `rewardStatus=1`: proactively ask "You have won a reward. Would you like me to claim it for you?"
- If `rewardStatus=4`: use the **Pending-draw canonical template** (English canonical below; translate to the user's language; substitute `{activityName}` from the activity's `name` / `shortName` field; do NOT paraphrase the 5-business-day window):
> "{activityName} has ended. The winners list is currently being finalized. The final reward list will be announced within 5 business days after the activity end — please return here to check your result and claim your reward then. Thank you for participating!"
- If `rewardStatus=3`: "Your reward has expired and can no longer be claimed."
## Step 6 — Claim Reward
Check status first via `competition_user_status` (see [Check Participation Status](#check-participation-status) above for the full flow):
| `rewardStatus` | Action |
|---|---|
| 0 | Inform user, no claim needed |
| 1 | Proceed to claim |
| 2 | Inform user (already claimed) |
| 3 | "Your reward has expired and can no longer be claimed" |
| 4 | Render the **Pending-draw canonical template** (see [Check Participation Status](#check-participation-status) above); do NOT call `competition_claim` |
### Pre-claim guard (rewardStatus=4 / Pending draw)
When the user explicitly requests to claim a reward (any "claim my reward" / "claim X" intent in any language) for an activity whose `rewardStatus` is `4` (Pending draw), do **NOT** call `competition_claim`. Render the **Pending-draw canonical template** (see [Check Participation Status](#check-participation-status) above, with `{activityName}` substituted) instead.
This applies whether the user explicitly named the activity or you inferred it from prior status output. (`rewardStatus=4` means the winners list is still being computed; a claim call would return a confusing backend error like "no eligible reward record." The Pending-draw template handles this case in product-canonical language.)
### Atomic claim (the only correct path)
Both the MCP tool `competition_claim` and the CLI `onchainos competition claim` now do the **same atomic flow**: pre-check `rewardStatus`, fetch calldata, sign each entry with the TEE session, broadcast on-chain, return txHash array. The CLI no longer returns raw unsigned calldata — the only externally visible behavior is the final result.
**Pre-claim preview**: before calling `competition_claim`, render the preview line below and wait for explicit confirmation. Reward fields come from the `competition_user_status` call already made in the pre-check step — do NOT make an extra round-trip just to fetch them.
**Why**: the atomic claim signs + broadcasts on-chain in one shot — there's no abort point after the tool fires. The preview is the user's only chance to verify the chain, token, and amount they're about to lock in. Skipping it makes the agent silently transact on the user's behalf.
Template (English canonical; translate natural-language strings to the user's language; preserve placeholders verbatim):
```
You are about to claim {rewardAmount} {rewardUnit} on {chainName}. Reply "confirm" to proceed.
```
Field-mapping:
- `{rewardAmount}` ← `competition_user_status` → matched entry's `rewardAmount`
- `{rewardUnit}` ← `competition_user_status` → matched entry's `rewardUnit`
- `{chainName}` ← from the activity's `chainId` mapped via `../SKILL.md` → Facts (chain id → display name table). Claim runs on the `chainId` claim chain, NOT `participateChainIds`.
Only invoke `competition_claim` after the user replies with an affirmative (e.g. `confirm`, `yes`, `ok`). On any other reply, treat as decline and stop without calling the tool.
Call `competition_claim` (MCP: `activity_name` only, wallets auto-resolved from active account; CLI: explicit `--evm-wallet` / `--sol-wallet` — see `cli-reference.md` for full result shape).
**How to report to the user:**
- All succeeded (`failed: []`): "Claimed {rewardAmount} {rewardUnit}, tx hash: {txHash}"
- Partial success (some `failed`): list each succeeded txHash, then list the failed entries with their `error`, then append the **fixed failure-suggestion block** (template below). **Do NOT re-run claim blindly** — succeeded entries already landed; another call will hit the "reward already claimed" guard.
- All failed: the tool returns an error, not this shape — surface the error message verbatim, then append the **fixed failure-suggestion block**.
- **If `needContact: true` in the response** (user is a top-tier winner who has NOT yet submitted contact info): after the success line above, also render the **Contact-collection prompt** below — invite (do NOT force) the user to share one contact method. See `### Contact collection (top-tier winners only)` further down for the prompt template, parsing rules, and follow-up.
The flow blocks before signing if `rewardStatus` is 0 (not eligible), 2 (already claimed), 3 (expired), or 4 (winners not announced yet). The error message is plain text — relay it to the user. **Skip** the failure-suggestion block in these pre-check rejections (they are semantic, not transient — telling the user to "check Gas / try later" is misleading).
#### Fixed failure-suggestion block
For runtime failures (signing/broadcast/simulation errors, network errors, unknown errors), append this block after the error description. Translate to the user's language while preserving the heading + 3 bullet items in this order. Do NOT add or remove items.
Template:
```
Suggestions:
- The claim process requires Gas. Please make sure your Gas is sufficient.
- Try again later — this may be a transient network issue.
- If it fails repeatedly, please contact customer support.
```
- Do NOT chain `gateway_broadcast` after a claim call — the on-chain submission already happened inside the tool. Re-broadcasting either fails (the tx is already mined) or, for Solana, signs a different transaction that the TEE rejects.
- Do NOT manually construct, encode, or sign a transaction (no Python base58 encoding, no manual hex assembly). The TEE-managed wallet key is the only valid signer; any locally-constructed tx will be rejected by the contract's signature check.
- Do NOT inspect the result for an empty `base58CallData` and conclude the CLI cannot sign a Solana claim — that field is empirically empty for Solana; the CLI/MCP code internally falls back to encoding `tx.data` byte array via base58 and proceeds. Just trust the `succeeded[]` and `failed[]` arrays.
- Do NOT split into a two-step "fetch calldata then wallet contract-call" flow — that mode no longer exists; the claim command is atomic.
**On claim error (code 11002 `not eligible for reward`):** "You did not win a reward and cannot claim."
**On any other error:** "Operation failed. Please contact customer support."
### Contact collection (top-tier winners only)
Run this sub-flow **if and only if** the `competition_claim` response contains `needContact: true` (the backend's signal that this user is a top-tier winner whose merchandise delivery requires contact info). Do NOT run it when `needContact: false` or the field is missing. Do NOT ask for a contact pro-actively in any other claim path.
**Step 6a — After the claim-success line, append this prompt** (English canonical; translate the natural-language strings to the user's conversation language; keep the 4 numbered options in this exact order and the literal labels `Telegram` / `WeChat` / `Email` / `Twitter (X)` as-is — these are product-canonical, do not paraphrase):
```
Congratulations on your standout performance in this competition! As a thank-you, we have a custom merchandise pack reserved for top winners. Please share ONE of the following contact methods so we can reach out about delivery — sharing is optional:
1. Telegram
2. WeChat
3. Email
4. Twitter (X)
```
**Step 6b — When the user replies with a contact method**, run this checklist before calling `competition_submit_contact`:
1. **Parse** `contactType` and `contactValue` from the message:
- `Telegram @handle` / `tg @handle` / `Telegram: @handle` → `contactType=Telegram`, `contactValue=<handle>` (preserve `@` if user included it)
- `WeChat <id>` / `WeChat: <id>` → `contactType=WeChat`, `contactValue=<id>`
- Anything looking like an email (`user@domain.com`) or `Email <addr>` → `contactType=Email`, `contactValue=<address>`
- `Twitter @handle` / `X @handle` / `Twitter: @handle` → `contactType=Twitter`, `contactValue=<handle>`
2. **Validate `contactType`** against the case-sensitive enum `{Telegram, WeChat, Email, Twitter}` — the backend rejects anything else (including lowercase `telegram` or `wechat`). On mismatch, re-prompt with the 4 numbered options from Step 6a and restart at step 1; do NOT auto-correct casing silently.
3. **Disambiguate** if the message is ambiguous (e.g. a bare `@username` with no platform): ask once `Which platform — Telegram, WeChat, Email, or Twitter (X)?`. Do NOT guess. After the reply, restart at step 1.
4. **Trim** `contactValue` to ≤ 256 characters; if longer, ask the user to shorten — do NOT truncate silently.
5. Proceed to Step 6c only after steps 1–4 pass.
**Step 6c — Call `competition_submit_contact`**:
```
competition_submit_contact(
activity_name="<same activity name used in competition_claim>",
contact_type="Telegram" | "WeChat" | "Email" | "Twitter",
contact_value="<the parsed value, max 256 chars>"
)
```
CLI equivalent:
```bash
onchainos competition submit-contact --activity-id <id> --contact-type Telegram --contact-value "@testemma"
```
**Step 6d — On `submitted: true` response, render this confirmation** (English canonical; translate to the user's language; do NOT echo the contact value back; do NOT show any internal id):
```
Got it. Thanks for sharing! We will reach out shortly — please keep an eye on your messages.
```
**On submit_contact error**, surface the message verbatim with a short hint:
- If the backend returns a validation error on `contactType`, re-prompt the user with the 4 options.
- If `not registered for activity` — this should never happen post-claim; flag as a backend anomaly and tell the user to retry later.
- Other errors: "Failed to record your contact, please try again later or contact customer support."
- Do NOT push / pressure the user if they decline to share. Acknowledge politely and move on.
- Do NOT prompt for multiple contacts — one is enough. Stop after the first valid submission.
references/cli-reference.md
# competition CLI Reference
All commands: `onchainos competition <subcommand> [flags]`
## competition list
List Agentic Wallet exclusive trading competitions.
```
onchainos competition list [--status <0|1|2>] [--page-size <n>] [--page-num <n>]
```
**API**: `GET /priapi/v1/dapp/agentic/competition/list`
| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `--status` | int | — | 0=active, 1=ended, 2=all; omit for all |
| `--page-size` | int | 10 | Results per page |
| `--page-num` | int | 1 | Page number (1-based) |
**Output:**
```json
{
"availableCompetitions": [
{
"id": 100,
"shortName": "hippo",
"name": "HIPPO Trading Competition",
"rewards": "50000 HIPPO",
"startTime": 1742913600,
"endTime": 1743432000,
"startTimeFormatted": "2025-03-26 02:13:20 (UTC+8)",
"endTimeFormatted": "2025-04-01 02:13:20 (UTC+8)",
"timeRangeFormatted": "2025-03-26 ~ 2025-04-01",
"chainId": 196,
"chainName": "X Layer",
"status": 3
}
],
"totalCount": 2
}
```
**`*Formatted` rule (whole file)**: any `*Formatted` sibling is a CLI-computed UTC+8 string with the ` (UTC+8)` suffix included — render verbatim, never recompute from the raw Unix value.
**Note**: Response `status` field uses different values from the query param:
- Query param: `0`=active, `1`=ended, `2`=all
- Response field: `3`=active, `4`=ended
Activity URL: `https://web3.okx.com/boost/trading-competition/<shortName>`
---
## competition detail
Get competition rules, prize pool, and timeline.
```
onchainos competition detail --activity-id <id>
```
**API**: `GET /priapi/v1/dapp/agentic/competition/detail`
| Flag | Required | Description |
|------|----------|-------------|
| `--activity-id` | Yes | Activity ID from `competition list` |
**Output:** Competition object. Key fields:
- `chainId` / `chainName`: the activity's **claim / reward chain ONLY** — the reward contract and activity address live on this chain. NOT a trading chain unless it also appears in `participateChainIds`.
- `participateChainIds`: the **trading-chain set** — only trades on chains in this array count toward the competition standing. Returned by **both `list` and `detail`** endpoints. Trading-eligibility = `participateChainIds`. Claim path = `chainId`.
- `startTime` / `endTime`: raw Unix seconds (do not display; use the `*Formatted` siblings).
- `tabConfigs[]`: per-leaderboard config. `rankFieldConfig[].title` / `.key` / `.sortValueMap.descend` drive the rank tool. `prizePoolDistribution[].rules[]` (`interval`, `reward`) + `rewardUnit` / `totalReward` / `rewardType` (`5`=volume, `7`=PnL, `8`=boost) populate the details template. May be empty on pre-prod.
- `totalPrizePool`: CLI-pre-summed reward pool — `{ amountByUnit: [{amount, rewardUnit}], display: "50,000 USDC" }` (multi-unit `display` joined by ` + `). Merges top-level + all `tabConfigs[].prizePoolDistribution[]`, grouped by `rewardUnit` with exact decimal-string sums. `null` when no distribution; `partial: true` if one entry was unparseable. Render `display` directly — do NOT sum yourself.
---
## competition rank
Get leaderboard and current user ranking.
```
onchainos competition rank --activity-id <id> [--wallet <addr>] --sort-type <type> [--limit <n>]
```
**API**: `GET /priapi/v1/dapp/agentic/competition/rank`
> The backend takes either `accountId` (self-query) or `walletAddress` (cross-user query) — never both. Omit `--wallet` to query your own rank; the command loads `accountId` from the active wallet session. Pass `--wallet` only to query someone else's rank; the address chain (EVM `0x...` else Solana) must match the activity chain or the command errors out (no silent wrong-chain query).
| Flag | Required | Default | Description |
|------|----------|---------|-------------|
| `--activity-id` | Yes | — | Activity ID |
| `--wallet` | No | (uses active account's `accountId` instead) | Optional wallet address — pass to query someone else's rank (chain-validated against the activity). |
| `--sort-type` | Yes | 1 | Currently observed: 1=PnL%, 7=PnL. Future activities may add more — discover via `competition detail` → `tabConfigs[].rankFieldConfig[].sortValueMap.descend`. |
| `--limit` | No | 20 | Max entries in `allRankInfos` (max 100; applied client-side) |
| `--all` | No | — | Fetch ALL leaderboards in one call (enumerated from `detail`, non-board columns filtered). Mutually exclusive with `--sort-type`. Returns `{activityId, boards:[…]}`. |
**Output:**
```json
{
"myRankInfo": {
"currentRank": 42,
"nickName": "Agentic...abcd",
"userTotal": "1250.5",
"expectedRewards": "100",
"format": 1,
"rewardUnit": "HIPPO"
},
"allRankInfos": [ ... ],
"rankUpdateTime": 1774359000638,
"rankUpdateTimeFormatted": "2026-03-24 18:50:00 (UTC+8)",
"agenticActivity": true,
"totalRewardToken": "1000000",
"rewardTokenSymbol": "HIPPO"
}
```
**`--all` output:** `{ "activityId": "<id>", "boards": [ { "sortType": <n>, "allRankInfos": [...], "myRankInfo": {...}, "rankUpdateTime": <ms> }, … ] }`. A failed board becomes `{sortType, error}`; overall `ok:true` if ≥1 board succeeds. Enumeration failure → `errorCode:"upstream_error"`; no leaderboards → `errorCode:"no_leaderboards"`.
`format`: `1`=number, `2`=percentage, `3`=token amount with unit. `userTotal` semantics come from `tabConfigs[].rankFieldConfig[].title` / `.key`.
---
## competition user-status
Get user's participation and reward status.
```
onchainos competition user-status [--activity-id <id>]
```
**API**:
- Single activity (`--activity-id` provided) → `GET /priapi/v1/dapp/agentic/competition/userStatus`
- All activities (`--activity-id` omitted) → `GET /priapi/v1/dapp/agentic/competition/batchUserStatus` (chunked at 20 ids per call, results merged transparently)
> The CLI sends `accountId` (loaded from the local wallet session) as the API identity, NOT a wallet address. One `accountId` covers every chain in the competition's `participateChainIds` — no chain picking, no wallet args. The batch endpoint replaces per-activity loops with a single (chunked) round-trip.
| Flag | Required | Description |
|------|----------|-------------|
| `--activity-id` | No | Activity ID; omit to check **all** activities (active + ended) |
Per-activity payload from `batchUserStatus` also carries `joinedAddress`, `winnerDownUrl`, `needContact` — fields absent from the single-activity response.
**Output (single activity):**
```json
{
"joinStatus": 1,
"joinTime": 1742920000,
"joinTimeFormatted": "2025-03-26 03:46:40 (UTC+8)",
"rewardStatus": 1,
"claimTime": null,
"claimTimeFormatted": null,
"rewardAmount": "10000",
"rewardUnit": "HIPPO",
"winnerDownUrl": "https://..."
}
```
`joinTime` / `claimTime`: raw Unix seconds (`null` if no event yet). `*Formatted` siblings are `null` when the source is `null`/`0`.
**Output (all activities — no --activity-id):**
```json
[
{
"activityId": 106,
"activityName": "XXX Trading Competition",
"shortName": "xxx",
"chainName": "Solana",
"activityStatus": 4,
"userStatus": { "joinStatus": 1, "rewardStatus": 1, "rewardAmount": "45", ... }
}
]
```
| Field | Values |
|-------|--------|
| `joinStatus` | 0=not joined, 1=joined |
| `rewardStatus` | 0=not won, 1=won (unclaimed), 2=claimed, 3=expired, 4=pending draw (winners not yet announced) |
`rewardAmount`, `rewardUnit`, `winnerDownUrl` only present when `rewardStatus=1` or `2` (a winner has been determined).
---
## competition join
Register for a competition. **Requires wallet login.**
```
onchainos competition join --activity-id <id> --evm-wallet <evm_addr> --sol-wallet <sol_addr> --chain-index <chain_id>
```
**API**: `POST /priapi/v5/wallet/agentic/competition/join`
**Extra header**: `OK-ACCESS-PROJECT: 4d156bf0c61130f2692d097ecb68dbe4`
| Flag | Required | Description |
|------|----------|-------------|
| `--activity-id` | Yes | Activity ID |
| `--evm-wallet` | Yes | EVM wallet address (XLayer) |
| `--sol-wallet` | Yes | Solana wallet address |
| `--chain-index` | Yes | Competition chain id (e.g. `"1"` Ethereum, `"196"` X Layer). Read from `competition_detail` → `chainIndex`. |
Body is built automatically: `accountId` is loaded from `wallet_store` (active session); other fields come from the flags verbatim. CLI wraps the bare `{ "code": 0 }` API response into a confirmation object:
```json
{ "joined": true, "activityId": "100", "evmAddress": "0x...", "solAddress": "...", "chainIndex": "1" }
```
**Errors:**
- `not logged in` → run `onchainos wallet login`
- `address limit reached` → one address per user per competition
- region blocked → "service is not available in your region"
---
## competition claim
**Atomic** claim flow: pre-checks `rewardStatus`, fetches calldata, signs each entry with the TEE session, broadcasts on-chain, and returns txHash array. **Requires wallet login.**
```
onchainos competition claim --activity-id <id> --evm-wallet <evm_addr> --sol-wallet <sol_addr>
```
**API**: `POST /priapi/v5/wallet/agentic/competition/claim` (called internally; output is post-broadcast txHashes, not raw calldata)
**Extra header**: `OK-ACCESS-PROJECT: 4d156bf0c61130f2692d097ecb68dbe4`
| Flag | Required | Description |
|------|----------|-------------|
| `--activity-id` | Yes | Activity ID |
| `--evm-wallet` | Yes | EVM wallet address |
| `--sol-wallet` | Yes | Solana wallet address |
**Output:** aggregate result with reward metadata, successful txHashes, and any per-entry failures. Also surfaces `needContact` (true for top-tier winners who have not yet shared a contact method), plus the activity/account/wallet identifiers needed by the downstream `submit-contact` flow:
```json
{
"ok": true,
"data": {
"rewardAmount": "460",
"rewardUnit": "PYBOBO",
"totalEntries": 1,
"succeeded": [{
"contractAddress": "7KRu...",
"chain": "501",
"txHash": "5abc...",
"orderId": "..."
}],
"failed": [],
"needContact": false,
"activityId": "107",
"accountId": "5747d742-...",
"joinedAddress": "0x8e3f..."
}
}
```
Pre-checks `rewardStatus` before signing — bails on 0 (not won), 2 (already claimed), 3 (expired), 4 (pending draw). Otherwise signs + broadcasts each entry atomically (Solana entries fall back to base58-encoding `tx.data` because `base58CallData` is empirically empty).
**Errors:**
- code 11002 `not eligible for reward` → user did not win
- code 11003 → activity not found / status mismatch
- code 11008 → reward already claimed or claim window expired
- code 1860402 → backend failed to assemble the transaction; retry, then escalate
- "Sui-chain reward claims are not yet supported" → user must claim from the Sui-compatible wallet UI
---
## competition submit-contact
Record a contact method for top-tier winners (Top 10 on PnL% / PnL leaderboards). Called **only** after a `competition claim` that returned `needContact: true`, and only when the user has affirmatively shared a contact value. **Requires wallet login.**
```
onchainos competition submit-contact --activity-id <id> --contact-type <type> --contact-value <text>
```
**API**: `POST /priapi/v5/wallet/agentic/competition/submitContact`
**Extra header**: `OK-ACCESS-PROJECT: 4d156bf0c61130f2692d097ecb68dbe4`
| Flag | Required | Description |
|------|----------|-------------|
| `--activity-id` | Yes | Activity ID |
| `--contact-type` | Yes | One of: `Telegram`, `WeChat`, `Email`, `Twitter` (case-sensitive — backend rejects other values) |
| `--contact-value` | Yes | The contact value (max 256 chars). e.g. `@username` for Telegram/Twitter, the WeChat ID, the email address |
**Output:**
```json
{
"ok": true,
"data": {
"submitted": true,
"activityId": "107",
"contactType": "Telegram"
}
}
```
**Errors:**
- `contactType must be one of: Telegram, WeChat, Email, Twitter` → caller typo; backend rejects anything else
- `contactValue exceeds 256 character limit` → trim before retry
- `not registered for activity X` → user never joined; submit-contact only makes sense post-claim
- `Refresh token expired` → re-login required
references/details.md
# Details — View Competition Rules & Prize Pool
> Scope: full competition detail view — rules / prize pool / 4 reward sections (PNL% / PnL / Participation / Skill Quality). `{supportedChains}` algo: see `participation.md` → Shared field mapping. Global rules in `../SKILL.md`.
## Step 2 — View Details
```bash
onchainos competition detail --activity-id <id>
```
**Display competition / reward info using the fixed English template below.** The structure (sections, ordering, numbered list, placeholder positions, the `{supportedChains}` placeholder on the chain line) is fixed. Copy the template character-for-character; only fill in placeholders. Do not paraphrase, abbreviate, or substitute synonyms.
When the user's language is not English, translate the natural-language strings to the user's language while preserving the structure, the placeholders, and every required content invariant listed below. Do not reorder, omit, or merge sections.
### Fixed display template
```
Basic Information
Supported chains: {supportedChains}
Duration: {startTime} ~ {endTime}
Total Prize Pool: {totalPrizePool}
Prize Categories:
Realized PNL% Prize Pool ({roiPoolAmount})
Ranked from highest to lowest by realized PNL%.
{roiRankTable}
Realized PnL Prize Pool ({pnlPoolAmount})
Ranked from highest to lowest by realized PNL amount.
{pnlRankTable}
Participation Prize ({participationPoolAmount})
Registered users who accumulate $100 or more in total trading volume via Agentic Wallet and maintain a total wallet balance of $100 or above throughout the competition period, will share the {participationPoolAmount} participation prize pool equally. Random asset snapshots will be taken during the competition period to verify eligibility.
Skill Quality Prize ({skillPoolAmount})
The Skill Quality Prize is an independently judged award. During the competition period, participants may submit their Agent Skills through the event landing page. Eligible submissions include, but are not limited to, on-chain autonomous yield strategies, trading analysis, and trading signal monitoring. All submitted Agent Skills will be evaluated through a dual-review process combining AI pre-screening and manual judging. The top {skillTopN} Skill creators by score will each receive a reward of {skillPerCreatorReward}.
```
### Field-mapping rules
- Chain line ← `{supportedChains}` — see `participation.md` → Shared field mapping.
- `{startTime}` / `{endTime}` ← `startTimeFormatted` / `endTimeFormatted` (CLI-formatted, already ends in `(UTC+8)` — render verbatim).
- `{totalPrizePool}` ← `data.totalPrizePool.display` from `competition detail` (render-ready; multi-unit joined by ` + `). `null` → render "no prize-pool info". Do NOT sum `prizePoolDistribution[]` yourself.
- `{roiPoolAmount}` ← totalReward of the realized-PnL% tab.
- `{pnlPoolAmount}` ← totalReward of the realized-PnL tab.
- `{participationPoolAmount}` ← totalReward of the participation prize tab.
- `{skillPoolAmount}` ← totalReward of the Skill quality prize tab.
- `{skillTopN}` ← upper bound of the Skill tab's `rules[].interval` (e.g. `"1-10"` → `10`).
- `{skillPerCreatorReward}` ← that rule entry's `reward` + `rewardUnit` (e.g. `500 USDC`).
- `{roiRankTable}` / `{pnlRankTable}` ← markdown table built from the corresponding tab's `rules[]`. Format (English canonical; localize headers to user's language):
```
| Rank | Reward |
|------|--------|
| <interval-formatted> | <reward-formatted> |
| ... | ... |
| Total | <totalReward> {rewardUnit} |
```
Interval / reward formatting per row:
- Single rank (`interval = "1"`) → Rank cell `Rank 1`, Reward cell `<reward> <rewardUnit>` (no `each` prefix)
- Range (`interval = "2-6"`) → Rank cell `Ranks 2-6`, Reward cell `<reward> <rewardUnit> each`
- Always end with a totals row whose Reward cell is the tab's `totalReward` + `rewardUnit`.
If any of the four pools is absent for a particular activity, omit just that section (keep the others as-is).
### Required content invariants (per section)
**Section 1 — Realized PNL% Prize Pool**
- Title MUST be exactly `Realized PNL% Prize Pool` (or its faithful translation in the user's language). Do NOT substitute with `PnL% Ranking Award`.
- Description MUST mention: ranking by realized PNL%, highest to lowest.
- Rank table MUST have headers `Rank / Reward` and end with a `Total` row.
**Section 2 — Realized PnL Prize Pool**
- Title MUST be exactly `Realized PnL Prize Pool`. Do NOT substitute with `PnL Ranking Award` / `Realized PnL Pool`.
- Description MUST mention: ranking by realized PNL amount, highest to lowest.
- Rank table MUST follow the same format as Section 1.
**Section 3 — Participation Prize** (PRODUCT-MANDATED COPY)
- Title MUST be exactly `Participation Prize`.
- The description body MUST include all of these specific terms:
- `Agentic Wallet`
- accumulate `$100` or more in total trading volume
- maintain a total wallet balance of `$100` or above throughout the competition period
- share the participation prize pool equally
- random asset snapshots to verify eligibility
**Section 4 — Skill Quality Prize** (PRODUCT-MANDATED COPY)
- Title MUST be exactly `Skill Quality Prize`.
- The description body MUST include all of these specific terms:
- independently judged award
- submission of Agent Skills through the event landing page
- examples of eligible submissions (on-chain autonomous yield strategies, trading analysis, trading signal monitoring)
- dual-review process combining AI pre-screening and manual judging
- `top {skillTopN} Skill creators ... each receive a reward of {skillPerCreatorReward}`
- Do NOT use bullet markers (`-`) inside the four numbered sections — the structure is `1. Title (amount)\n description text` then the rank table; not a bullet list.
After printing the template, ask: `Would you like me to register you for this competition?`
references/participation.md
# Participation Flow — Discover, Register, Trade, Track Standing
> Scope: participation phase — discover / register / trade / registered-wallet / export guard. Global rules in `../SKILL.md`.
## Shared field mapping
### `{supportedChains}`
Computed from `participateChainIds` only (trading-chain set, see `../SKILL.md` Facts).
1. Take the ids in `participateChainIds` (in backend-returned order).
2. Map each id via `1 → Ethereum`, `196 → X Layer`, `501 → Solana`.
3. Join with `, `.
Example: `[196, 501]` → `X Layer, Solana`.
## Step 1 — Discover Competitions
### Choosing the status filter
Default `status=2` (all). Use `status=0` only when user explicitly wants active (e.g. "which can I join now"), `status=1` only for ended ("winners list").
**Display the result as markdown tables — one row per competition. Do not use a numbered prose list, do not collapse fields into a single sentence.**
When the result contains BOTH active (`activityStatus=3`) and ended (`activityStatus=4`) entries, **split into two separate tables under bold subheadings (`**Active**` / `**Ended**`, translated to the user's language), in that order**. When only one status is present, render a single table without a subheading.
### Fixed table template
| Name | Chain | Time | Total Prize Pool | Details |
|------|-------|------|------------------|---------|
| {name} | {supportedChains} | {timeRange} | {rewards} | [View](https://web3.okx.com/boost/trading-competition/{shortName}) |
### Field-mapping rules
- Group rows by `availableCompetitions[].status`: `3` → Active table, `4` → Ended table.
- Name column ← `name`
- **Chain column** ← `{supportedChains}` — see [Shared field mapping](#shared-field-mapping) above.
- Time column ← `{timeRange}` from `timeRangeFormatted`.
- Total Prize Pool column ← `rewards` field (already a formatted string like `50,000 USDC`)
- Details column ← `https://web3.okx.com/boost/trading-competition/<shortName>` as a markdown link
After the table(s), ask the user (in their language):
- If only Active has entries: `Which competition would you like to view in detail, or would you like to register directly?`
- If only Ended has entries: `Would you like to check your ranking or claim status for any of these?`
- If both: combine — `Which active competition would you like to register or view, or which ended competition would you like to check your ranking / claim?`
### Empty-result handling (English canonical; translate to user's language)
- All filters returned 0 entries → `No trading competitions available right now.`
- `status=0` filter returned 0 entries → `No active trading competitions at the moment.`
- `status=1` filter returned 0 entries → `No ended trading competitions yet.`
## Step 3 — Join (requires wallet login)
Call `competition_join` (MCP: wallet args auto-resolved from active account; CLI: explicit address params — see `cli-reference.md`). Get `chain_index` from `competition_detail` → `chainIndex` field.
If not logged in, route via `../SKILL.md` → Pre-flight (Cross-skill routing), then retry registration.
### Required pre-flight: distinguish duplicate-registration scenarios
**Before calling `competition_join`, you MUST first call `competition_user_status` for the activity to read the current account's `joinStatus`.** This separates the two duplicate-registration cases (Scenario A / B below) — without the pre-flight both collapse into a generic backend error.
| Scenario | `user_status.joinStatus` (current account) | Action | Template |
|----------|-------------------------------------------|--------|----------|
| **A — current account already joined** | `1` | Do NOT call `competition_join` | Scenario A template (below) |
| **B — current account NOT joined** | `0` | Call `competition_join` | If success → success template; if `code=11016` → Scenario B template |
#### Scenario A — current wallet already registered
Template:
```
Your current wallet account [accountName] is already registered for [activityName]. No need to register again. Would you like me to walk you through the rules in detail, or start trading directly?
```
Field-mapping:
- `[accountName]` ← `accountName` of the currently selected account (read from `wallet_store` / `wallet status`, e.g. `Account 1`)
- `[activityName]` ← `activityName` from the prior `competition_user_status` / `competition_list` response
#### Scenario B — same login, different account already registered
Triggered when `competition_join` returns `code=11016 Participation limit reached`.
Template:
```
Registration failed. Your wallet account [registeredAccountName] is already registered. You cannot register again. Please switch to your registered account to trade.
```
Field-mapping:
- `[registeredAccountName]` ← name of the OTHER account in the same login that holds the registration. To find it, iterate every account from `wallet_store` other than the current one and call `competition_user_status` for the activity, picking the one whose `joinStatus=1`.
**Fallback template** — only when the iteration above finds no `joinStatus=1` account (rare race condition). Output verbatim instead of the main Scenario B template; do not author your own phrasing.
```
Registration failed. Another one of your wallet accounts is already registered for this competition. Please run `onchainos wallet status` to see which account is registered, then switch to it.
```
### Successful registration
On every successful `competition_join` call (`joined: true`), output the fixed template below. **All four lead-sentence elements must appear**: (1) which chains it runs on (`{supportedChains}`), (2) total prize pool, (3) dual-axis PnL% / realized PnL ranking, (4) Participation + Skill Quality Prizes. **The bracketed disclaimer must appear on its own line at the end.** `{supportedChains}` is **`participateChainIds` only** per Field-mapping. `{totalPrizePool}` is from `competition_detail` (call it first if not cached). Translate natural-language strings to the user's language; preserve structure and placeholders.
Template:
```
Registered successfully! This competition runs on {supportedChains}, with a total prize pool of {totalPrizePool}. The trading contest ranks players by both PnL% and realized PnL, with additional Participation and Skill Quality Prizes. Would you like me to walk you through the detailed rules, or help you initiate a trade on {supportedChains}?
[Disclaimer: Digital asset trading involves risk. Prices can be highly volatile. Please understand the risks fully and do your own research before trading.]
```
**Field-mapping rules**
- `{supportedChains}` ← computed from `data.participateChainIds` per [Shared field mapping](#shared-field-mapping). Lead sentence and closing question share the same string; do not list chains separately.
- `{totalPrizePool}` ← `data.totalPrizePool.display` from `competition detail` (render-ready, thousands-separated, e.g. `50,000 USDC`; multiple units joined by ` + `). If `totalPrizePool` is `null`, render "no prize-pool info". Do NOT sum `prizePoolDistribution[]` yourself.
### Other errors
**On error containing `region` / `not available in your region`:**
> Registration failed: service is not available in your region. Please switch to a supported region and try again.
**On any other error:**
> Operation failed. Please contact customer support.
## Step 4 — Trade (delegate to okx-agentic-wallet)
When user asks to trade per competition rules:
**Case A — User does NOT provide a CA (only token name/symbol):**
1. Resolve the CA via the `token_search` MCP tool (CLI: `onchainos token search`).
2. Confirm with user before proceeding:
> Just to confirm, the CA for token "{tokenSymbol}" is "{contractAddress}". Is that correct?
3. Wait for user to confirm. Only proceed after explicit "yes".
4. Then follow **Case B** below.
**Case B — User provides a CA directly:**
1. **Execute swap** via the `swap_swap` MCP tool (CLI: `onchainos swap swap`); see the `okx-agentic-wallet` skill for parameters.
2. Report: "Done — your trade has been submitted." + tx hash.
**Competition constraints per trade:**
- Single-trade min $1 (orders below $1 are not counted)
- Token pairs must match competition rules from `detail` response
## Query Registered Wallet
When user asks "show my registered address" or similar:
1. Call `competition_user_status` (MCP) — `accountId` is loaded from the active wallet session; no wallet args needed. CLI equivalent: `onchainos competition user-status` (omit `--activity-id` to query all activities).
2. Find entries where `joinStatus=1`
3. For each matched entry, present: competition name (`activityName`) + chain (`chainName`) + `joinedAddress` rendered verbatim. Use chain to determine which address was used (EVM or SOL).
If multiple entries match, list all of them.
Example layout (single):
> Your Account 1 is registered for **XXX Trading Competition**. Registered address: Solana address {joinedAddress}.
Example layout (multiple):
> Your Account 1 is registered for the following trading competitions:
> - **XXX Trading Competition** (Solana): {joinedAddress}
> - **YYY Trading Competition** (XLayer): {joinedAddress}
If no entry has `joinStatus=1`:
> You are not currently registered for any trading competition.
## Wallet Export Guard
When the user requests to export the Agentic Wallet:
1. Call `competition_user_status` (MCP) — uses `accountId` from active session. CLI equivalent: `onchainos competition user-status`.
2. If any `joinStatus=1`:
> Your wallet is registered for an Agentic Wallet trading competition. Exporting the wallet will forfeit your eligibility for this competition. Please confirm whether you want to proceed with the export.
3. Only proceed with export if the user explicitly confirms.
references/rank.md
# Rank — Leaderboard & My Own Rank
> Scope: rankings — full leaderboard + user's own rank (CASE 1/2/3 templates). Reward status checks: see `claim.md` → Check Participation Status. Global rules in `../SKILL.md`.
## Check leaderboard (full board)
When the user says "view leaderboard" without specifying which one, make ONE call:
1. Call `competition rank --activity-id <id> --all [--wallet <addr>] [--limit 20]`. The CLI enumerates every leaderboard from `detail` (filtering non-board columns) and returns `data.boards[]` — one entry per `sort_type`, each with the full single-board payload (`allRankInfos`, `myRankInfo`, `rankUpdateTime`). Do NOT fan out per `sort_type` yourself.
2. Render every entry in `data.boards[]` — one section per board. A board that failed carries `{sortType, error}`; render the others normally.
Only ask the user to pick one when there are clearly too many to fit (≥ 3 leaderboards on a single competition). With 1–2 leaderboards, always show all by default.
`tabConfigs[].rankFieldConfig[]` fields:
- `title` — display name (e.g. `PnL%`, `PnL`)
- `key` — internal sort field (e.g. `pnl`, `realizedProfit`)
- `sortValueMap.descend` — the numeric value to pass as `--sort-type`
**Per-leaderboard fetch:**
```bash
onchainos competition rank --activity-id <id> [--wallet <addr>] --sort-type <descend> --limit 20
```
**Display rules:** for each leaderboard render a separate section labeled by its `title`. Each section shows top N entries: rank, `nickName`, score (`userTotal` formatted by `format` field), estimated reward. Render `nickName` verbatim from the response.
Example layout (`nickName`, score, and reward values come from the backend):
> **PnL% leaderboard** — pool 200 DJT
> Rank 1, {nickName}, PnL% +0.17%, estimated reward 100 DJT
> Rank 2, {nickName}, PnL% +0.03%, estimated reward 20 DJT
>
> **PnL leaderboard** — pool 200 DJT
> Rank 1, {nickName}, PnL $0.1885, estimated reward 100 DJT
> Rank 2, {nickName}, PnL $0.0006, estimated reward 20 DJT
After the leaderboards, append a "Your rank" section using the **CASE 1 / 2 / 3 templates** from the next section, since you already have all the data.
## Check user's own rank (across ALL leaderboards)
A user can simultaneously appear on multiple leaderboards (e.g. PnL% AND PnL). When the user asks "what's my rank?", you MUST query every leaderboard the activity exposes, then render one of the three fixed templates below.
**Required flow:**
1. Call `competition rank --activity-id <id> --all [--wallet <addr>]` once — `data.boards[]` already covers every `sort_type`.
2. For each board in `data.boards[]`, read `myRankInfo` plus the threshold (lowest `userTotal` in `allRankInfos`).
3. Classify the result:
- **CASE 1** — user has `currentRank > 0` on every leaderboard
- **CASE 2** — user has `currentRank > 0` on at least one but not all
- **CASE 3** — user has no `currentRank > 0` on any leaderboard
4. Output the matching fixed template, **rendered in the user's language** (English canonical below; localize for Chinese / other-language users).
**Output exactly the matching template structure below — never paraphrase the data fields, never collapse the two-leaderboard sections into one. Localize the natural-language strings to the user's language; keep placeholders, numeric values, and units verbatim.**
### CASE 1 — ranked on both PnL and PnL%
Template:
```
Realized PnL ranking:
You are currently ranked #{pnlRank}, estimated reward {pnlReward} {rewardUnit}!
Realized PnL% ranking:
You are currently ranked #{roiRank}, estimated reward {roiReward} {rewardUnit}!
| Leaderboard | My rank | Estimated reward |
|-------------|---------|------------------|
| Realized PnL | #{pnlRank} | {pnlReward} {rewardUnit} |
| Realized PnL% | #{roiRank} | {roiReward} {rewardUnit} |
Your total estimated reward across both rankings: {totalReward} {rewardUnit} (sum of the two)
```
### CASE 2 — ranked on one leaderboard, off the other
There are two symmetric sub-cases. The structure is identical: the ranked leaderboard goes first ("ranked #N, estimated reward X"), then the unranked one ("not on the leaderboard, current value Y, threshold Z"). Each sub-case has its own pinned template — do NOT improvise the unranked-section unit (`%` for PnL%, currency `$` for PnL).
#### CASE 2-A — on PnL, off PnL% (currentRank for sort_type=7 > 0; sort_type=1 == 0)
Template:
```
Realized PnL ranking:
You are currently ranked #{pnlRank}, estimated reward {pnlReward} {rewardUnit}!
Realized PnL% ranking:
Not on the leaderboard yet. Your current realized PnL% is {currentRoi}%. You need at least {minRoi}% (the current leaderboard minimum) to qualify.
```
#### CASE 2-B — on PnL%, off PnL (currentRank for sort_type=1 > 0; sort_type=7 == 0)
Template:
```
Realized PnL% ranking:
You are currently ranked #{roiRank}, estimated reward {roiReward} {rewardUnit}!
Realized PnL ranking:
Not on the leaderboard yet. Your current realized PnL is ${currentPnl}. You need at least ${minPnl} (the current leaderboard minimum) to qualify.
```
**Unit rule**: PnL% uses `%` suffix (no currency symbol); PnL uses `$` prefix (or the appropriate currency unit). Do NOT mix them up — the user's threshold for PnL is a dollar amount, not a percentage.
### CASE 3 — off both leaderboards
Template:
```
Your address is not on any leaderboard. Your current realized PnL is ${currentPnl}, realized PnL% {currentRoi}%.
The current minimum to qualify: realized PnL ${minPnl}, realized PnL% {minRoi}%.
```
### Field-mapping rules
- `{pnlRank}` ← `myRankInfo.currentRank` of the PnL leaderboard (sort_type 7)
- `{pnlReward}` ← `myRankInfo.expectedRewards` of the PnL leaderboard
- `{roiRank}` ← `myRankInfo.currentRank` of the PnL% leaderboard (sort_type 1)
- `{roiReward}` ← `myRankInfo.expectedRewards` of the PnL% leaderboard
- `{rewardUnit}` ← `myRankInfo.rewardUnit` (e.g. `DJT`); per-leaderboard if they ever differ
- `{totalReward}` ← `pnlReward + roiReward` (numeric sum, same unit)
- `{currentRoi}` ← user's PnL% score from `myRankInfo.userTotal` of the PnL% board (or 0 if backend returned null)
- `{currentPnl}` ← user's PnL score from `myRankInfo.userTotal` of the PnL board
- `{minRoi}` ← lowest qualifying PnL% — last entry's `userTotal` in the PnL% board's `allRankInfos[]`
- `{minPnl}` ← lowest qualifying PnL — last entry's `userTotal` in the PnL board's `allRankInfos[]`
`format`: `1`=number, `2`=percentage, `3`=token amount with unit.
SKILL.md
---
name: okx-growth-competition
description: "List OKX Agentic Wallet exclusive trading competitions, register users for contests, track participation and leaderboard rankings, and claim won rewards. Use when users want to list available trading competitions or trading cups, view competition rules / prize pool / total prizes, register or sign up or enroll or join a contest, check the leaderboard (who is winning) or their own rank (am I in the prize zone, what is my place), ask did I win or query participation / claim status, claim won rewards or prizes from completed competitions, see which wallet account they registered with, or submit Telegram / WeChat / Email / Twitter contact for prize delivery to top-tier winners."
license: MIT
metadata:
author: okx
version: "4.4.10"
homepage: "https://web3.okx.com"
---
# OKX Growth Competition — Trading Competition
Agentic Wallet exclusive trading competitions. Full lifecycle split across focused references:
- **Participation** (discover / register / trade / registered wallet / export guard) — `references/participation.md`
- **Details** (rules / prize pool / four reward sections) — `references/details.md`
- **Rank** (leaderboard / my own rank with CASE 1/2/3 templates) — `references/rank.md`
- **Claim** (reward status check / atomic claim / contact collection) — `references/claim.md`
- **CLI reference** (commands, parameters, return schemas) — `references/cli-reference.md`
This SKILL.md holds the **global rules** (facts, identity invariants, routing, output rules, time formatting, status codes, error handling) that ALL references depend on. Always read this file first; then jump into the matching reference for the user's intent.
## Facts about every Agentic Wallet competition
Treat the following as **factual ground truth** when the user asks about how a competition works. The two chain-related fields play **distinct, non-overlapping roles** — never conflate them:
- `chainId` — single id. **The claim / reward chain ONLY** (rewards are paid on this chain; its contract address lives here). It is NOT a trading chain unless it also appears in `participateChainIds`.
- `participateChainIds` — array of ids returned by **both `list` and `detail`** endpoints. **The trading chain set.** Trades on any chain in this list count toward the same competition standing.
**Trading-chain set = `participateChainIds`. Claim chain = `chainId`.** These are two separate concepts; the display rules below NEVER union them.
1. **Chain id → display name** mapping. Currently supported competition chains: `1 → Ethereum`, `196 → X Layer`, `501 → Solana`.
2. Never tell a user "your chain doesn't count" without first checking `participateChainIds`.
3. `myRankInfo.userTotal = 0` means the user has not yet hit the qualifying threshold or the backend metric pipeline has not picked up their trades yet — it does **NOT** mean the user's chain is unsupported.
4. `competition_rank` takes a single optional `wallet`. Omit it for self-rank — the tool sends your `accountId` (covers every chain in `participateChainIds` in one call; no chain pick). Pass an explicit address ONLY when querying someone else's rank; the address chain family (EVM `0x...` else Solana) must match the activity's primary chain or the tool rejects the call (no silent wrong-chain queries).
## Identity resolution invariant
The query identity for `competition_rank` and `competition_user_status` is **mutually exclusive**: backend accepts EITHER `accountId` (self) OR `walletAddress` (cross-user) — never both. The answer to "which identity did you use?" is **deterministic from the call shape**.
| Call shape | Identity sent |
|---|---|
| `competition_user_status` (any) | `accountId` — covers every chain in `participateChainIds` in one call |
| `competition_rank` without `wallet` | `accountId` |
| `competition_rank` with `wallet=<addr>` | `walletAddress` — tool validates addr's chain family (EVM `0x...` else Solana) matches activity's `chainId`; mismatch → rejected |
| `competition_claim` (pre-check) | `accountId` |
For multi-activity `competition_user_status` (no `activity_name`), the same `accountId` is reused across all activities — backend joins by accountId.
## Mandatory reading order
**Before producing ANY user-facing message about a competition, you MUST first locate the matching section in the right reference file below and follow its fixed template structure.** Do NOT improvise the format. Do NOT shorten the templates. Do NOT drop sections or merge them. Templates are product-mandated copy (Participation / Skill Quality wording, disclaimer) and must not be paraphrased.
The template **structure is fixed**; the **language follows the user** — see the `## Output Language` rule below. When the user writes Chinese, translate the template strings to natural Chinese. When the user writes English, use English as written. Placeholders (including chain display names from `{supportedChains}`) stay as-is.
Quick router (user intent → reference file + section):
| User intent | Reference file | Section |
|---|---|---|
| "list competitions / show available competitions" | `references/participation.md` | Step 1 — Discover |
| "show details / show rules / show prize pool" | `references/details.md` | Step 2 — View Details |
| "register / join" | `references/participation.md` | Step 3 — Join |
| "trade for me" | `references/participation.md` | Step 4 — Trade (delegates to okx-agentic-wallet) |
| "leaderboard / full board / who is winning" | `references/rank.md` | Check leaderboard (full board) |
| "my rank / what's my ranking / am I in the prize zone" | `references/rank.md` | Check user's own rank (across ALL leaderboards) |
| "show registered wallet" | `references/participation.md` | Query Registered Wallet |
| "export wallet" | `references/participation.md` | Wallet Export Guard |
| "check my status / did I win" | `references/claim.md` | Check Participation Status |
| "claim reward / claim my prize" | `references/claim.md` | Step 6 — Claim Reward |
| Top-tier winner contact follow-up (`needContact: true` after claim) | `references/claim.md` | Contact collection (top-tier winners only) |
If the user's intent does not clearly map to one of the above, ask which they meant before responding — do **not** invent a freeform format.
## Pre-flight
> Read `../okx-agentic-wallet/_shared/preflight.md`. If missing, read `_shared/preflight.md`.
**Cross-skill routing on common errors**:
- `not logged in` → walk the user through the `okx-agentic-wallet` login flow (run `onchainos wallet login`), then retry the original action.
- Backend status codes (`--status` filter / `status` / `joinStatus` / `rewardStatus`) and error code messages (`11002` / `11003` / `11008` / `1860402` / `address limit reached` / `Sui-chain` / region-blocked / `not eligible`): see `references/cli-reference.md`.
## Command Index
All MCP tools mirror the CLI; MCP variants accept `activity_name` (server-resolves the id) and auto-resolve `accountId` / wallet addresses from the active session. Full flag tables and return shapes: `references/cli-reference.md`.
| # | Command | Auth | Description |
|---|---------|------|-------------|
| 1 | `onchainos competition list [--status 0\|1\|2] [--page-size N] [--page-num N]` | None | List competitions (default `status=0`, active only) |
| 2 | `onchainos competition detail --activity-id <id>` | None | Rules, prize pool, chain, timeline |
| 3 | `onchainos competition rank --activity-id <id> [--wallet <addr>] --sort-type <type> [--limit N]` | None | Leaderboard + user rank. See `references/rank.md` for self/cross-user semantics and `sort-type` discovery. |
| 4 | `onchainos competition user-status [--activity-id <id>]` | Wallet login | Participation & reward status (omit `--activity-id` for all activities) |
| 5 | `onchainos competition join --activity-id <id> --evm-wallet <addr> --sol-wallet <addr> --chain-index <chain_id>` | Wallet login | Register the active account for the competition |
| 6 | `onchainos competition claim --activity-id <id> --evm-wallet <addr> --sol-wallet <addr>` | Wallet login | Atomic claim — signs + broadcasts inside the call. See `references/claim.md`. |
| 7 | `onchainos competition submit-contact --activity-id <id> --contact-type <Telegram\|WeChat\|Email\|Twitter> --contact-value <text>` | Wallet login | Record contact for a top-tier winner; only after a claim with `needContact: true`. See `references/claim.md`. |
`--status` (request filter): `0`=active, `1`=ended, `2`=all
`activityStatus` (response field): **`3`=active, `4`=ended** — different from the request filter
## Output Rules
> **Internal-only IDs vs user-facing display.** Internal numeric IDs (`activityId`, `chainIndex`, `accountId`) are returned in tool responses on purpose — they are needed to chain calls between tools (e.g. after `competition_join`, you may need to call `competition_detail` with the activity id to fill the success template). **Keep them in the data layer; never render them in user-visible messages.**
**Never include any internal id in a message produced for the user — under ANY circumstance, in ANY format.** Identify activities to the user EXCLUSIVELY by `activityName` (or `shortName` if name is unavailable).
**Forbidden user-visible patterns** (do NOT produce output like this):
- `Agentic Trading Contest (#107)`
- `#106 (agenticwallettest1)`
- Any column, row, or inline reference exposing an activity ID (e.g. `competition 107`, an `ID` column, a labeled `Activity ID` row) — same rule, regardless of label, shape, or language.
**Correct user-visible pattern**:
- `Agentic Trading Contest`
- When disambiguating two activities with the same name, append `chainName` (e.g. `Agentic Trading Contest (Solana)`), never the ID.
**Behind the scenes (allowed and expected)**:
- Reading `activityId` from a `competition_user_status` / `competition_join` response and passing it to `competition_detail` to fetch the data needed by a fixed template.
- Any tool-to-tool chaining via numeric ids — as long as the final user-facing message omits them.
When the user asks to act on a specific activity (e.g. "claim Agentic Trading Contest"), the MCP tools `competition_claim` / `competition_join` accept `activity_name` and resolve the id server-side, so you can also use names directly without doing your own lookup.
## Output Language
**Render every fixed template in the user's conversation language.** The template structure (sections, ordering, numbered items, table column count, placeholder positions, the `{supportedChains}` placeholder, and the `[Disclaimer: ...]` block) is fixed and must NOT change. Only the natural-language text inside is translated to the user's language naturally.
**Placeholders are never translated.** `{supportedChains}`, `{chainName}`, `{rewardUnit}`, `{txHash}`, `{accountName}`, etc. are filled with API values verbatim — do not localize them. Chain display names (e.g. `Solana`, `X Layer`, `Base`) come from the canonical id → name mapping and stay as-is in every language.
## Pre-Delivery Checklist
Final check before sending — covers the reference-file MUSTs that are easy to skip after a long response. (Rules already covered in earlier sections — internal IDs, `participateChainIds`, `*Formatted`, language/template fidelity — are not repeated here; verify them by following the rules at their home sections.)
- [ ] On a successful registration response → the `[Disclaimer: Digital asset trading involves risk. ...]` line is present on its own line at the end. (→ `participation.md` → Successful registration)
- [ ] On a claim runtime failure (signing / broadcast / network) → the 3-bullet failure-suggestion block is appended. On a pre-check rejection (rewardStatus 0/2/3/4, code 11002, code 11008) → the suggestion block is **OMITTED**. (→ `claim.md` → Fixed failure-suggestion block)
- [ ] Before invoking `competition_claim` → the pre-claim preview line (`You are about to claim {rewardAmount} {rewardUnit} on {chainName}. Reply "confirm" to proceed.`) was rendered and the user replied with an explicit confirmation. (→ `claim.md` → Pre-claim preview)