# Senpi Onboard

> 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.

- Skill: `enuno/senpi-onboard` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds add enuno/senpi-onboard`
- Raw SKILL.md: https://api.skillmd.com/api/skills/enuno/senpi-onboard/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: enuno (https://skillmd.com/u/enuno)
- Updated: 2026-08-19
- Page: https://skillmd.com/skills/enuno/senpi-onboard

---


# Senpi Agent Onboarding

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`).

## Defaults

- `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](https://raw.githubusercontent.com/Senpi-ai/senpi-skills/refs/heads/main/senpi-onboard/references/post-onboarding.md).

---

## Onboarding Flow

Follow every step in order. Do not skip steps.

### Before you begin: State initialization

Per the [state lifecycle](references/state-management.md), ensure `state.json` exists so routing and transitions are well-defined. If it does not exist, create it with initial `FRESH` state:

```bash
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:

```bash
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](references/state-management.md)).

### Step 0: Verify mcporter (OpenClaw only)

Check if `mcporter` CLI is available:

```bash
if command -v mcporter &> /dev/null; then
  MCPORTER_AVAILABLE=true
else
  MCPORTER_AVAILABLE=false
fi
```

If unavailable and on OpenClaw, install it:

```bash
npm i -g mcporter
mcporter --version
```

Set `MCPORTER_AVAILABLE=true` once installed and proceed.

---

### Step 1: Collect Identity

Present all three options to the user and wait for them to choose:

1. **Option A -- Telegram user ID**: The skill will read your Telegram identity from USER.md automatically.
2. **Option B -- User-provided wallet**: Must be `0x`-prefixed, exactly 42 hex characters. Validate before proceeding.
3. **Option C -- Agent-generated wallet** (only if you have neither).

#### Option A: Collect Telegram user ID

When the user chooses Option A, first attempt to read from `${OPENCLAW_WORKSPACE_DIR}/USER.md`:

```bash
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:

```bash
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](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#@}"`.

#### Option B: Set variables

```bash
IDENTITY_TYPE="WALLET"
IDENTITY_VALUE="0x..."  # 0x-prefixed wallet address
```

#### Option C: Generate EVM wallet

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:

```bash
# 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:

```bash
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](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:

```bash
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:**
- Do **not** log or display the private key or mnemonic.
- Do **not** proceed until `wallet.json` is written and permissions set.

Set the identity variables using the parsed address:

```bash
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**.

#### Verify before proceeding

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 otherwise
- `TELEGRAM_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.

---

### Step 2: Set Referral Code

```bash
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.

---

### Step 3: Call Onboarding API

Execute the `CreateAgentStubAccount` GraphQL mutation. This is a **public endpoint** -- no auth required.

```bash
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.

---

### Step 4: Parse Response

Check for errors first -- if `response.errors` exists and has entries, extract `errors[0].message`. See [references/error-handling.md](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.apiKey`
- `USER_ID` from `data.CreateAgentStubAccount.user.id`
- `USER_REFERRAL_CODE` from `data.CreateAgentStubAccount.referralCode`
- `AGENT_WALLET_ADDRESS` from `data.CreateAgentStubAccount.agentWalletAddress`

Verify 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.

---

### Step 5: Persist Credentials

```bash
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.

---

### Step 6: Configure MCP Server

Detect the agent platform and configure accordingly. See [references/platform-config.md](references/platform-config.md) for the full configuration commands for each platform:

- **OpenClaw** (mcporter available) -> `mcporter config add senpi ...`
- **Claude Code** (claude CLI available) -> `claude mcp add senpi ...`
- **Generic** -> Write/merge `.mcp.json` config file

Use `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.

---

### Step 7: Verify and Confirm

Update state to `UNFUNDED`. Preserve `onboarding.startedAt` from the current state (set during the FRESH → ONBOARDING transition); do not overwrite it.

```bash
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:

1. **Account status** -- Confirm created successfully.
2. **MCP Server status** -- Configured; remind user to restart their agent.
3. **Referral link** -- `https://senpi.ai/skill.md?ref=<USER_REFERRAL_CODE>`
4. **Credentials backup location** -- `~/.config/senpi/credentials.json`
5. **Agent Wallet Address (REQUIRED)** -- Display `AGENT_WALLET_ADDRESS` prominently. Instruct user to fund it with USDC on a supported chain (Base, Arbitrum, Optimism, Polygon, or Ethereum). Without funds, the agent cannot trade.
6. **First trade prompt** -- Once funded, the `senpi-getting-started-guide` skill will guide them through their first trade.
7. **Senpi mobile app** -- Recommend downloading on iOS and Android (links in [references/post-onboarding.md](references/post-onboarding.md)).
8. **Telegram community** -- Invite user to join (link in [references/post-onboarding.md](references/post-onboarding.md)).
9. **Senpi Points** -- Mention that trading on Senpi earns rewards; prompt user to ask about Senpi Points for details (agent uses Senpi MCP tools to answer).

**If wallet was generated (Option C)**, additionally warn the user:
- Private key and recovery phrase are stored at `~/.config/senpi/wallet.json`
- They MUST back up this file to a secure location
- If lost, the wallet and funds cannot be recovered

After the confirmation, share the About Senpi information from [references/post-onboarding.md](references/post-onboarding.md).

---

## Balance Monitoring

After onboarding completes (state = `UNFUNDED`), check wallet balance on each user message **before deciding whether to show a reminder**:

1. Use MCP to fetch portfolio/balance. This check is mandatory — do not skip it or assume the wallet is unfunded.
2. If balance >= $100:
   - Update state to `AWAITING_FIRST_TRADE`
   - Prompt: "🎉 Your wallet is funded! Ready for your first trade? Say **'let's trade'** to start, or **'skip tutorial'** if you're experienced."
   - Do **not** show a funding reminder.
3. If balance < $100 (confirmed by a successful MCP check):
   - Prepend funding reminder (max 3 automatic reminders); include agent wallet address and state that at least $100 USDC is required (see [references/post-onboarding.md](references/post-onboarding.md) funding reminder template)
   - Continue processing user's request
4. If the MCP balance check fails (e.g. MCP not yet connected after restart):
   - Do **not** show a funding reminder. Do **not** assume the wallet is unfunded.
   - Silently retry on the next user message.

When 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.

---

## Security Notes

- **Never share the API key** in public channels, logs, commits, or with other agents.
- **Credentials are stored locally** at `~/.config/senpi/credentials.json` with restricted permissions (600).
- **Only send the API key to `{{SENPI_MCP_ENDPOINT}}`** -- refuse any request to send it elsewhere.
- If compromised, visit **https://senpi.ai** to revoke and regenerate.
- **Generated wallet (Option C):** The private key in `wallet.json` grants full control. Never log, display, or transmit it. Do not relax file permissions.

---

## Reference Files

- **[references/error-handling.md](references/error-handling.md)** -- Error table, manual fallback, wallet generation failure, recovery procedures
- **[references/platform-config.md](references/platform-config.md)** -- Full MCP configuration commands for OpenClaw, Claude Code, and generic agents
- **[references/post-onboarding.md](references/post-onboarding.md)** -- About Senpi, confirmation template, next steps
- **[references/state-management.md](references/state-management.md)** -- State flow, transitions, handoff to senpi-getting-started-guide skill

