ワンクリックで
crypto-builder
Build custom crypto tools, protocol adapters, and DeFi strategies that plug into OpenClawnch
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Build custom crypto tools, protocol adapters, and DeFi strategies that plug into OpenClawnch
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Check eligibility for and claim token airdrops
Scan, audit, and revoke ERC-20 token approvals to manage wallet spending permissions
Set up automated server-side trading strategies (limit buys/sells) that execute on Base via Bankr
Deploy tokens on Base (Uniswap V4) or Solana (Raydium) via Bankr gas-sponsored launch API
Open leveraged long/short positions via Avantis on Base with up to 10x leverage
Access Polymarket prediction markets to search, place bets, view positions, and redeem winnings
SOC 職業分類に基づく
| name | crypto-builder |
| description | Build custom crypto tools, protocol adapters, and DeFi strategies that plug into OpenClawnch |
Build custom crypto tools, protocol adapters, and DeFi strategies that plug safely into OpenClawnch.
Use this skill when the user wants to:
Every generated tool MUST follow these rules. Non-negotiable.
Any tool that sends a transaction must go through ClawnchConnect. The tool proposes; the user's phone wallet approves.
// CORRECT — use ClawnchConnect
import { getWalletState, requireWalletClient } from '../services/walletconnect-service.js';
const wallet = requireWalletClient();
const txHash = await wallet.sendTransaction({
to: contractAddress,
data: encodedCalldata,
value: 0n,
});
// WRONG — never import or use private keys
import { privateKeyToAccount } from 'viem/accounts'; // BANNED
Never use top-level imports for SDK packages. They may not be installed.
// CORRECT — dynamic import inside function
async function execute(params: Record<string, unknown>) {
const { SomeClient } = await import('@some/sdk');
const client = new SomeClient();
}
// WRONG — top-level import blocks tool loading
import { SomeClient } from '@some/sdk'; // fails if not installed
Every write operation must validate before execution:
import { getSafetyService } from '../services/safety-service.js';
const safety = getSafetyService();
const check = await safety.preFlightCheck({
operation: 'swap',
tokenAddress,
amount,
walletAddress: state.address,
});
if (!check.safe) {
return errorResult(`Pre-flight failed: ${check.reason}`);
}
Every tool must match AgentTool from OpenClaw's plugin SDK:
import { Type, type Static } from '@sinclair/typebox';
import { jsonResult, textResult, errorResult } from '../lib/tool-helpers.js';
const ToolParams = Type.Object({
action: Type.String({ description: 'Action to perform' }),
// ... other parameters
});
export function createMyTool() {
return {
name: 'my_tool',
label: 'My Tool',
description: 'One-line description of what this tool does',
ownerOnly: true, // true for write ops, false for read-only
parameters: ToolParams,
async execute(
toolCallId: string,
params: Static<typeof ToolParams>,
) {
try {
const action = params.action;
switch (action) {
case 'read_something':
return handleRead(params);
case 'write_something':
return handleWrite(params);
default:
return errorResult(`Unknown action: ${action}`);
}
} catch (err) {
return errorResult(
`My tool error: ${err instanceof Error ? err.message : String(err)}`,
);
}
},
};
}
Always use the helper functions. They include the required details field.
// Success with structured data
return jsonResult({ balance: '1.5', token: 'ETH' });
// Success with text
return textResult('Operation completed successfully.');
// Error
return errorResult('Insufficient balance for this operation.');
import { Type, type Static } from '@sinclair/typebox';
import { jsonResult, errorResult } from '../lib/tool-helpers.js';
const Params = Type.Object({
action: Type.String({ description: 'Action: tvl, apy, positions' }),
protocol: Type.Optional(Type.String({ description: 'Protocol name' })),
pool: Type.Optional(Type.String({ description: 'Pool address or ID' })),
});
export function createProtocolInfoTool() {
return {
name: 'protocol_info',
label: 'Protocol Info',
description: 'Query DeFi protocol data: TVL, APY, positions',
ownerOnly: false, // read-only = accessible to anyone
parameters: Params,
async execute(toolCallId: string, params: Static<typeof Params>) {
try {
switch (params.action) {
case 'tvl': return handleTvl(params);
case 'apy': return handleApy(params);
case 'positions': return handlePositions(params);
default: return errorResult(`Unknown action: ${params.action}`);
}
} catch (err) {
return errorResult(`Protocol info error: ${err instanceof Error ? err.message : String(err)}`);
}
},
};
}
async function handleTvl(params: Record<string, unknown>) {
const resp = await fetch('https://api.llama.fi/v2/protocols');
const data = (await resp.json()) as any[];
// ... process and return
return jsonResult({ tvl: '1.2B', protocol: 'Aave' });
}
import { Type, type Static } from '@sinclair/typebox';
import { jsonResult, errorResult } from '../lib/tool-helpers.js';
import { getWalletState, requireWalletClient, requirePublicClient } from '../services/walletconnect-service.js';
import { getSafetyService } from '../services/safety-service.js';
const Params = Type.Object({
action: Type.String({ description: 'Action: deposit, withdraw, claim, dry_run' }),
vault: Type.Optional(Type.String({ description: 'Vault address' })),
amount: Type.Optional(Type.String({ description: 'Amount in human-readable units' })),
token: Type.Optional(Type.String({ description: 'Token symbol or address' })),
});
export function createVaultTool() {
return {
name: 'vault',
label: 'Vault Manager',
description: 'Deposit, withdraw, and claim from DeFi vaults',
ownerOnly: true, // write operations
parameters: Params,
async execute(toolCallId: string, params: Static<typeof Params>) {
try {
switch (params.action) {
case 'deposit': return handleDeposit(params);
case 'withdraw': return handleWithdraw(params);
case 'claim': return handleClaim(params);
case 'dry_run': return handleDryRun(params);
default: return errorResult(`Unknown action: ${params.action}`);
}
} catch (err) {
return errorResult(`Vault error: ${err instanceof Error ? err.message : String(err)}`);
}
},
};
}
async function handleDeposit(params: Record<string, unknown>) {
const state = getWalletState();
if (!state.address) return errorResult('No wallet connected. Use /connect first.');
const safety = getSafetyService();
const check = await safety.preFlightCheck({
operation: 'vault_deposit',
amount: params.amount as string,
walletAddress: state.address,
});
if (!check.safe) return errorResult(`Pre-flight failed: ${check.reason}`);
const wallet = requireWalletClient();
// ... encode calldata, send transaction via wallet
// The wallet will prompt the user's phone for approval
return jsonResult({
status: 'pending_approval',
message: 'Check your wallet app to approve the deposit.',
vault: params.vault,
amount: params.amount,
});
}
async function handleDryRun(params: Record<string, unknown>) {
// Simulate without executing — always safe
return jsonResult({
simulation: true,
wouldDeposit: params.amount,
estimatedGas: '0.001 ETH',
estimatedApy: '4.2%',
});
}
import { Type, type Static } from '@sinclair/typebox';
import { jsonResult, errorResult } from '../lib/tool-helpers.js';
const Params = Type.Object({
action: Type.String({ description: 'Action: create, list, cancel, status, dry_run' }),
strategy: Type.Optional(Type.String({ description: 'Strategy type: dca, rebalance' })),
token_buy: Type.Optional(Type.String({ description: 'Token to buy' })),
token_sell: Type.Optional(Type.String({ description: 'Token to sell' })),
amount_per_period: Type.Optional(Type.String({ description: 'Amount per DCA period' })),
period: Type.Optional(Type.String({ description: 'Period: hourly, daily, weekly' })),
max_slippage_bps: Type.Optional(Type.Number({ description: 'Max slippage in basis points (default 100 = 1%)' })),
});
export function createStrategyTool() {
return {
name: 'strategy',
label: 'DeFi Strategy',
description: 'Create and manage automated DeFi strategies (DCA, rebalancing)',
ownerOnly: true,
parameters: Params,
async execute(toolCallId: string, params: Static<typeof Params>) {
// Every strategy creation should include a dry_run by default
// to show the user what would happen before committing
try {
switch (params.action) {
case 'create': return handleCreate(params);
case 'dry_run': return handleDryRun(params);
case 'list': return handleList();
case 'cancel': return handleCancel(params);
default: return errorResult(`Unknown action: ${params.action}`);
}
} catch (err) {
return errorResult(`Strategy error: ${err instanceof Error ? err.message : String(err)}`);
}
},
};
}
Before registering any custom tool, verify:
privateKeyToAccount, mnemonicToAccount, or similar importsrequireWalletClient() (goes through ClawnchConnect)type(uint256).max without user consent)dry_run action available for simulationawait import(), not top-level)Add to extensions/crypto/index.ts:
import { createMyTool } from './src/tools/my-tool.js';
// In register():
api.registerTool(createMyTool());
Then rebuild:
pnpm build:ext
import { describe, it, expect, vi } from 'vitest';
import { createMyTool } from '../extensions/crypto/src/tools/my-tool.js';
// Mock SDK at module level
vi.mock('@some/sdk', () => ({
SomeClient: vi.fn().mockImplementation(() => ({
getData: vi.fn().mockResolvedValue({ value: '42' }),
})),
}));
describe('my_tool', () => {
const tool = createMyTool();
it('has correct shape', () => {
expect(tool.name).toBe('my_tool');
expect(tool.execute).toBeTypeOf('function');
expect(tool.parameters).toBeDefined();
});
it('handles read action', async () => {
const result = await tool.execute('test', { action: 'read_something' });
expect(result.content).toBeDefined();
expect(result.details).toBeDefined();
});
it('returns error when no wallet for write action', async () => {
const result = await tool.execute('test', { action: 'write_something' });
expect(result.content[0]!.text).toContain('wallet');
});
});