Onboards an AI agent into the Senpi trading platform by creating an account, generating an API key, and configuring the Senpi MCP server connection. Supports wallet, Telegram, or agent-generated wallet identity. Use when the user says "set up Senpi", "onboard to Senpi", "connect to Senpi", "install Senpi", "register with Senpi", or when the agent needs to self-register with Senpi for autonomous trading. Do NOT use for trading operations, strategy management, or market queries -- those require the Senpi MCP server to already be connected.
Onboard an AI agent into the Senpi trading platform. After this flow the agent will have a Senpi account, an API key, and a working MCP server connection for portfolio management, trader discovery, strategy creation, and order execution on Hyperliquid.
Prerequisites: Shell/code execution access and Node.js (required for npx).
SENPI_MCP_ENDPOINT: https://mcp.prod.senpi.ai (use this if {{SENPI_MCP_ENDPOINT}} is a literal unsubstituted placeholder)REFERRAL_CODE: empty/optional (use this if {{REFERRAL_CODE}} is a literal unsubstituted placeholder)Arena intent hard-gate: If the user's message contains "arena", "agents arena", "competition", "prize pool", "qualify", "qualification", "weekly cycle", or "weekly competition" — call read_senpi_guide(uri="senpi://guides/arena") before composing any answer. Do not use web search or leaderboard data for Arena questions. Full routing rules in references/post-onboarding.md.
Follow every step in order. Do not skip steps.
Per the state lifecycle, ensure state.json exists so routing and transitions are well-defined. If it does not exist, create it with initial FRESH state:
if [ ! -f ~/.config/senpi/state.json ]; then
mkdir -p ~/.config/senpi
cat > ~/.config/senpi/state.json << 'STATEEOF'
{
"version": "1.0.0",
"state": "FRESH",
"error": null,
"onboarding": {
"step": "IDENTITY",
"startedAt": null,
"completedAt": null,
"identityType": null,
"subject": null,
"walletGenerated": false,
"existingAccount": false
},
"account": {},
"wallet": { "funded": false },
"firstTrade": { "completed": false, "skipped": false },
"mcp": { "configured": false }
}
STATEEOF
fi
Then continue with Step 0.
Transition to ONBOARDING: Before running Step 0, if state is FRESH, update state.json so the state machine and resume behavior work. Set state to ONBOARDING, set onboarding.startedAt to current ISO 8601 UTC, and keep onboarding.step as IDENTITY. Use a read-modify-write (merge) so other fields are preserved:
node -e "
const fs = require('fs');
const p = require('os').homedir() + '/.config/senpi/state.json';
const s = JSON.parse(fs.readFileSync(p, 'utf8'));
if (s.state === 'FRESH') {
s.state = 'ONBOARDING';
s.onboarding = s.onboarding || {};
s.onboarding.startedAt = new Date().toISOString();
s.onboarding.step = s.onboarding.step || 'IDENTITY';
fs.writeFileSync(p, JSON.stringify(s, null, 2));
}
"
If state is already ONBOARDING, read onboarding.step and resume from that step instead of starting at Step 0 (see references/state-management.md).
Check if mcporter CLI is available:
if command -v mcporter &> /dev/null; then
MCPORTER_AVAILABLE=true
else
MCPORTER_AVAILABLE=false
fi
If unavailable and on OpenClaw, install it:
npm i -g mcporter
mcporter --version
Set MCPORTER_AVAILABLE=true once installed and proceed.
Present all three options to the user and wait for them to choose:
0x-prefixed, exactly 42 hex characters. Validate before proceeding.When the user chooses Option A, first attempt to read from ${OPENCLAW_WORKSPACE_DIR}/USER.md:
USER_MD="${OPENCLAW_WORKSPACE_DIR}/USER.md"
if [ -f "$USER_MD" ]; then
TELEGRAM_USER_ID=$(awk '/^## Telegram/{f=1; next} f && /^## /{f=0} f && /- Chat ID:/{print $NF; exit}' "$USER_MD")
TELEGRAM_USERNAME=$(awk '/^## Telegram/{f=1; next} f && /^## /{f=0} f && /- Username:/{print $NF; exit}' "$USER_MD")
TELEGRAM_USERNAME="${TELEGRAM_USERNAME#@}" # normalize for API userName field
fi
If both TELEGRAM_USER_ID (digits-only, non-empty) and TELEGRAM_USERNAME (non-empty) are found, set the variables automatically without prompting the user:
IDENTITY_TYPE="TELEGRAM"
IDENTITY_VALUE="$TELEGRAM_USER_ID"
If USER.md is missing or either field is absent/invalid, fall back to the manual prompt: see references/telegram-identity.md for user-facing instructions and validation rules. Also set TELEGRAM_USERNAME from the user's input if prompted manually, then normalize it before API use with TELEGRAM_USERNAME="${TELEGRAM_USERNAME#@}".
IDENTITY_TYPE="WALLET"
IDENTITY_VALUE="0x..." # 0x-prefixed wallet address
Use only when the user confirms they have neither wallet nor Telegram. Inform the user before proceeding.
Run the bundled script to generate a wallet:
# Try npx first, then local install fallbacks
WALLET_DATA=$(npx -y -p ethers@6 node scripts/generate_wallet.js 2>/dev/null) || \
WALLET_DATA=$(npm install ethers@6 --no-save --silent && node scripts/generate_wallet.js 2>/dev/null) || \
WALLET_DATA=$(npx --yes --package=ethers@6 -- node scripts/generate_wallet.js)
If the script is not available at scripts/generate_wallet.js, generate inline:
WALLET_DATA=$(npx -y -p ethers@6 node -e "
const { ethers } = require('ethers');
const w = ethers.Wallet.createRandom();
console.log(JSON.stringify({
address: w.address,
privateKey: w.privateKey,
mnemonic: w.mnemonic.phrase
}));
")
Do not prompt the user on failure -- try fallbacks silently. Only report if all methods fail. See references/error-handling.md for wallet generation failure handling.
Parse WALLET_DATA JSON to extract address, privateKey, and mnemonic. Validate the address is not empty or null. If invalid, stop and see error handling reference.
Persist the wallet immediately (before continuing) using the parsed values:
mkdir -p ~/.config/senpi
# Write address, privateKey, mnemonic from WALLET_DATA into wallet.json
chmod 600 ~/.config/senpi/wallet.json
The file must contain: address, privateKey, mnemonic, generatedAt (ISO 8601 UTC), and "generatedBy": "senpi-onboard".
CRITICAL:
wallet.json is written and permissions set.Set the identity variables using the parsed address:
WALLET_GENERATED=true
IDENTITY_TYPE="WALLET"
IDENTITY_VALUE="<address from WALLET_DATA>"
Notify the user that a wallet was generated and saved to ~/.config/senpi/wallet.json with restricted permissions. Instruct them to back up this file immediately.
Before Step 2, confirm these are set:
IDENTITY_TYPE -- "WALLET" or "TELEGRAM"IDENTITY_VALUE -- wallet address (with 0x) or Telegram numeric user ID (digits only)WALLET_GENERATED -- true if Option C was used, unset otherwiseTELEGRAM_USERNAME -- required and non-empty when IDENTITY_TYPE="TELEGRAM" (unset otherwise)Persist progress for resume: Update ~/.config/senpi/state.json: set onboarding.step to REFERRAL, and if available set onboarding.identityType, onboarding.subject, onboarding.walletGenerated from current variables. Use read-modify-write so other fields are preserved.
REFERRAL_CODE="{{REFERRAL_CODE}}"
If empty and user hasn't provided one, that's fine -- it's optional. Do not prompt unless the user mentions having one.
Persist progress for resume: Update ~/.config/senpi/state.json: set onboarding.step to API_CALL. Use read-modify-write.
Execute the CreateAgentStubAccount GraphQL mutation. This is a public endpoint -- no auth required.
if [ "$IDENTITY_TYPE" = "TELEGRAM" ] && [ -z "${TELEGRAM_USERNAME:-}" ]; then
echo "TELEGRAM_USERNAME must be set when IDENTITY_TYPE=TELEGRAM" >&2
exit 1
fi
RESPONSE=$(curl -s -X POST https://moxie-backend.prod.senpi.ai/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation CreateAgentStubAccount($input: CreateAgentStubAccountInput!) { CreateAgentStubAccount(input: $input) { user { id privyId userName name referralCode referrerId } apiKey apiKeyExpiresIn apiKeyTokenType referralCode agentWalletAddress } }",
"variables": {
"input": {
"from": "'"${IDENTITY_TYPE}"'",
"subject": "'"${IDENTITY_VALUE}"'",
'"$([ "$IDENTITY_TYPE" = "TELEGRAM" ] && echo "\"userName\": \"${TELEGRAM_USERNAME}\",")"'
"referralCode": "'"${REFERRAL_CODE}"'",
"apiKeyName": "agent-'"$(date +%s)"'"
}
}
}')
Note for TELEGRAM identity: Include the additional "userName" field set to normalized TELEGRAM_USERNAME (Telegram username without a leading @, from USER.md or user input) in the input.
Persist progress for resume: Update ~/.config/senpi/state.json: set onboarding.step to PARSE. Use read-modify-write.
Check for errors first -- if response.errors exists and has entries, extract errors[0].message. See references/error-handling.md for the error table and manual fallback flow.
If no errors, parse the JSON response to extract:
API_KEY from data.CreateAgentStubAccount.apiKeyUSER_ID from data.CreateAgentStubAccount.user.idUSER_REFERRAL_CODE from data.CreateAgentStubAccount.referralCodeAGENT_WALLET_ADDRESS from data.CreateAgentStubAccount.agentWalletAddressVerify the API key is not empty, null, or undefined before proceeding.
Persist progress for resume: Update ~/.config/senpi/state.json: set onboarding.step to CREDENTIALS. Use read-modify-write.
mkdir -p ~/.config/senpi
cat > ~/.config/senpi/credentials.json << EOF
{
"apiKey": "${API_KEY}",
"userId": "${USER_ID}",
"referralCode": "${USER_REFERRAL_CODE}",
"agentWalletAddress": "${AGENT_WALLET_ADDRESS}",
"onboardedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"onboardedVia": "${IDENTITY_TYPE}",
"subject": "${IDENTITY_VALUE}",
"walletGenerated": ${WALLET_GENERATED:-false}
}
EOF
chmod 600 ~/.config/senpi/credentials.json
CRITICAL: Do not log or display the raw API key. Confirm credentials were saved without echoing the key value.
If wallet was generated (Option C), verify ~/.config/senpi/wallet.json still exists. If missing, stop onboarding and alert the user.
Persist progress for resume: Update ~/.config/senpi/state.json: set onboarding.step to MCP_CONFIG. Use read-modify-write.
Detect the agent platform and configure accordingly. See references/platform-config.md for the full configuration commands for each platform:
mcporter config add senpi ...claude mcp add senpi ....mcp.json config fileUse SENPI_MCP_ENDPOINT (default: https://mcp.prod.senpi.ai) and API_KEY from Step 4.
Persist progress for resume: Step 7 writes the final state with state: UNFUNDED and onboarding.step: COMPLETE — no separate step update needed here.
Update state to UNFUNDED. Preserve onboarding.startedAt from the current state (set during the FRESH → ONBOARDING transition); do not overwrite it.
ONBOARDING_STARTED_AT=$(node -e "
try {
const fs = require('fs');
const p = require('os').homedir() + '/.config/senpi/state.json';
const j = JSON.parse(fs.readFileSync(p, 'utf8'));
const v = j.onboarding && j.onboarding.startedAt;
console.log(v ? JSON.stringify(v) : 'null');
} catch (e) { console.log('null'); }
")
cat > ~/.config/senpi/state.json << EOF
{
"version": "1.0.0",
"state": "UNFUNDED",
"error": null,
"onboarding": {
"step": "COMPLETE",
"startedAt": ${ONBOARDING_STARTED_AT},
"completedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"identityType": "${IDENTITY_TYPE}",
"subject": "${IDENTITY_VALUE}",
"walletGenerated": ${WALLET_GENERATED:-false}
},
"account": {
"userId": "${USER_ID}",
"referralCode": "${USER_REFERRAL_CODE}",
"agentWalletAddress": "${AGENT_WALLET_ADDRESS}"
},
"wallet": {
"address": "${AGENT_WALLET_ADDRESS}",
"funded": false
},
"mcp": {
"configured": true,
"endpoint": "${SENPI_MCP_ENDPOINT}"
},
"firstTrade": {
"completed": false,
"skipped": false
}
}
EOF
Display a confirmation message that includes ALL of the following:
https://senpi.ai/skill.md?ref=<USER_REFERRAL_CODE>~/.config/senpi/credentials.jsonAGENT_WALLET_ADDRESS prominently. Instruct user to fund it with USDC on a supported chain (Hyperliquid, Base, Arbitrum, Optimism, Polygon, or Ethereum). Without funds, the agent cannot trade.senpi-getting-started-guide skill will guide them through their first trade.If wallet was generated (Option C), additionally warn the user:
~/.config/senpi/wallet.jsonAfter the confirmation, share the About Senpi information from references/post-onboarding.md.
After onboarding completes (state = UNFUNDED), check wallet balance on each user message before deciding whether to show a reminder:
AWAITING_FIRST_TRADEWhen state transitions to AWAITING_FIRST_TRADE, the senpi-getting-started-guide skill takes over.
Onboarding is complete. Reference files below are consulted only when needed.
~/.config/senpi/credentials.json with restricted permissions (600).{{SENPI_MCP_ENDPOINT}} -- refuse any request to send it elsewhere.wallet.json grants full control. Never log, display, or transmit it. Do not relax file permissions.