MetaComp-Swap/SKILL.md
Currency exchange (swap) via MetaComp Swap. Trigger when the user mentions: currency exchange, swap, convert currency, or specific requests like "I want to exchange 10000 SGD", "换汇", "换钱", "我想换钱".
npx skillsauth add metacomp-ai/metacomp-skill MetaComp-SwapInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Before writing a single word, before calling any MCP tool:
Step A — Read all sub-skill files:
subSkills/account-display.mdsubSkills/swap-confirm.mdsubSkills/wealth-recommendation.mdStep B — Output this line verbatim as the FIRST visible output:
Sub-files have been read: account-display ✓ / swap-confirm ✓ / wealth-recommendation ✓
Do not proceed until this line appears in the response.
Call both tools in parallel:
get_account_summary()get_available_currency_pairs()Server not configured. Show Setup Guide (see bottom of this file), STOP.
success: false with authPageUrlExample response:
{
"success": false,
"authPageUrl": "https://demo.metacomp.ai/auth/metacomp/login",
"msg": "Invalid token"
}
Output:
Your session has expired. Please log in to continue:
Log in to MetaComp
Once logged in, come back here and let me know.
⛔ STOP. Do not call any tool. Wait for the user to confirm they have logged in.
When the user confirms → go back to STEP 1 (re-call both tools).
🔁 Resume note: a continuation reply like "go on" / "继续" / "I've logged in" is a flow-control signal — it does NOT replace the original triggering message. When later evaluating WEALTH_RECOMMENDATION_TRIGGER (e.g. at STEP 3C when the user's reply has no clear swap intent), anchor condition 5 to the user's original intent, not the continuation.
success: falseget_account_summary and get_available_currency_pairs do not return a success field on success — they return the data directly (e.g. { "fiat": {...}, "crypto": {...} } and { "pairs": [...] }). Only error responses contain success: false.
Check: if neither response contains success: false → proceed to STEP 2 with the data received.
Call get_account_detail for all productCodes in parallel (for display purposes), plus exchange quotes:
get_account_detail({ "productCode": "fiat" })get_account_detail({ "productCode": "crypto" })get_account_detail({ "productCode": "investment_fiat" })get_account_detail({ "productCode": "quarantine_portfolio" })get_account_detail({ "productCode": "investment_product" })get_exchange_quote({ "fromCurrency": X, "toCurrency": Y }) for each available pairIMPORTANT — Swap-eligible accounts: Only fiat and crypto accounts can be used for currency exchange. The other accounts (investment_fiat, quarantine_portfolio, investment_product) are displayed for informational purposes only and must NOT be used as source accounts for swap operations.
→ Any call returns success: false with authPageUrl → same as STEP 1 Case B (show login link, STOP)
→ All succeed → proceed to STEP 3
get_account_detail:data.holderCode — account identifier (e.g. "A0102634"), use in confirmation pagedata.instrumentInfoMap — per-currency breakdown; do not hardcode which currencies belong to which productCode — each user's currency list is differentinstrumentInfoMap from all productCode responses. A currency may appear in multiple productCodes (e.g. USD exists in fiat, investment_fiat, and investment_product). For each (currency, productCode) pair, record availableAmount, pendingAmount, and pendingCreditAmountfiat and crypto — when validating balance for swap in STEP 4C, only check these two productCodes. Ignore balances in investment_fiat, quarantine_portfolio, and investment_productavailableAmount > 0 OR pendingAmount > 0 OR pendingCreditAmount > 0Present the information following subSkills/account-display.md, then ask the user.
Display the account summary from STEP 1.
Display available pairs from STEP 1.
Rules:
get_exchange_quote alongside each pairDisplay note below the pair/rate table (respond in the user's language):
ℹ Rates update continuously. You'll have 60 seconds to confirm once you select a pair.ℹ 汇率实时波动,选定币对后将有 60 秒确认窗口。How would you like to exchange? For example: "Exchange USD for 10,000 SGD"
⛔ STOP. Wait for the user's answer. Do not assume, guess, or pre-fill any values.
When the user replies to STEP 3C and the reply does NOT contain a valid swap intent (no currencies, no amounts, no exchange direction — instead expressing balance curiosity, flow abandonment, or open-ended exploration), evaluate the WEALTH_RECOMMENDATION_TRIGGER (defined in subSkills/wealth-recommendation.md). If TRUE, follow the recommendation flow in that file, then re-ask the STEP 3C question.
If the user's reply IS a valid swap intent → proceed to STEP 4 with no recommendation.
Extract these fields from the user's message:
| Field | Description | Example |
|---|---|---|
| source_currency | Currency to sell | USD |
| target_currency | Currency to buy | SGD |
| amount | Numeric amount | 10,000 |
| direction | Is amount the source or target amount? | target |
Rules:
target_currency=SGD, amount=10000, direction=targetsource_currency=USD, amount=10000, direction=sourceCheck if {source_currency}/{target_currency} exists in the pairs list from STEP 1.
→ Not available:
This pair ({source_currency}/{target_currency}) is not currently supported.
Available pairs: {list available pairs}
Please choose from the above.
⛔ STOP. Wait for user to choose a different pair. Then re-validate from 4B.
→ Available: proceed to 4C.
Purpose: determine the exact totalValue (in source currency) that STEP 5 will lock, and pick the correct source_productCode.
If direction=target (user said "I want to receive N of target"):
get_exchange_quote({ "fromCurrency": source_currency, "toCurrency": target_currency }).totalValue = target_amount / rate (keep enough precision for the source currency).get_exchange_quote returns a failure indicating an unsupported pair, tell the user and return to STEP 3C. Do not proceed.If direction=source: totalValue is the amount the user stated.
Resolve source_productCode:
source_currency and are swap-eligible (fiat / crypto only).⚠ Do NOT pre-check balance here. Balance adequacy is verified at STEP 5 when get_otc_quote locks the quote. A pre-check would use an approximate (unlocked) rate and can mislead the user if the rate moves between now and lock.
This step replaces the former one-shot execute_currency_exchange. It is three sub-steps: 5a lock, 5b confirm, 5c execute. Do them in order. Never skip 5b.
Call:
{
"tool": "get_otc_quote",
"args": {
"fromCurrency": "{source_currency}",
"toCurrency": "{target_currency}",
"totalValue": "{totalValue resolved in STEP 4C}"
}
}
Handle the response:
Failure — { "success": false, "message": "..." }:
Show the message verbatim to the user (translated to their language if needed). Typical causes are insufficient balance or unsupported pair. Return to STEP 3C. Do NOT auto-retry.
Success — receive { quoteId, exchangeRate, finalPrice, symbolCode, action, baseCurrency, quoteCurrency, fromCurrency, toCurrency, totalValue }. Keep every field for STEP 5c.
Render the confirmation page following subSkills/swap-confirm.md.
ABSOLUTE RULE — Rate display: The confirmation page MUST show exchangeRate from get_otc_quote (a string). Never show finalPrice to the user — it is an internal trading-pair price used only by confirm_otc_trade. The 5a-locked exchangeRate is authoritative — do NOT reuse the browse-time rate shown on STEP 3B; rates drift between 3B and 5a and only the locked value is correct for the trade the user is about to confirm.
ABSOLUTE RULE — 60-second rate window: The confirmation page MUST include the ⚠ 60-second rate-validity notice from subSkills/swap-confirm.md in the user's language. Non-negotiable.
ABSOLUTE RULE — Explicit confirmation: Wait for the user to explicitly confirm. Never auto-confirm. Never proceed without an explicit yes.
⛔ STOP. Wait for the user.
If the user cancels or asks to change amounts/currencies → return to STEP 3C. The locked quote is abandoned (it expires on its own).
After explicit user confirmation, call:
{
"tool": "confirm_otc_trade",
"args": {
"quoteId": "{quoteId from 5a}",
"symbolCode": "{symbolCode from 5a}",
"totalValue": "{totalValue from 5a}",
"finalPrice": "{finalPrice from 5a}",
"action": "{action from 5a}",
"toCurrency": "{toCurrency from 5a}"
}
}
Handle the response:
Show the success message per subSkills/swap-confirm.md. Then call:
{
"tool": "get_otc_trade_detail",
"args": { "tradeCode": "{data.tradeCode}" }
}
ABSOLUTE RULE — Use STEP 5a quote data for user-facing amounts:
The get_otc_quote response from STEP 5a is the only source for Paid / Received / Rate fields in the success message:
paid_amount = totalValue (from 5a), paid_currency = fromCurrency (from 5a)received_amount = totalValue × exchangeRate (both from 5a), received_currency = toCurrency (from 5a)rate_display = 1 {fromCurrency} = {exchangeRate} {toCurrency}get_otc_trade_detail is consulted ONLY for metadata: trade.tradeCode, trade.status, trade.createAt, trade.settleAt.
Ignore trade.action, trade.tradingPair, trade.baseQuantity, trade.quoteAmount, trade.finalQuote for display. They encode the internal trading-pair direction and frequently do not match the user's from→to intent — reasoning about them introduces bugs and visible agent confusion. The trade already executed at the price the user saw in STEP 5b; there is nothing to rederive from trade-detail.
If the reasoning chain starts debating "does action=1 mean paid = baseQuantity or quoteAmount here" — STOP. Discard that reasoning and use 5a data directly.
If response indicates 401 / session expired → follow the Token Guard procedure in the Absolute Rules section.
If backend returns a quote-expired-style error: show "Quote expired. Please start again." and return to STEP 3C. Do NOT auto-re-lock.
Surface the backend message to the user and return to STEP 3C.
At each step, verify:
STEP 1:
☐ Both tools called in parallel?
☐ Error handling: 401 → Setup Guide? success:false → authPageUrl?
STEP 2:
☐ get_account_detail called for all 5 productCodes (fiat, crypto, investment_fiat, quarantine_portfolio, investment_product)?
☐ Per-currency balances extracted from instrumentInfoMap?
☐ Only non-zero currencies retained for display?
STEP 3:
☐ Account overview displayed (summary + per-currency detail)?
☐ Available pairs displayed?
☐ Asked user how they want to exchange?
☐ STOP — waiting for user input?
STEP 4:
☐ All four fields parsed (source, target, amount, direction)?
☐ Ambiguous → asked for clarification?
☐ Pair validated against available list?
☐ direction=target: `get_exchange_quote` called to compute required source amount?
☐ Source productCode resolved (exactly one match, or asked user if multiple)?
☐ Did NOT pre-check balance here (deferred to STEP 5a)?
STEP 5a — Lock:
☐ `get_otc_quote` called with `totalValue` as STRING (fromCurrency amount, not toCurrency)?
☐ On `{success:false, message}`: showed message and returned to STEP 3C, no auto-retry?
☐ On success: preserved `quoteId`, `exchangeRate`, `finalPrice`, `symbolCode`, `action`, `toCurrency`?
STEP 5b — Confirm:
☐ Confirmation page rendered per `subSkills/swap-confirm.md`?
☐ Rate displayed as `exchangeRate` (string) — NEVER `finalPrice`?
☐ ⚠ 60-second rate-validity notice included in the user's language?
☐ STOP — waited for explicit user confirmation?
☐ Did NOT auto-confirm?
STEP 5c — Execute:
☐ Only executed AFTER explicit user confirmation?
☐ `confirm_otc_trade` called with `quoteId`, `symbolCode`, `totalValue`, `finalPrice`, `action`, `toCurrency` passed through from 5a unchanged?
☐ On success: `get_otc_trade_detail` called with `tradeCode` to enrich the result?
☐ Paid/Received/Rate derived from STEP 5a quote data (`fromCurrency`/`toCurrency`/`totalValue`/`exchangeRate`), NOT from `trade.action`/`baseQuantity`/`quoteAmount`/`finalQuote`?
☐ Quote-expired / token / other failures handled per 5c guidance (return to STEP 3C)?
☐ Success/failure message displayed per `subSkills/swap-confirm.md`?
Repeat-swap sanity check:
☐ If the user is asking for a subsequent swap in this conversation, did I run the full flow from STEP 1 instead of refusing with a "one swap per conversation" message?
Any unchecked item → complete it before ending the response.
No server added yet → complete all 3 steps. Server added, no API key → skip to Step 2.
Sidebar → Customize → Connectors → + → Add custom connector
metacomp mcphttps://demo.metacomp.ai/mcpCustomize → Connectors → find metacomp mcp → Connect
Enter your sk-... API key → Allow
No API key? Apply at metacomp.ai
401 after connecting? Re-authorize or apply for a new key at metacomp.ai.
get_account_summary{}
Returns account balances across 5 categories: fiat, crypto, investment_fiat, quarantine_portfolio, investment_product. Each with availableAmount, pendingAmount, totalAmount.
get_available_currency_pairs{}
Returns pairs array in BASE/QUOTE format, e.g. ["USD/USDC", "GBP/USDT", ...].
get_account_detail{ "productCode": "fiat" }
productCode values: "fiat", "crypto", "investment_fiat", "quarantine_portfolio", "investment_product"
Returns:
data.holderCode — account identifier (e.g. "A0102634")data.productCode — echoes inputdata.totalAmount / availableAmount / pendingAmount — aggregated totalsdata.instrumentInfoMap — per-currency breakdown, keyed by currency code:
{
"USD": {
"unitCode": "USD",
"availableAmount": 13887754197.5,
"pendingAmount": 0,
"totalAmount": 13887754197.5,
"lockedAmount": null,
"availableAmountUSD": 13887754197.5
}
}
Key fields per currency: unitCode, availableAmount, pendingAmount, totalAmount, availableAmountUSD (USD equivalent).get_exchange_quote{
"fromCurrency": "GBP",
"toCurrency": "USDT"
}
Returns the current exchange rate for the specified currency pair.
Returns:
{
"rate": "0.09085953116481918953"
}
rate — exchange rate as a string (high precision). Meaning: 1 unit of fromCurrency = rate units of toCurrency.get_otc_quote{
"fromCurrency": "USD",
"toCurrency": "USDC",
"totalValue": "10"
}
fromCurrency — source currency codetoCurrency — target currency codetotalValue — amount of fromCurrency to lockReturns on success:
{
"quoteId": "q_abc123",
"exchangeRate": "0.95",
"finalPrice": "9.5",
"symbolCode": "S0000052",
"action": 1,
"baseCurrency": "USD",
"quoteCurrency": "USDC",
"fromCurrency": "USD",
"toCurrency": "USDC",
"totalValue": "10"
}
quoteId — locked quote identifierexchangeRate — displayed exchange rate (string, shown to user)finalPrice — internal trading-pair price (never shown to user, passed to confirm_otc_trade)symbolCode — trading symbol identifieraction — 1 = buy base, 2 = sell baseconfirm_otc_trade{
"quoteId": "q_abc123",
"symbolCode": "S0000052",
"totalValue": "10",
"finalPrice": "9.5",
"action": 1,
"toCurrency": "USDC"
}
Confirms the locked quote and executes the trade.
Returns on success:
{
"success": true,
"data": {
"tradeCode": "OT2026040917520001"
}
}
data.tradeCode — transaction reference IDget_otc_trade_detail{ "tradeCode": "OT2026040917520001" }
Call after confirm_otc_trade succeeds to fetch the final settled transaction details.
Returns:
{
"trade": {
"tradeCode": "OT2026041310070002",
"status": 1,
"action": 1,
"symbolCode": "S0000052",
"tradingPair": "GBP/USDT",
"baseQuantity": 5000,
"quoteAmount": 454.50413599,
"finalQuote": 0.0909008272,
"settleAt": "2026-04-13T16:00:00.000+00:00",
"createAt": "2026-04-13T02:07:41.000+00:00",
"updateAt": "2026-04-13T02:07:41.000+00:00"
}
}
trade is null if no matching trade is found.tradingPair is always BASE/QUOTE. baseQuantity is in the base currency, quoteAmount in the quote.action: 1 = buy base (user paid quote), 2 = sell base (user paid base).status: 1 = pending, 4 = settled/completed.finalQuote: locked-in rate where 1 base = finalQuote quote.Each swap request is an independent transaction. There is NO per-conversation limit on swaps.
subSkills/wealth-recommendation.md) — that rule does NOT apply to swaps.After EVERY MCP tool call, before processing the response data, check:
success: false AND authPageUrl → TOKEN EXPIREDYour session has expired. Please log in to continue:
Log in to MetaComp
Once logged in, come back here and let me know — I'll pick up where we left off.
get_account_summary() to re-verify session:
success: false with authPageUrl → repeat step 3 (show login link again)This rule takes priority over all step-specific error handling. Token expiration is always detected and handled before any other error logic.
This rule has the same priority as Token Guard. It applies whenever the user replies to STEP 3C and the reply does NOT contain a valid swap intent (no explicit currencies, no explicit amount, no explicit exchange direction).
Before you re-ask the STEP 3C question ("How would you like to exchange?") on a no-intent reply, you MUST have completed the following sequential evaluation:
Step A — Evaluate ALL 5 conditions of WEALTH_RECOMMENDATION_TRIGGER (defined in subSkills/wealth-recommendation.md). This evaluation is mandatory and must happen BEFORE any tool call. There is NO path that skips this evaluation.
Step B — Branch on result:
investor_precheck, then follow the recommendation flow in subSkills/wealth-recommendation.md.investor_precheck. Proceed directly to re-asking the STEP 3C question.⛔ There is no legitimate path where investor_precheck is called without first confirming all 5 conditions are TRUE. Calling investor_precheck "just in case" or "to be safe" when condition 5 is FALSE is a rule violation — it wastes an API call and may render an unwanted recommendation to a user with clear swap intent.
Evaluation is mandatory, render is non-blocking. "Non-blocking" in wealth-recommendation.md refers ONLY to the rendered output (the recommendation block never halts the primary flow). The evaluation itself is not skippable under any circumstance where the trigger context applies. Treating the evaluation as optional is a rule violation.
Self-check (mandatory before sending response): Re-read your draft:
investor_precheck, and proceed to STEP 4.A response that calls investor_precheck when condition 5 is FALSE, or that re-asks STEP 3C without evaluating the 5 conditions, is treated the same as skipping Token Guard — a rule violation.
This rule takes priority over the phrasing of the "Wealth Product Recommendation (non-blocking, on user's reply)" section in STEP 3C — that heading describes the render as non-blocking; the evaluation is mandatory per this Gate.
Primo_Link and PX-First are internal currencies — display them like any other currency0.00.tools
MetaComp + VisionX — one skill for all MetaComp account and Web3-security actions over the metacomp-mcp connector; routes to the matching scenario. Use it whenever the user wants to: DEPOSIT / receive funds (deposit, 充值, 入金, 收款, 收钱); WITHDRAW / cash out (withdraw, cash out, 提现, 出金, 转出, 取钱, withdrawal history, 出金记录); SWAP / exchange currency (swap, exchange, convert, 换汇, 换钱, "100k USDT to SGD", swap history, 换汇记录); GET A RATE / PRICE (汇率, 查汇率, 报价, 价格, "price X to Y", "X to Y rate", "how much is X in Y", "X 值多少 Y"); WEALTH / FIP (wealth, fixed income, subscribe, 理财, 买理财, 认购, FIP 申购); VIEW BALANCE / ASSETS (check balance, view assets, account overview, 查余额, 查看资产, 账户概览); or WEB3 SECURITY via VisionX (a wallet address 0x…/Bitcoin/Tron, a transaction hash, or any Web3 security / risk / scam / suspicious-activity question). Trigger even when the user doesn't say "MetaComp", as long as the intent is one of these; when unsure, load it and let the router (STEP ZERO) disambiguate.
tools
Withdraw funds out of MetaComp Withdrawal account. Trigger when the user mentions: withdraw, withdrawal, send money, cash out, 提现, 出金, 转出, 取钱, 我要出金, 我要提现.
testing
Subscribe to MetaComp Wealth / Fixed Income Products (FIP). Trigger when the user mentions: wealth, financial product, fixed income, subscribe, 理财, 理财产品, 买理财, 我想买一些理财, 了解理财, 认购理财.
development
Check Web3 wallet or transaction security using the MetaComp VisionX Trigger when the user mentions: wallet address (0x..., Bitcoin address, Tron address), transaction hash, or asks about Web3 security, risk, scam, or suspicious activity.