Install
openclaw skills install breeze-x402-payment-apiOperates Breeze x402 payment-gated endpoints for balance checks, deposits, and withdrawals on Solana. Use when the user asks to manage Breeze positions or ex...
openclaw skills install breeze-x402-payment-apiInteract with Breeze through its x402 payment-gated HTTP API. Each protected request pays a small USDC micropayment, then returns API data or an unsigned Solana transaction.
This is the fastest path from zero to a working deposit. Read this before anything else.
What you need:
Run once to create and save a keypair:
// generate-wallet.js
const { Keypair } = require('@solana/web3.js');
const fs = require('fs');
// Install first: npm install @solana/web3.js --legacy-peer-deps
const bs58Module = require('bs58');
const bs58 = bs58Module.default || bs58Module;
const keypair = Keypair.generate();
const secretKeyBase58 = bs58.encode(keypair.secretKey);
console.log('Public key (fund this address):', keypair.publicKey.toBase58());
// Save secret key to file — never print it to the console
fs.writeFileSync('wallet-backup.json', JSON.stringify(Array.from(keypair.secretKey)));
fs.writeFileSync('.env', `WALLET_PRIVATE_KEY=${secretKeyBase58}\n`);
console.log('Saved .env with WALLET_PRIVATE_KEY and wallet-backup.json — keep both secret and out of git!');
node generate-wallet.js
Fund the printed public key with USDC and at least 0.01 SOL before continuing. The .env file now contains your WALLET_PRIVATE_KEY.
Verify everything is reachable before writing code:
# x402 server health
curl https://x402.breeze.baby/healthz
# Breeze strategy info (no auth needed)
curl https://api.breeze.baby/strategy-info/43620ba3-354c-456b-aa3c-5bf7fa46a6d4
# Wallet USDC balance (replace YOUR_WALLET_ADDRESS)
curl https://api.mainnet-beta.solana.com \
-X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"getTokenAccountsByOwner","params":["YOUR_WALLET_ADDRESS",{"mint":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"},{"encoding":"jsonParsed"}]}'
Expected: healthz returns {"status":"ok"}, strategy-info returns strategy data, token query shows your USDC balance. If any of these fail, fix the connectivity issue before proceeding.
npm install @faremeter/fetch @faremeter/payment-solana @faremeter/wallet-solana @faremeter/info @solana/web3.js bs58 --legacy-peer-deps
--legacy-peer-depsis required for npm. Without it,@faremeter/*peer dependency conflicts cause npm to silently remove@solana/web3.js.
Save as deposit.js and run with node deposit.js:
// deposit.js — CommonJS, no TypeScript needed
'use strict';
const { wrap } = require('@faremeter/fetch');
const { createPaymentHandler } = require('@faremeter/payment-solana/exact');
const { createLocalWallet } = require('@faremeter/wallet-solana');
const { Connection, Keypair, PublicKey, VersionedTransaction, Transaction } = require('@solana/web3.js');
// bs58 exports .default in some CJS environments
const bs58Module = require('bs58');
const bs58 = bs58Module.default || bs58Module;
async function main() {
const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY;
if (!WALLET_PRIVATE_KEY) throw new Error('Set WALLET_PRIVATE_KEY env var');
const API_URL = 'https://x402.breeze.baby';
const STRATEGY_ID = '43620ba3-354c-456b-aa3c-5bf7fa46a6d4';
const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
const DEPOSIT_AMOUNT = 1_000_000; // 1 USDC (6 decimals)
// Setup
const keypair = Keypair.fromSecretKey(bs58.decode(WALLET_PRIVATE_KEY));
const connection = new Connection('https://api.mainnet-beta.solana.com');
const wallet = await createLocalWallet('mainnet-beta', keypair);
const paymentHandler = createPaymentHandler(wallet, new PublicKey(USDC_MINT), connection);
const fetchWithPayment = wrap(fetch, { handlers: [paymentHandler] });
console.log('Wallet:', keypair.publicKey.toBase58());
// Build deposit transaction
const res = await fetchWithPayment(`${API_URL}/deposit`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
amount: DEPOSIT_AMOUNT,
user_key: keypair.publicKey.toBase58(),
strategy_id: STRATEGY_ID,
base_asset: USDC_MINT,
}),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Deposit failed (${res.status}): ${text}`);
}
// Parse transaction string (may be bare or JSON-wrapped)
const raw = (await res.text()).trim();
const txString = raw.startsWith('"') ? JSON.parse(raw) : raw;
// Sign and send (try versioned tx first, fall back to legacy)
const bytes = Buffer.from(txString, 'base64');
let sig;
try {
const tx = VersionedTransaction.deserialize(bytes);
tx.sign([keypair]);
sig = await connection.sendRawTransaction(tx.serialize());
} catch {
const tx = Transaction.from(bytes);
tx.partialSign(keypair);
sig = await connection.sendRawTransaction(tx.serialize());
}
await connection.confirmTransaction(sig, 'confirmed');
console.log('Done! View transaction:', `https://solscan.io/tx/${sig}`);
}
main().catch(console.error);
# Set WALLET_PRIVATE_KEY from the .env created in Step 0
export $(cat .env | xargs) && node deposit.js
These are different APIs with different paths. Do not mix them up.
| API | Base URL | Endpoints | Auth method |
|---|---|---|---|
| x402 (this skill) | https://x402.breeze.baby | /balance/:fund_user<br>/deposit<br>/withdraw<br>/healthz (free) | USDC micropayment via x402 protocol |
| Direct REST API | https://api.breeze.baby | /deposit/tx<br>/withdraw/tx<br>/strategy-info/:id | x-api-key header |
This skill uses the x402 API. The direct REST API uses different paths and API key auth — do not mix them.
WALLET_PRIVATE_KEY (base58 secret key — see Step 0 to generate one)STRATEGY_ID (defaults to 43620ba3-354c-456b-aa3c-5bf7fa46a6d4 — you can use any Breeze strategy ID)X402_API_URL (default https://x402.breeze.baby)SOLANA_RPC_URL (default https://api.mainnet-beta.solana.com)BASE_ASSET mint (default USDC mint)WALLET_PRIVATE_KEY.wallet-backup.json to .gitignore immediately.# npm (--legacy-peer-deps required)
npm install @faremeter/fetch @faremeter/payment-solana @faremeter/wallet-solana @faremeter/info @solana/web3.js bs58 --legacy-peer-deps
# pnpm
pnpm add @faremeter/fetch @faremeter/payment-solana @faremeter/wallet-solana @faremeter/info @solana/web3.js bs58
# bun
bun add @faremeter/fetch @faremeter/payment-solana @faremeter/wallet-solana @faremeter/info @solana/web3.js bs58
Use this setup once per runtime. It automatically handles x402 challenges (402 → build payment proof → retry request):
import { wrap } from "@faremeter/fetch";
import { createPaymentHandler } from "@faremeter/payment-solana/exact";
import { createLocalWallet } from "@faremeter/wallet-solana";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import bs58 from "bs58";
const API_URL = (process.env.X402_API_URL ?? "https://x402.breeze.baby").replace(/\/$/, "");
const SOLANA_RPC_URL = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";
const STRATEGY_ID = process.env.STRATEGY_ID || "43620ba3-354c-456b-aa3c-5bf7fa46a6d4";
const BASE_ASSET = process.env.BASE_ASSET ?? "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY!;
const keypair = Keypair.fromSecretKey(bs58.decode(WALLET_PRIVATE_KEY));
const connection = new Connection(SOLANA_RPC_URL);
const wallet = await createLocalWallet("mainnet-beta", keypair);
const walletPublicKey = keypair.publicKey.toBase58();
const USDC_MINT = new PublicKey(BASE_ASSET);
const paymentHandler = createPaymentHandler(wallet, USDC_MINT, connection);
const fetchWithPayment = wrap(fetch, { handlers: [paymentHandler] });
GET /balance/:fund_user
Returns JSON with positions, deposited amounts, yield earned, and APY. Values are in base units. Convert to human amounts with token decimals.
const response = await fetchWithPayment(
`${API_URL}/balance/${encodeURIComponent(walletPublicKey)}`,
{ method: "GET" },
);
const balances = await response.json();
POST /deposit
Content-Type: application/json
Builds an unsigned deposit transaction.
amount must be base units (example: 10_000_000 = 10 USDC).
const response = await fetchWithPayment(`${API_URL}/deposit`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
amount: 10_000_000, // 10 USDC (6 decimals)
user_key: walletPublicKey,
strategy_id: STRATEGY_ID,
base_asset: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
}),
});
const txString = await response.text(); // encoded unsigned transaction
POST /withdraw
Content-Type: application/json
Builds an unsigned withdrawal transaction. Supports optional WSOL handling flags.
amount must be base units.
const response = await fetchWithPayment(`${API_URL}/withdraw`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
amount: 5_000_000,
user_key: walletPublicKey,
strategy_id: STRATEGY_ID,
base_asset: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
all: false,
exclude_fees: true, // always recommended
// For wrapped SOL withdrawals only:
// unwrap_wsol_ata: true, // unwrap WSOL to native SOL
// create_wsol_ata: true, // create WSOL ATA if needed
// detect_wsol_ata: true, // auto-detect WSOL ATA existence
}),
});
const txString = await response.text();
Withdraw parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | number | yes | Amount in base units |
user_key | string | yes | User's Solana public key |
strategy_id | string | yes | Breeze strategy ID (default: 43620ba3-354c-456b-aa3c-5bf7fa46a6d4, or any valid strategy) |
base_asset | string | yes | Token mint address |
all | boolean | no | Withdraw entire position |
exclude_fees | boolean | no | Exclude fees from amount (recommended: true) |
unwrap_wsol_ata | boolean | no | Unwrap WSOL to native SOL after withdraw |
create_wsol_ata | boolean | no | Create WSOL ATA if it doesn't exist |
detect_wsol_ata | boolean | no | Auto-detect WSOL ATA and set flags accordingly |
WSOL handling: when withdrawing WSOL (So11111111111111111111111111111111111111112), pass unwrap_wsol_ata: true to receive native SOL.
GET /healthz
Free endpoint (no x402 payment required). Returns {"status":"ok"} when the service is running. Use this to verify connectivity before making paid requests.
Copy a checklist into your working notes and mark each step complete.
Task Progress:
wallet public key inputGET /balance/:fund_user with URL-encoded wallet keyresponse.ok; if not, capture status/body and stopTask Progress:
floor(amount * 10^decimals))POST /deposit with validated payloadresponse.ok; if not, capture status/body and stopTask Progress:
all=trueexclude_fees: true unless user asks otherwiseunwrap_wsol_ata: truePOST /withdraw with validated payloadresponse.ok; if not, capture status/body and stopDeposit and withdraw return encoded unsigned transactions. Normalize then sign/send:
import { VersionedTransaction, Transaction } from "@solana/web3.js";
function extractTransactionString(responseText: string): string {
const trimmed = responseText.trim();
try {
const parsed = JSON.parse(trimmed);
if (typeof parsed === "string") return parsed;
throw new Error("expected transaction string");
} catch (e) {
if (e instanceof SyntaxError) return trimmed;
throw e;
}
}
async function signAndSend(txString: string) {
const bytes = Uint8Array.from(Buffer.from(txString, "base64"));
// Try versioned transaction first, then legacy
try {
const tx = VersionedTransaction.deserialize(bytes);
tx.sign([keypair]);
const sig = await connection.sendRawTransaction(tx.serialize());
await connection.confirmTransaction(sig, "confirmed");
return sig;
} catch {
const tx = Transaction.from(bytes);
tx.partialSign(keypair);
const sig = await connection.sendRawTransaction(tx.serialize());
await connection.confirmTransaction(sig, "confirmed");
return sig;
}
}
Validation loop:
VersionedTransaction path.Transaction path.400 errors: payload issue. Re-check required fields and amount positivity.401/403: wallet/payment authorization issue. Verify wallet and x402 payment capability.402: payment challenge not satisfied. Re-run request through wrapped fetch and do not bypass payment handler.500+: upstream or proxy issue. Retry once with short backoff, then report failure.deserialize, sign, send, or confirm).For successful deposit/withdraw, return:
deposit or withdraw)https://solscan.io/tx/{sig})For balance, return:
| Token | Mint | Decimals |
|---|---|---|
| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | 6 |
| USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB | 6 |
| USDS | USDSwr9ApdHk5bvJKMjzff41FfuX8bSxdKcR81vTwcA | 6 |
| SOL | So11111111111111111111111111111111111111112 | 9 |
| JitoSOL | J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn | 9 |
| mSOL | mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So | 9 |
| JupSOL | jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v | 9 |
| JLP | 27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4 | 6 |
| Variable | Required | Default | Description |
|---|---|---|---|
WALLET_PRIVATE_KEY | yes | — | Base58-encoded Solana private key |
STRATEGY_ID | no | 43620ba3-354c-456b-aa3c-5bf7fa46a6d4 | Breeze strategy ID — any valid strategy ID works |
X402_API_URL | no | https://x402.breeze.baby | x402 payment API URL |
SOLANA_RPC_URL | no | https://api.mainnet-beta.solana.com | Solana RPC endpoint |
BASE_ASSET | no | USDC mint | Default token mint for operations |
This skill sends requests to:
https://x402.breeze.baby — Breeze x402 payment-gated API (deposits, withdrawals, balances)https://api.mainnet-beta.solana.com — Solana RPC (transaction signing and broadcasting)This skill requires WALLET_PRIVATE_KEY as an environment variable for signing Solana transactions and x402 USDC micropayments. The key is read from the process environment at runtime and is never logged, printed, or returned in output. By using this skill, small USDC micropayments are sent from your wallet to gate API access. Only install this skill if you trust Breeze with your wallet's signing capability for USDC transactions.
See agent-using-x402 for a full TypeScript implementation.