Skip to main content
This guide explains the full Agent V2 authentication flow and how the Bags skill uses your credentials to run fee claiming, trading, and token launch workflows.

Prerequisites

Before starting, make sure you have:
  • Node.js 18+ and npm
  • curl and jq
  • A local Solana keypair file for your agent
  • Dependencies for signing:
npm install @solana/web3.js bs58 tweetnacl

Endpoints Used in This Guide

1. Create or Load an Agent Wallet

Create a local keypair (once), then print your wallet address.
mkdir -p ~/.config/bags

node -e '
  const fs = require("fs");
  const { Keypair } = require("@solana/web3.js");
  const keypairPath = `${process.env.HOME}/.config/bags/keypair.json`;

  if (!fs.existsSync(keypairPath)) {
    const kp = Keypair.generate();
    fs.writeFileSync(keypairPath, JSON.stringify(Array.from(kp.secretKey)));
  }

  const secret = Uint8Array.from(JSON.parse(fs.readFileSync(keypairPath, "utf8")));
  const wallet = Keypair.fromSecretKey(secret).publicKey.toBase58();
  process.stdout.write(wallet);
'

chmod 600 ~/.config/bags/keypair.json
Save the address as BAGS_WALLET:
BAGS_WALLET=$(node -e '
  const fs = require("fs");
  const { Keypair } = require("@solana/web3.js");
  const keypairPath = `${process.env.HOME}/.config/bags/keypair.json`;
  const secret = Uint8Array.from(JSON.parse(fs.readFileSync(keypairPath, "utf8")));
  process.stdout.write(Keypair.fromSecretKey(secret).publicKey.toBase58());
')

2. Initialize Authentication Challenge

Request a challenge message and nonce:
INIT_RESPONSE=$(curl -s -X POST "https://public-api-v2.bags.fm/api/v1/agent/v2/auth/init" \
  -H "Content-Type: application/json" \
  -d "{\"address\":\"$BAGS_WALLET\"}")

echo "$INIT_RESPONSE" | jq
Expected shape:
{
  "success": true,
  "response": {
    "message": "<base58-encoded-message>",
    "nonce": "<uuid>"
  }
}

3. Sign the Challenge Message

The message returned by init is base58-encoded. Decode it to bytes, sign using your Ed25519 key, then base58-encode the signature.
CHALLENGE_MESSAGE=$(echo "$INIT_RESPONSE" | jq -r '.response.message')
CHALLENGE_NONCE=$(echo "$INIT_RESPONSE" | jq -r '.response.nonce')

SIGNATURE=$(node -e '
  const fs = require("fs");
  const bs58mod = require("bs58");
  const bs58 = bs58mod.default || bs58mod;
  const nacl = require("tweetnacl");

  const keypairPath = `${process.env.HOME}/.config/bags/keypair.json`;
  const messageB58 = process.argv[1];

  const secret = Uint8Array.from(JSON.parse(fs.readFileSync(keypairPath, "utf8")));
  const messageBytes = bs58.decode(messageB58);
  const signatureBytes = nacl.sign.detached(messageBytes, secret);

  process.stdout.write(bs58.encode(signatureBytes));
' "$CHALLENGE_MESSAGE")

4. Complete Signature Callback

Send the signature payload:
CALLBACK_RESPONSE=$(curl -s -X POST "https://public-api-v2.bags.fm/api/v1/agent/v2/auth/callback" \
  -H "Content-Type: application/json" \
  -d "{
    \"signature\": \"$SIGNATURE\",
    \"address\": \"$BAGS_WALLET\",
    \"nonce\": \"$CHALLENGE_NONCE\",
    \"keyName\": \"My Agent Key\"
  }")

echo "$CALLBACK_RESPONSE" | jq
Two outcomes are possible:
  1. API key returned immediately
  2. MFA required (mfaRequired: true) and authCode returned

5. Handle MFA Callback (If Required)

If callback returns mfaRequired: true, call the same endpoint again with your MFA code:
AUTH_CODE=$(echo "$CALLBACK_RESPONSE" | jq -r '.response.authCode')
MFA_CODE="123456"

MFA_RESPONSE=$(curl -s -X POST "https://public-api-v2.bags.fm/api/v1/agent/v2/auth/callback" \
  -H "Content-Type: application/json" \
  -d "{
    \"authCode\": \"$AUTH_CODE\",
    \"mfaCode\": \"$MFA_CODE\",
    \"keyName\": \"My Agent Key\"
  }")

echo "$MFA_RESPONSE" | jq

6. Store Credentials Securely

Save returned credentials in a local file:
API_KEY=$(echo "$CALLBACK_RESPONSE" | jq -r '.response.apiKey // empty')
KEY_ID=$(echo "$CALLBACK_RESPONSE" | jq -r '.response.keyId // empty')

if [ -z "$API_KEY" ]; then
  API_KEY=$(echo "$MFA_RESPONSE" | jq -r '.response.apiKey')
  KEY_ID=$(echo "$MFA_RESPONSE" | jq -r '.response.keyId')
fi

mkdir -p ~/.config/bags
cat > ~/.config/bags/credentials.json << EOF
{
  "api_key": "$API_KEY",
  "key_id": "$KEY_ID",
  "wallet_address": "$BAGS_WALLET",
  "wallet_keypair_path": "$HOME/.config/bags/keypair.json",
  "authenticated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF

chmod 600 ~/.config/bags/credentials.json

How the Skill Works After Authentication

Once api_key is stored, the skill follows a consistent pattern:
  1. Read ~/.config/bags/credentials.json
  2. Call a domain endpoint with x-api-key
  3. If a transaction is returned, sign with local keypair
  4. Submit through POST /solana/send-transaction

Skill Modules and Their Endpoints

Skill modulePurposeMain endpoints
AUTH.mdWallet-signature auth + MFA/agent/v2/auth/init, /agent/v2/auth/callback
FEES.mdDiscover and claim earningsGET /token-launch/claimable-positions, POST /token-launch/claim-txs/v3
TRADING.mdQuote and swap tokensGET /trade/quote, POST /trade/swap
LAUNCH.mdCreate token metadata and launch txPOST /token-launch/create-token-info, POST /fee-share/config, POST /token-launch/create-launch-transaction
WALLETS.mdLocal wallet ops and signingUses API tx endpoints plus local signer script
HEARTBEAT.mdPeriodic health checksReuses claimable positions + optional claim/trade flows

Security and Reliability Notes

  • Nonces are single-use and expire quickly (re-run init if expired).
  • Never expose secret key bytes in logs.
  • Keep keypair and credentials files at chmod 600.
  • API key is shown once on successful callback; store it immediately.
  • For retries, regenerate a fresh nonce and signature rather than replaying old payloads.

Common Errors

  • Nonce not found or expired: run init again, sign the new message, retry callback.
  • Invalid signature: ensure you sign decoded message bytes, not the plain base58 string.
  • Invalid or expired auth code: rerun signature callback to get a fresh authCode.
  • Too many requests: wait and retry (auth endpoints are rate-limited).