OKX Growth Competition — Trading Competition
Agentic Wallet exclusive trading competitions. Full lifecycle: list → detail → join → trade → rank → claim.
CLI reference:
references/cli-reference.md
Facts about every Agentic Wallet competition (always true unless backend confirms otherwise)
<MUST>
Treat the following as **factual ground truth** when the user asks about how a competition works. Do NOT infer otherwise from a single-chain `chainName` in `competition_detail` — that field currently only carries the primary EVM chain; the second chain is a hardcoded product invariant that backend has not yet split into a separate field.
</MUST>
- Every active competition runs simultaneously on Solana AND the chain returned by the backend (e.g. → activity runs on both X Layer and Solana). Solana is the hardcoded second chain on every activity.
- Trades on either chain count toward the same competition standing. A user trading on Solana for an X-Layer-titled competition is participating, not bystanding. Likewise EVM trades count for a Solana-titled competition. (When backend later returns a multi-chain field listing all supported chains, drop this hardcoding.)
- 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. Never tell the user "your chain doesn't count" based on a observation.
- The competition argument used in is the chain-appropriate address: SOL for Solana-primary activities, EVM for EVM-primary activities. The address you pass is just a query lens — trades on the OTHER chain still count toward the same ranking.
- The shape of point 1–4 may change in the future when backend exposes the full supported-chain list. Until then, NEVER answer "Does Solana count for this competition?" with anything other than YES.
When the user asks anything like "Does my Solana trade count for this competition?" or "Which chain should I trade on?", answer based on this section, not from
alone.
⚠️ Mandatory reading order
<MUST>
**Before producing ANY user-facing message about a competition (list / detail / join / claim / rank / status / wallet-export-guard), you MUST first locate the matching `Step N` section below and follow its fixed template structure.** Do NOT improvise the format. Do NOT shorten the templates. Do NOT drop sections or merge them.
The template
structure is fixed; the
language follows the user — see the
rule above. When the user writes Chinese, translate the template strings to natural Chinese. When the user writes English, use English as written. Placeholders and
literal stay as-is.
Quick router (user intent → template section):
- "list competitions / show available competitions" → Step 1 (table, optionally split by Active / Ended)
- "show details / show rules / show prize pool" → Step 2 (Basic info block + 4 reward sections, with hardcoded and required participation / Skill copy)
- "register / join" → Step 3 (registration success fixed template + disclaimer)
- "trade for me" → Step 4 (delegate to okx-dex-swap)
- "leaderboard / ranking" → Step 5
- "claim reward" → Step 6 (use MCP, atomic)
- "show registered wallet" → Additional Flows / Query Registered Wallet
- "export wallet" → Additional Flows / Wallet Export Guard
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.
</MUST>
Pre-flight
Read
../okx-agentic-wallet/_shared/preflight.md
. If missing, read
.
Command Index
| # | Command | Auth | Description |
|---|
| 1 | onchainos competition list [--status 0|1|2] [--page-size N] [--page-num N]
| None | List Agentic Wallet exclusive competitions (default status=0, active only) |
| 2 | onchainos competition detail --activity-id <id>
| None | Get rules, prize pool, chain, timeline |
| 3 | onchainos competition rank --activity-id <id> --wallet <addr> --sort-type <type> [--limit N]
| None | Leaderboard + user rank. MCP tool makes optional — when omitted it auto-picks the EVM or SOL address of the active account based on the activity's chain. Discover available values from → tabConfigs[].rankFieldConfig[].sortValueMap.descend
(do not hardcode). |
| 4 | onchainos competition user-status [--activity-id <id>] --evm-wallet <evm_addr> --sol-wallet <sol_addr>
| None | Check participation & reward status; uses chain-appropriate address (omit to check all activities). MCP tool makes both wallet args optional — auto-resolves from active account. |
| 5 | onchainos competition join --activity-id <id> --evm-wallet <addr> --sol-wallet <addr> --chain-index <chain_id>
| Wallet login | Register for the competition. MCP tool makes both wallet args optional. |
| 6 | onchainos competition claim --activity-id <id> --evm-wallet <addr> --sol-wallet <addr>
| Wallet login | CLI returns unsigned calldata. MCP tool is atomic — wallets are optional, signing + broadcast happens inside the tool, returns txHash array. |
(request filter):
=active,
=ended,
=all
(response field):
=active, =ended — these are DIFFERENT values from the request filter
: dynamic — read from
→
tabConfigs[].rankFieldConfig[].sortValueMap.descend
. Currently observed values:
=PnL% (realized ROI),
=PnL (realized profit). Future activities may add more — always trust
over hardcoding.
Output Rules
Internal-only IDs vs user-facing display. Internal numeric IDs (
,
,
) are returned in tool responses on purpose — they are needed to chain calls between tools (e.g. after
, you may need to call
with the activity id to fill the success template).
Keep them in the data layer; never render them in user-visible messages.
<NEVER>
**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).
</NEVER>
Forbidden user-visible patterns (do NOT produce output like this):
- ❌
Agentic Trading Contest (#107)
- ❌
#106 (agenticwallettest1)
- ❌ A column titled "ID" or "#"
- ❌ Any reference like "competition 107" or "id 107"
Correct user-visible pattern:
- ✅
- ✅ When disambiguating two activities with the same name, append (e.g.
Agentic Trading Contest (Solana)
), never the ID.
Behind the scenes (allowed and expected):
- ✅ Reading from a / response and passing it to 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
/
accept
and resolve the id server-side, so you can also use names directly without doing your own lookup.
Output Language
<MUST>
**Render every fixed template in the user's conversation language.** The template structure (sections, ordering, numbered items, table column count, placeholder positions, hardcoded literal phrases like `Solana, {chainName}` 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. ,
,
,
, etc. are filled with API values verbatim — do not localize them.
(the hardcoded second-chain name) also stays as-is in every language.
</MUST>
Execution Flow
Step 1 — Discover Competitions
Choosing the status filter
Decide which
to pass based on the user's intent:
| User intent | Pass | Returned values |
|---|
| Generic listing ("show competitions") | (all) | mix of 3 (active) and 4 (ended) |
| Active only ("which can I join now") | (active filter) | only 3 |
| Ended only ("winners list") | (ended filter) | only 4 |
When in doubt, prefer
so the user can see the full picture and pick.
<MUST>
**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 (
) and ended (
) entries,
split into two separate tables under bold subheadings ( / , translated to the user's language), in that order. When only one status is present, render a single table without a subheading.
</MUST>
Fixed table template (English canonical; translate cells when user is non-English)
**Active**
| Name | Chain | Time | Total Prize Pool | Details |
|------|-------|------|------------------|---------|
| {name} | Solana, {chainName} | {startTime} ~ {endTime} | {rewards} | [View](https://web3.okx.com/boost/trading-competition/{shortName}) |
| ... | ... | ... | ... | ... |
**Ended**
| Name | Chain | Time | Total Prize Pool | Details |
|------|-------|------|------------------|---------|
| {name} | Solana, {chainName} | {startTime} ~ {endTime} | {rewards} | [View](https://web3.okx.com/boost/trading-competition/{shortName}) |
| ... | ... | ... | ... | ... |
For non-English users, translate the column headers, section headers, and link text naturally. The structure (column count, ordering,
literal) does not change.
Field-mapping rules
- Group rows by
availableCompetitions[].status
: → Active table, → Ended table.
- Name column ←
- Chain column ← same hardcoding as Step 2: always include Solana plus the backend .
- If is Solana → write just
- Otherwise → write (e.g. )
- Temporary until backend returns a full supported-chain list.
- Time column ← ~ (human-readable, e.g. )
- Total Prize Pool column ← field (already a formatted string like )
- 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.
- filter returned 0 entries →
No active trading competitions at the moment.
- filter returned 0 entries →
No ended trading competitions yet.
CLI equivalent
bash
onchainos competition list --status 2 # all
onchainos competition list --status 0 # active only
onchainos competition list --status 1 # ended only
Step 2 — View Details (if requested)
bash
onchainos competition detail --activity-id <id>
<MUST>
**Display competition / reward info using the fixed English template below.** The structure (sections, ordering, numbered list, placeholder positions, the hardcoded `Solana, {chainName}` chain prefix) 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.
</MUST>
Fixed display template
Basic info:
Chain: Solana, {chainName}
Time: {startTime} ~ {endTime}
Total prize pool: {totalPrizePool}
Reward categories:
1. Realized ROI Pool ({roiPoolAmount})
Ranked by realized ROI of the wallet account during the competition, high to low
{roiRankTable}
2. Realized PnL Pool ({pnlPoolAmount})
Ranked by realized PnL of the wallet account during the competition, high to low
{pnlRankTable}
3. Participation Reward ({participationPoolAmount})
During the competition, registered users with cumulative trading volume of $100 or more via Agentic Wallet, and wallet total assets maintained at $100 or more throughout, will share the {participationPoolAmount} participation reward pool. We will perform unscheduled asset snapshots during the competition to verify eligibility.
4. Skill Quality Award ({skillPoolAmount})
The Skill Quality Award is an independent judging category. During the competition, participants may submit their own Agent Skills via the activity page, including but not limited to on-chain autonomous yield strategies, trade analysis, and trading signal monitoring.
All submitted Agent Skills will be evaluated through a dual-rating mechanism of AI initial screening and human review. The top {skillTopN} Skill creators by score will each receive {skillPerCreatorReward}.
Field-mapping rules
-
Chain line ←
Solana first, then the backend . Concretely:
- If already is Solana → write just
- Otherwise → write (e.g. )
- This is a temporary hardcoding because the backend currently returns only the primary chain. A future backend release will return the full supported-chain list as a separate field; remove this hardcoding then.
-
/
← human-readable timestamps.
-
← sum of all
prizePoolDistribution[].totalReward
plus
(e.g.
).
-
← totalReward of the realized-ROI tab.
-
← totalReward of the realized-PnL tab.
-
{participationPoolAmount}
← totalReward of the participation prize tab.
-
← totalReward of the Skill quality prize tab.
-
← upper bound of the Skill tab's
(e.g.
→
).
-
← that rule entry's
+
(e.g.
).
-
/
← markdown table built from the corresponding tab's
. 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 () → Rank cell , Reward cell (no prefix)
- Range () → Rank cell , Reward cell
<reward> <rewardUnit> each
- Always end with a totals row whose Reward cell is the tab's + .
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 ROI Pool
- Title MUST be exactly (or its faithful translation in the user's language). Do NOT substitute with / / similar.
- Description MUST mention: ranking by realized ROI, high to low, during the competition period.
- Rank table MUST have headers and end with a row.
Section 2 — Realized PnL Pool
- Title MUST be exactly . Do NOT substitute with .
- Description MUST mention: ranking by realized PnL, high to low.
- Rank table MUST follow the same format as Section 1.
Section 3 — Participation Reward (PRODUCT-MANDATED COPY)
- Title MUST be exactly .
- The description body MUST include all of these specific terms:
- cumulative trading volume threshold of
- wallet total assets maintained at throughout
- sharing the participation pool
- asset snapshots to verify eligibility
Section 4 — Skill Quality Award (PRODUCT-MANDATED COPY)
- Title MUST be exactly .
- The description body MUST include all of these specific terms:
- submission of Agent Skill via the activity page
- examples of skill content (on-chain yield strategies, trade analysis, signal monitoring)
- dual-rating mechanism of AI initial screening and human review
top {skillTopN} Skill creators ... each receive {skillPerCreatorReward}
<NEVER>
- ❌ Do NOT drop the trailing `, Solana` from the chain line, even if the backend's `chainName` is already an EVM chain like XLayer / Arbitrum.
- ❌ Do NOT reorder or merge the four reward sections — they must appear in the order 1 → 2 → 3 → 4.
- ❌ Do NOT add ID columns or expose any internal numeric id (`activityId`, etc.) anywhere in the output.
- ❌ Do NOT paraphrase, abbreviate, or substitute synonyms in Sections 3 and 4. These are product-mandated copy.
- ❌ Do NOT invent rank-distribution rules from the pool amount. The actual rules come from `prizePoolDistribution[].rules[]` — read them; do not divide.
- ❌ 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.
</NEVER>
After printing the template, ask:
Would you like me to register you for this competition?
Step 3 — Join (requires wallet login)
MCP: call
with
and
only —
and
are auto-resolved from the active account.
CLI: pass addresses explicitly:
bash
onchainos competition join --activity-id <id> --evm-wallet <evm_addr> --sol-wallet <sol_addr> --chain-index <chain_id>
If the user is not logged in, the tool returns
not logged in — please run: onchainos wallet login
. Tell the user verbatim:
Please run
onchainos wallet login <your_email>
in your terminal to log in (it cannot be done from inside this conversation), then ask me to register again.
Required pre-flight: distinguish duplicate-registration scenarios
<MUST>
**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 that have different user-facing messages.
</MUST>
| Scenario | (current account) | Action | Template |
|---|
| A — current account already joined | | Do NOT call | Scenario A template (below) |
| B — current account NOT joined | | Call | If success → success template; if → 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:
- ← of the currently selected account (read from / , e.g. )
- ← from the prior / response
Scenario B — same login, different account already registered
Triggered when
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:
- ← name of the OTHER account in the same login that holds the registration. To find it, iterate every account from other than the current one and call for the activity, picking the one whose . If no account is found (rare race), fall back to a generic phrase like
another of your wallet accounts is already registered
and ask the user to check themselves.
Successful registration
<MUST>
**On every successful `competition_join` call (the tool returns `joined: true`), output the fixed template below.** Structure (the lead phrase + the dual-chain sentence + the closing question + the bracketed disclaimer on its own line) is fixed. Solana literal is hardcoded; `{chainName}` and `{totalPrizePool}` are filled from `competition_detail` (call it before formatting if you don't have it cached). Translate the natural-language strings to the user's language while preserving structure, placeholders, and the `Solana` literal.
</MUST>
Template:
Registered successfully! This competition runs simultaneously on {chainName} and Solana, with a total prize pool of {totalPrizePool}. The trading contest ranks players by both PnL% and realized PnL, with additional Participation and Skill Quality Awards. Would you like me to walk you through the detailed rules, or help you initiate a trade on {chainName} or Solana?
[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
- ← backend from (e.g. ). Special case: if backend is already Solana, the activity is single-chain — collapse the sentence to
This competition runs on Solana
and the trailing question to Would you like me to walk you through the detailed rules, or help you initiate a trade on Solana?
. The disclaimer line still appears at the end either way.
- ← total reward pool (sum of all
prizePoolDistribution[].totalReward
+ , e.g. ).
<NEVER>
- ❌ Do NOT drop the hardcoded `Solana` mention even when the backend's primary chain is already an EVM chain — the activity actually runs on both chains.
- ❌ Do NOT drop or merge the four key phrases of the lead sentence: (1) which two chains it runs on, (2) the total prize pool, (3) the dual-axis PnL%/realized PnL ranking, (4) the existence of Participation and Skill Quality Awards. These are required content; the wording can be localized but the four pieces must all appear.
- ❌ Do NOT drop the bracketed disclaimer line — it must appear on its own line at the end of the message, in the user's language.
</NEVER>
Other errors
On error containing / 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-dex-swap)
When user asks to trade per competition rules:
Case A — User does NOT provide a CA (only token name/symbol):
- Resolve the CA via the MCP tool (CLI: ).
- Confirm with user before proceeding:
Just to confirm, the CA for token "{tokenSymbol}" is "{contractAddress}". Is that correct?
- Wait for user to confirm. Only proceed after explicit "yes".
- Then follow Case B below.
Case B — User provides a CA directly:
- Execute swap via the MCP tool (CLI: ); see the skill for parameters.
- Report: "Done — your trade has been submitted." + tx hash.
Note: do NOT pre-empt the swap with an extra "token prices are volatile, do you accept the risk?" prompt. The user already requested the trade — additional friction is unwanted. Per-token risk metadata (e.g. honeypot / extreme volatility flags) belongs to
and only fires when actually flagged.
Competition constraints per trade:
- Single-trade min $1 (orders below $1 are not counted)
- Token pairs must match competition rules from response
Step 5 — Check Status & Rank
Check participation status
bash
onchainos competition user-status --evm-wallet <evm_addr> --sol-wallet <sol_addr> # all activities
onchainos competition user-status --activity-id <id> --evm-wallet <evm_addr> --sol-wallet <sol_addr> # single
Display: join status, join time, reward status, reward amount.
- If (won, not claimed): proactively ask "You have won a reward. Would you like me to claim it for you?"
- If (expired): "Your reward has expired and can no longer be claimed."
Check leaderboard (full board)
<MUST>
When the user says "view leaderboard" without specifying which one, you MUST:
- Call for the activity and enumerate
tabConfigs[].rankFieldConfig[].sortValueMap.descend
— this is the full set of leaderboards the activity exposes.
- Call ONCE PER (one HTTP call per leaderboard) so you have data for every leaderboard.
- Render ALL of them in the response — one section per leaderboard. Do NOT silently default to a single leaderboard (e.g. only ) when the activity has more than one.
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.
</MUST>
tabConfigs[].rankFieldConfig[]
fields:
- — display name (e.g. , )
- — internal sort field (e.g. , )
- — the numeric value to pass as
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
. Each section shows top N entries: rank, nickname (masked), score (
formatted by
field), estimated reward.
Example response (activity with two leaderboards):
PnL% leaderboard — pool 200 DJT
Rank 1, Agentic....sMWP, PnL% +0.17%, estimated reward 100 DJT
Rank 2, Agentic....gweD, PnL% +0.03%, estimated reward 20 DJT
PnL leaderboard — pool 200 DJT
Rank 1, Agentic....sMWP, PnL $0.1885, estimated reward 100 DJT
Rank 2, Agentic....gweD, 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:
- Call → enumerate
tabConfigs[].rankFieldConfig[].sortValueMap.descend
to get the full set of values for this activity.
- For EACH , call
competition_rank --sort-type <descend>
and capture plus the leaderboard's threshold (lowest in ).
- Classify the result:
- CASE 1 — user has on every leaderboard
- CASE 2 — user has on at least one but not all
- CASE 3 — user has no on any leaderboard
- Output the matching fixed template, rendered in the user's language (English canonical below; localize for Chinese / other-language users).
<MUST>
**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.**
</MUST>
CASE 1 — ranked on both PnL and PnL%
Template:
Realized PnL ranking:
You are currently ranked #{pnlRank}, estimated reward {pnlReward} {rewardUnit}!
Realized ROI ranking:
You are currently ranked #{roiRank}, estimated reward {roiReward} {rewardUnit}!
| Leaderboard | My rank | Estimated reward |
|-------------|---------|------------------|
| Realized PnL | #{pnlRank} | {pnlReward} {rewardUnit} |
| Realized ROI | #{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 ROI ranking:
Not on the leaderboard yet. Your current realized ROI 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 ROI 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.
Section ordering rule: the leaderboard the user IS ranked on ALWAYS goes first. Don't put the "Not on the leaderboard" section before the ranked one.
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 ROI {currentRoi}%.
The current minimum to qualify: realized PnL ${minPnl}, realized ROI {minRoi}%.
Field-mapping rules
- ← of the PnL leaderboard (sort_type 7)
- ←
myRankInfo.expectedRewards
of the PnL leaderboard
- ← of the PnL% leaderboard (sort_type 1)
- ←
myRankInfo.expectedRewards
of the PnL% leaderboard
- ← (e.g. ); per-leaderboard if they ever differ
- ← (numeric sum, same unit)
- ← user's PnL% score from of the PnL% board (or 0 if backend returned null)
- ← user's PnL score from of the PnL board
- ← lowest qualifying PnL% — last entry's in the PnL% board's
- ← lowest qualifying PnL — last entry's in the PnL board's
If the activity exposes leaderboards beyond PnL/PnL% (future expansion via
), extend the same template pattern: one section per leaderboard, summary table aggregates all, total reward sums all
.
:
=number,
=percentage,
=token amount with unit.
Step 6 — Claim Reward
| Action |
|---|
| 0 | Not won — inform user, no claim needed |
| 1 | Won — proceed to claim |
| 2 | Already claimed |
| 3 | Expired — "Your reward has expired and can no longer be claimed" |
Atomic claim (the only correct path)
Both the MCP tool
and the CLI
onchainos competition claim
now do the
same atomic flow: pre-check
, 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.
MCP (preferred when running inside Claude Code / any AI environment):
competition_claim(activity_name="...") → { rewardAmount, rewardUnit, succeeded[], failed[] }
CLI (terminal use, or AI shelling out via Bash):
bash
onchainos competition claim --activity-id <id> --evm-wallet <evm_addr> --sol-wallet <sol_addr>
# → returns the same { rewardAmount, rewardUnit, succeeded[], failed[] } shape
Result shape (both paths):
json
{
"rewardAmount": "460",
"rewardUnit": "PYBOBO",
"totalEntries": 1,
"succeeded": [{"contractAddress": "...", "chain": "501", "txHash": "...", "orderId": "..."}],
"failed": []
}
How to report to the user:
- All succeeded (): "Claimed {rewardAmount} {rewardUnit}, tx hash: {txHash}"
- Partial success (some ): list each succeeded txHash, then list the failed entries with their , 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.
The flow blocks before signing if
is 0 (not eligible), 2 (already claimed), or 3 (expired). 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
<MUST>
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.
</MUST>
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.
<NEVER>
- ❌ Do NOT show this block on pre-check rejections (rewardStatus=0/2/3) — the issue is not Gas / not transient.
- ❌ Do NOT show this block on `code=11002` (not won) or `code=11008` (claim expired/already claimed) — same reason.
</NEVER>
<NEVER>
- ❌ Do NOT chain `gateway_broadcast` after a claim call — the on-chain submission already happened inside the tool.
- ❌ 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.
- ❌ 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.
</NEVER>
On claim error (code 11002 ): "You did not win a reward and cannot claim."
On any other error: "Operation failed. Please contact customer support."
Additional Flows
Query Registered Wallet
When user asks "show my registered address" or similar:
- Call (MCP) — addresses auto-resolve from the active account; no needed. CLI equivalent:
onchainos competition user-status --evm-wallet <evm_addr> --sol-wallet <sol_addr>
(omit to query all activities).
- Find entries where
- For each matched entry, present: competition name () + chain () + masked address (first4...last4). Use chain to determine which address was used (EVM or SOL).
If multiple entries match, list all of them.
Example (single):
Your Account 1 is registered for XXX Trading Competition. Registered address: Solana address DeEV...Fbx.
Example (multiple):
Your Account 1 is registered for the following trading competitions:
- XXX Trading Competition (Solana): DeEV...Fbx
- YYY Trading Competition (XLayer): 0x1234...abcd
You are not currently registered for any trading competition.
Wallet Export Guard
When the user requests to export the Agentic Wallet:
- Call (MCP) — addresses auto-resolved. CLI equivalent:
onchainos competition user-status --evm-wallet <evm_addr> --sol-wallet <sol_addr>
.
- If any :
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.
- Only proceed with export if the user explicitly confirms.
Status Codes
filter parameter (input only)
| Value | Meaning |
|---|
| 0 | Active competitions (default) |
| 1 | Ended competitions |
| 2 | All competitions |
Response field values
| Field | Value | Meaning |
|---|
| status | 3 | Competition active |
| status | 4 | Competition ended |
| joinStatus | 0 | Not joined |
| joinStatus | 1 | Joined |
| rewardStatus | 0 | Not won |
| rewardStatus | 1 | Won, not claimed |
| rewardStatus | 2 | Claimed |
| rewardStatus | 3 | Reward expired |
Error Handling
| Error | Response |
|---|
| Login is interactive (email + OTP) and cannot run inside this conversation. Tell the user verbatim: Please run "onchainos wallet login <your_email>" in your terminal, then ask me again.
|
| Registration failed: this wallet account is already registered and cannot register again |
| code 11002 | You did not win a reward and cannot claim |
code 11003 activity not found / status mismatch
| The competition does not exist or its status no longer permits this action |
| code 11008 | The reward has already been claimed or the claim window has expired |
code 1860402 failed to assemble transaction
| Backend failed to build the on-chain transaction. Ask the user to retry; if it persists, contact customer support |
Sui-chain reward claims are not yet supported
| Sui rewards must be claimed from the Sui-compatible wallet UI (this client only signs EVM and Solana) |
/ not available in your region
| Registration failed: service is not available in your region. Please switch to a supported region and try again. |
| Any other error | Operation failed. Please contact customer support. |