From a4cbc7570519602a6b73afc1b9e93586700304fe Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 14 Feb 2026 01:34:36 +0900 Subject: [PATCH 1/3] feat: add PumpClaw action provider Add comprehensive PumpClaw action provider for Base mainnet with the following actions: - create_token: Create new tokens with Uniswap V4 liquidity (FREE, 0 ETH) - get_token_info: Get detailed token information from factory registry - list_tokens: List all tokens created on PumpClaw - buy_token: Buy tokens with ETH via SwapRouter - sell_token: Sell tokens for ETH via SwapRouter (with automatic approval) - set_image_url: Update token image (creator only) Key features: - FREE deployment (0 ETH cost) - 80% creator fees on all trades - LP locked forever (cannot rug) - Built on Uniswap V4 Factory: 0xe5bCa0eDe9208f7Ee7FCAFa0415Ca3DC03e16a90 SwapRouter: 0x3A9c65f4510de85F1843145d637ae895a2Fe04BE Includes: - Comprehensive test coverage with Jest mocks - Proper Zod schemas with validation - Error handling for all edge cases - Network support: Base mainnet only --- .../agentkit/src/action-providers/index.ts | 1 + .../src/action-providers/pumpclaw/README.md | 54 ++ .../action-providers/pumpclaw/constants.ts | 196 ++++++++ .../src/action-providers/pumpclaw/index.ts | 2 + .../pumpclaw/pumpclawActionProvider.test.ts | 359 ++++++++++++++ .../pumpclaw/pumpclawActionProvider.ts | 462 ++++++++++++++++++ .../src/action-providers/pumpclaw/schemas.ts | 145 ++++++ 7 files changed, 1219 insertions(+) create mode 100644 typescript/agentkit/src/action-providers/pumpclaw/README.md create mode 100644 typescript/agentkit/src/action-providers/pumpclaw/constants.ts create mode 100644 typescript/agentkit/src/action-providers/pumpclaw/index.ts create mode 100644 typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.test.ts create mode 100644 typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts create mode 100644 typescript/agentkit/src/action-providers/pumpclaw/schemas.ts diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 7f2b0233a..6d577c64f 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -19,6 +19,7 @@ export * from "./farcaster"; export * from "./jupiter"; export * from "./messari"; export * from "./pyth"; +export * from "./pumpclaw"; export * from "./moonwell"; export * from "./morpho"; export * from "./opensea"; diff --git a/typescript/agentkit/src/action-providers/pumpclaw/README.md b/typescript/agentkit/src/action-providers/pumpclaw/README.md new file mode 100644 index 000000000..f00bcda0f --- /dev/null +++ b/typescript/agentkit/src/action-providers/pumpclaw/README.md @@ -0,0 +1,54 @@ +# PumpClaw Action Provider + +This directory contains the **PumpclawActionProvider** implementation, which provides actions to interact with the **PumpClaw protocol** on Base mainnet. + +## Directory Structure + +``` +pumpclaw/ +├── pumpclawActionProvider.ts # Main provider with PumpClaw functionality +├── pumpclawActionProvider.test.ts # Test file for PumpClaw provider +├── constants.ts # PumpClaw contract constants and ABIs +├── schemas.ts # PumpClaw action schemas +├── index.ts # Main exports +└── README.md # This file +``` + +## Actions + +- `create_token`: Create a new token via PumpClaw factory with Uniswap V4 liquidity +- `get_token_info`: Get detailed information about a PumpClaw token +- `list_tokens`: List all tokens created on PumpClaw +- `buy_token`: Buy tokens with ETH via SwapRouter +- `sell_token`: Sell tokens for ETH via SwapRouter +- `set_image_url`: Update token image (creator only) + +## Adding New Actions + +To add new PumpClaw actions: + +1. Define your action schema in `schemas.ts` +2. Implement the action in `pumpclawActionProvider.ts` +3. Add tests in `pumpclawActionProvider.test.ts` + +## Network Support + +The PumpClaw provider supports Base mainnet only. + +## PumpClaw Advantages + +- **FREE deployment**: 0 ETH cost to create tokens +- **80% creator fees**: Creators earn 80% of all trading fees +- **LP locked forever**: Liquidity cannot be rugged +- **Uniswap V4**: Built on the latest Uniswap infrastructure + +## Contract Addresses + +- **Factory**: `0xe5bCa0eDe9208f7Ee7FCAFa0415Ca3DC03e16a90` (Base mainnet) +- **SwapRouter**: `0x3A9c65f4510de85F1843145d637ae895a2Fe04BE` (Base mainnet) + +## Notes + +PumpClaw is a free token launcher that uses Uniswap V4 to provide deep liquidity and fair token launches. The protocol is designed to prevent rug pulls by locking liquidity forever and rewarding creators with a significant share of trading fees. + +For more information on the **PumpClaw protocol**, visit [PumpClaw](https://pumpclaw.fun). diff --git a/typescript/agentkit/src/action-providers/pumpclaw/constants.ts b/typescript/agentkit/src/action-providers/pumpclaw/constants.ts new file mode 100644 index 000000000..c169721b1 --- /dev/null +++ b/typescript/agentkit/src/action-providers/pumpclaw/constants.ts @@ -0,0 +1,196 @@ +import type { Abi } from "abitype"; + +export const SUPPORTED_NETWORKS = ["base-mainnet"]; + +/** + * PumpClaw Factory contract ABI - minimal functions needed for PumpClaw interactions. + */ +export const PUMPCLAW_FACTORY_ABI: Abi = [ + { + type: "function", + name: "createToken", + inputs: [ + { name: "name", type: "string", internalType: "string" }, + { name: "symbol", type: "string", internalType: "string" }, + { name: "imageUrl", type: "string", internalType: "string" }, + { name: "totalSupply", type: "uint256", internalType: "uint256" }, + { name: "initialFdv", type: "uint256", internalType: "uint256" }, + { name: "creator", type: "address", internalType: "address" }, + ], + outputs: [{ name: "token", type: "address", internalType: "address" }], + stateMutability: "payable", + }, + { + type: "function", + name: "getTokenInfo", + inputs: [{ name: "token", type: "address", internalType: "address" }], + outputs: [ + { name: "name", type: "string", internalType: "string" }, + { name: "symbol", type: "string", internalType: "string" }, + { name: "imageUrl", type: "string", internalType: "string" }, + { name: "totalSupply", type: "uint256", internalType: "uint256" }, + { name: "creator", type: "address", internalType: "address" }, + { name: "pool", type: "address", internalType: "address" }, + { name: "createdAt", type: "uint256", internalType: "uint256" }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getTokenCount", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "getTokens", + inputs: [ + { name: "offset", type: "uint256", internalType: "uint256" }, + { name: "limit", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "address[]", internalType: "address[]" }], + stateMutability: "view", + }, + { + type: "function", + name: "setImageUrl", + inputs: [ + { name: "token", type: "address", internalType: "address" }, + { name: "imageUrl", type: "string", internalType: "string" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, +] as const; + +/** + * PumpClaw SwapRouter contract ABI - minimal functions needed for swap interactions. + */ +export const PUMPCLAW_SWAPROUTER_ABI: Abi = [ + { + type: "function", + name: "buyTokens", + inputs: [ + { name: "token", type: "address", internalType: "address" }, + { name: "minTokensOut", type: "uint256", internalType: "uint256" }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "sellTokens", + inputs: [ + { name: "token", type: "address", internalType: "address" }, + { name: "tokensIn", type: "uint256", internalType: "uint256" }, + { name: "minEthOut", type: "uint256", internalType: "uint256" }, + ], + outputs: [], + stateMutability: "nonpayable", + }, +] as const; + +/** + * ERC20 token ABI - standard functions needed for token interactions. + */ +export const ERC20_ABI: Abi = [ + { + type: "function", + name: "symbol", + inputs: [], + outputs: [{ name: "", type: "string", internalType: "string" }], + stateMutability: "view", + }, + { + type: "function", + name: "decimals", + inputs: [], + outputs: [{ name: "", type: "uint8", internalType: "uint8" }], + stateMutability: "view", + }, + { + type: "function", + name: "totalSupply", + inputs: [], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "balanceOf", + inputs: [{ name: "account", type: "address", internalType: "address" }], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "approve", + inputs: [ + { name: "spender", type: "address", internalType: "address" }, + { name: "amount", type: "uint256", internalType: "uint256" }, + ], + outputs: [{ name: "", type: "bool", internalType: "bool" }], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "allowance", + inputs: [ + { name: "owner", type: "address", internalType: "address" }, + { name: "spender", type: "address", internalType: "address" }, + ], + outputs: [{ name: "", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, +] as const; + +/** + * Contract addresses on Base mainnet. + */ +export const PUMPCLAW_CONTRACT_ADDRESSES = { + "base-mainnet": { + Factory: "0xe5bCa0eDe9208f7Ee7FCAFa0415Ca3DC03e16a90", + SwapRouter: "0x3A9c65f4510de85F1843145d637ae895a2Fe04BE", + }, +} as const; + +/** + * Gets the PumpClaw Factory contract address for the specified network. + * + * @param network - The network ID to get the contract address for. + * @returns The contract address for the specified network. + * @throws Error if the specified network is not supported. + */ +export function getFactoryAddress(network: string): string { + const addresses = + PUMPCLAW_CONTRACT_ADDRESSES[ + network.toLowerCase() as keyof typeof PUMPCLAW_CONTRACT_ADDRESSES + ]; + if (!addresses) { + throw new Error( + `Unsupported network: ${network}. Supported: ${Object.keys(PUMPCLAW_CONTRACT_ADDRESSES).join(", ")}`, + ); + } + return addresses.Factory; +} + +/** + * Gets the PumpClaw SwapRouter contract address for the specified network. + * + * @param network - The network ID to get the contract address for. + * @returns The contract address for the specified network. + * @throws Error if the specified network is not supported. + */ +export function getSwapRouterAddress(network: string): string { + const addresses = + PUMPCLAW_CONTRACT_ADDRESSES[ + network.toLowerCase() as keyof typeof PUMPCLAW_CONTRACT_ADDRESSES + ]; + if (!addresses) { + throw new Error( + `Unsupported network: ${network}. Supported: ${Object.keys(PUMPCLAW_CONTRACT_ADDRESSES).join(", ")}`, + ); + } + return addresses.SwapRouter; +} diff --git a/typescript/agentkit/src/action-providers/pumpclaw/index.ts b/typescript/agentkit/src/action-providers/pumpclaw/index.ts new file mode 100644 index 000000000..8b00d3cd9 --- /dev/null +++ b/typescript/agentkit/src/action-providers/pumpclaw/index.ts @@ -0,0 +1,2 @@ +export * from "./schemas"; +export * from "./pumpclawActionProvider"; diff --git a/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.test.ts b/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.test.ts new file mode 100644 index 000000000..07662321d --- /dev/null +++ b/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.test.ts @@ -0,0 +1,359 @@ +import { EvmWalletProvider } from "../../wallet-providers"; +import { PumpclawActionProvider } from "./pumpclawActionProvider"; +import { getFactoryAddress, getSwapRouterAddress } from "./constants"; + +describe("PumpclawActionProvider", () => { + const MOCK_TOKEN_ADDRESS = + "0x1234567890123456789012345678901234567890" as `0x${string}`; + const MOCK_POOL_ADDRESS = + "0x2345678901234567890123456789012345678901" as `0x${string}`; + const MOCK_CREATOR_ADDRESS = + "0x3456789012345678901234567890123456789012" as `0x${string}`; + const MOCK_WALLET_ADDRESS = + "0x9876543210987654321098765432109876543210" as `0x${string}`; + const MOCK_TX_HASH = "0xabcdef1234567890"; + const MOCK_TOTAL_SUPPLY = "1000000000000000000000000000"; // 1B tokens + const MOCK_INITIAL_FDV = "10000000000000000000"; // 10 ETH + + let provider: PumpclawActionProvider; + let mockWallet: jest.Mocked; + + beforeEach(() => { + mockWallet = { + getAddress: jest.fn().mockReturnValue(MOCK_WALLET_ADDRESS), + getNetwork: jest + .fn() + .mockReturnValue({ protocolFamily: "evm", networkId: "base-mainnet" }), + sendTransaction: jest + .fn() + .mockResolvedValue(MOCK_TX_HASH as `0x${string}`), + waitForTransactionReceipt: jest.fn().mockResolvedValue({}), + readContract: jest.fn(), + } as unknown as jest.Mocked; + + provider = new PumpclawActionProvider(); + }); + + describe("supportsNetwork", () => { + it("should support base-mainnet", () => { + expect( + provider.supportsNetwork({ + protocolFamily: "evm", + networkId: "base-mainnet", + }), + ).toBe(true); + }); + + it("should not support base-sepolia", () => { + expect( + provider.supportsNetwork({ + protocolFamily: "evm", + networkId: "base-sepolia", + }), + ).toBe(false); + }); + + it("should not support non-EVM networks", () => { + expect( + provider.supportsNetwork({ + protocolFamily: "bitcoin", + networkId: "base-mainnet", + }), + ).toBe(false); + }); + }); + + describe("createToken", () => { + it("should create a token successfully", async () => { + const response = await provider.createToken(mockWallet, { + name: "Test Token", + symbol: "TEST", + imageUrl: "https://example.com/image.png", + totalSupply: MOCK_TOTAL_SUPPLY, + initialFdv: MOCK_INITIAL_FDV, + }); + + expect(mockWallet.sendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + to: getFactoryAddress("base-mainnet"), + }), + ); + expect(response).toContain("Successfully created"); + expect(response).toContain("Test Token"); + expect(response).toContain("TEST"); + expect(response).toContain(MOCK_TX_HASH); + }); + + it("should handle creation failure", async () => { + mockWallet.sendTransaction.mockRejectedValue(new Error("create failed")); + + const response = await provider.createToken(mockWallet, { + name: "Test Token", + symbol: "TEST", + imageUrl: "https://example.com/image.png", + totalSupply: MOCK_TOTAL_SUPPLY, + initialFdv: MOCK_INITIAL_FDV, + }); + expect(response).toContain("Error creating"); + }); + }); + + describe("getTokenInfo", () => { + beforeEach(() => { + mockWallet.readContract.mockImplementation((params: any) => { + if (params.functionName === "getTokenInfo") { + return Promise.resolve([ + "Test Token", + "TEST", + "https://example.com/image.png", + BigInt(MOCK_TOTAL_SUPPLY), + MOCK_CREATOR_ADDRESS, + MOCK_POOL_ADDRESS, + BigInt(1640995200), + ]); + } + if (params.functionName === "decimals") { + return Promise.resolve(18); + } + return Promise.resolve(null); + }); + }); + + it("should return token information", async () => { + const response = await provider.getTokenInfo(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + }); + + expect(mockWallet.readContract).toHaveBeenCalled(); + expect(response).toContain("Test Token"); + expect(response).toContain("TEST"); + expect(response).toContain("https://example.com/image.png"); + expect(response).toContain(MOCK_CREATOR_ADDRESS); + expect(response).toContain(MOCK_POOL_ADDRESS); + }); + + it("should handle non-existent token", async () => { + mockWallet.readContract.mockImplementation((params: any) => { + if (params.functionName === "getTokenInfo") { + return Promise.resolve([ + "", + "", + "", + BigInt(0), + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + BigInt(0), + ]); + } + return Promise.resolve(null); + }); + + const response = await provider.getTokenInfo(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + }); + expect(response).toContain("not a valid PumpClaw token"); + }); + + it("should handle errors", async () => { + mockWallet.readContract.mockRejectedValue(new Error("read failed")); + + const response = await provider.getTokenInfo(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + }); + expect(response).toContain("Error getting token information"); + }); + }); + + describe("listTokens", () => { + it("should list tokens successfully", async () => { + mockWallet.readContract.mockImplementation((params: any) => { + if (params.functionName === "getTokenCount") { + return Promise.resolve(BigInt(5)); + } + if (params.functionName === "getTokens") { + return Promise.resolve([ + MOCK_TOKEN_ADDRESS, + "0x1111111111111111111111111111111111111111", + "0x2222222222222222222222222222222222222222", + ]); + } + return Promise.resolve(null); + }); + + const response = await provider.listTokens(mockWallet, { + offset: 0, + limit: 10, + }); + + expect(response).toContain("showing 3 of 5 total"); + expect(response).toContain(MOCK_TOKEN_ADDRESS); + }); + + it("should handle empty list", async () => { + mockWallet.readContract.mockImplementation((params: any) => { + if (params.functionName === "getTokenCount") { + return Promise.resolve(BigInt(0)); + } + return Promise.resolve(null); + }); + + const response = await provider.listTokens(mockWallet, { + offset: 0, + limit: 10, + }); + expect(response).toContain("No PumpClaw tokens"); + }); + + it("should handle offset beyond token count", async () => { + mockWallet.readContract.mockImplementation((params: any) => { + if (params.functionName === "getTokenCount") { + return Promise.resolve(BigInt(5)); + } + if (params.functionName === "getTokens") { + return Promise.resolve([]); + } + return Promise.resolve(null); + }); + + const response = await provider.listTokens(mockWallet, { + offset: 10, + limit: 10, + }); + expect(response).toContain("No tokens found at offset 10"); + }); + }); + + describe("buyToken", () => { + it("should buy tokens successfully", async () => { + const response = await provider.buyToken(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + ethAmount: "1000000000000000000", + minTokensOut: "0", + }); + + expect(mockWallet.sendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + to: getSwapRouterAddress("base-mainnet"), + value: BigInt("1000000000000000000"), + }), + ); + expect(response).toContain("Successfully bought"); + expect(response).toContain(MOCK_TX_HASH); + }); + + it("should handle buy failure", async () => { + mockWallet.sendTransaction.mockRejectedValue(new Error("buy failed")); + + const response = await provider.buyToken(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + ethAmount: "1000000000000000000", + minTokensOut: "0", + }); + expect(response).toContain("Error buying"); + }); + }); + + describe("sellToken", () => { + beforeEach(() => { + mockWallet.readContract.mockImplementation((params: any) => { + if (params.functionName === "balanceOf") { + return Promise.resolve(BigInt("2000000000000000000")); + } + if (params.functionName === "allowance") { + return Promise.resolve(BigInt(0)); + } + return Promise.resolve(null); + }); + }); + + it("should sell tokens successfully with approval", async () => { + const response = await provider.sellToken(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + tokensIn: "1000000000000000000", + minEthOut: "0", + }); + + expect(mockWallet.sendTransaction).toHaveBeenCalledTimes(2); // approve + sell + expect(response).toContain("Successfully sold"); + expect(response).toContain(MOCK_TX_HASH); + }); + + it("should sell tokens without approval if already approved", async () => { + mockWallet.readContract.mockImplementation((params: any) => { + if (params.functionName === "balanceOf") { + return Promise.resolve(BigInt("2000000000000000000")); + } + if (params.functionName === "allowance") { + return Promise.resolve(BigInt("2000000000000000000")); + } + return Promise.resolve(null); + }); + + const response = await provider.sellToken(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + tokensIn: "1000000000000000000", + minEthOut: "0", + }); + + expect(mockWallet.sendTransaction).toHaveBeenCalledTimes(1); // only sell + expect(response).toContain("Successfully sold"); + }); + + it("should reject when balance is insufficient", async () => { + mockWallet.readContract.mockImplementation((params: any) => { + if (params.functionName === "balanceOf") { + return Promise.resolve(BigInt("100")); + } + return Promise.resolve(null); + }); + + const response = await provider.sellToken(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + tokensIn: "1000000000000000000", + minEthOut: "0", + }); + expect(response).toContain("Insufficient balance"); + }); + + it("should handle sell failure", async () => { + mockWallet.sendTransaction.mockRejectedValue(new Error("sell failed")); + + const response = await provider.sellToken(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + tokensIn: "1000000000000000000", + minEthOut: "0", + }); + expect(response).toContain("Error selling"); + }); + }); + + describe("setImageUrl", () => { + it("should set image URL successfully", async () => { + const response = await provider.setImageUrl(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + imageUrl: "https://example.com/new-image.png", + }); + + expect(mockWallet.sendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + to: getFactoryAddress("base-mainnet"), + }), + ); + expect(response).toContain("Successfully updated"); + expect(response).toContain(MOCK_TX_HASH); + }); + + it("should handle non-creator error", async () => { + mockWallet.sendTransaction.mockRejectedValue( + new Error("Only creator can update"), + ); + + const response = await provider.setImageUrl(mockWallet, { + tokenAddress: MOCK_TOKEN_ADDRESS, + imageUrl: "https://example.com/new-image.png", + }); + expect(response).toContain("Error updating"); + expect(response).toContain("Only the token creator"); + }); + }); +}); diff --git a/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts b/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts new file mode 100644 index 000000000..0c682c821 --- /dev/null +++ b/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts @@ -0,0 +1,462 @@ +import { z } from "zod"; +import { ActionProvider } from "../actionProvider"; +import { EvmWalletProvider } from "../../wallet-providers"; +import { CreateAction } from "../actionDecorator"; +import { Network } from "../../network"; +import { + SUPPORTED_NETWORKS, + PUMPCLAW_FACTORY_ABI, + PUMPCLAW_SWAPROUTER_ABI, + ERC20_ABI, + getFactoryAddress, + getSwapRouterAddress, +} from "./constants"; +import { encodeFunctionData, formatUnits } from "viem"; +import { + PumpclawCreateTokenInput, + PumpclawGetTokenInfoInput, + PumpclawListTokensInput, + PumpclawBuyTokenInput, + PumpclawSellTokenInput, + PumpclawSetImageUrlInput, +} from "./schemas"; + +/** + * PumpclawActionProvider is an action provider for PumpClaw protocol interactions. + * + * PumpClaw is a free token launcher on Base using Uniswap V4. It allows anyone to + * create tokens with 0 ETH cost, 80% creator fees, and LP locked forever. + * + * @see https://pumpclaw.fun + */ +export class PumpclawActionProvider extends ActionProvider { + /** + * Constructor for the PumpclawActionProvider class. + */ + constructor() { + super("pumpclaw", []); + } + + /** + * Creates a new token via PumpClaw factory. + * + * @param walletProvider - The wallet provider to create the token from. + * @param args - The input arguments for the action. + * @returns A message containing the token creation details. + */ + @CreateAction({ + name: "create_token", + description: ` +This tool creates a new ERC20 token on Base via PumpClaw with Uniswap V4 liquidity. + +Inputs: +- Token name and symbol +- Image URL for the token +- Total supply (default: 1B tokens) +- Initial FDV (default: 10 ETH) +- Creator address (optional, defaults to sender) + +PumpClaw advantages: +- FREE deployment (0 ETH cost) +- 80% creator fees on all trades +- LP locked forever (cannot rug) +- Built on Uniswap V4 for deep liquidity + +Important notes: +- Amounts are in wei (no decimal points) +- Default total supply: 1,000,000,000 tokens (1B) +- Default initial FDV: 10 ETH +- Only supported on Base mainnet`, + schema: PumpclawCreateTokenInput, + }) + async createToken( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const factoryAddress = getFactoryAddress(walletProvider.getNetwork().networkId!); + const creator = args.creator || walletProvider.getAddress(); + + const createData = encodeFunctionData({ + abi: PUMPCLAW_FACTORY_ABI, + functionName: "createToken", + args: [ + args.name, + args.symbol, + args.imageUrl, + BigInt(args.totalSupply), + BigInt(args.initialFdv), + creator as `0x${string}`, + ], + }); + + const txHash = await walletProvider.sendTransaction({ + to: factoryAddress as `0x${string}`, + data: createData, + }); + + await walletProvider.waitForTransactionReceipt(txHash); + + return `Successfully created PumpClaw token "${args.name}" (${args.symbol}). + +Transaction hash: ${txHash} + +Key features: +- FREE deployment (0 ETH) +- 80% creator fees +- LP locked forever +- Built on Uniswap V4 + +The token contract address can be found in the transaction logs (TokenCreated event).`; + } catch (error) { + return `Error creating PumpClaw token: ${error}`; + } + } + + /** + * Gets detailed information about a PumpClaw token. + * + * @param walletProvider - The wallet provider to get token information from. + * @param args - The input arguments for the action. + * @returns A message containing the token information. + */ + @CreateAction({ + name: "get_token_info", + description: ` +This tool gets detailed information about a PumpClaw token on Base. + +Inputs: +- Token contract address + +Returns token details including name, symbol, image URL, total supply, +creator address, pool address, and creation timestamp. + +Important notes: +- Only works with PumpClaw tokens +- Supported on Base mainnet only`, + schema: PumpclawGetTokenInfoInput, + }) + async getTokenInfo( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const factoryAddress = getFactoryAddress(walletProvider.getNetwork().networkId!); + + const tokenInfo = (await walletProvider.readContract({ + address: factoryAddress as `0x${string}`, + abi: PUMPCLAW_FACTORY_ABI, + functionName: "getTokenInfo", + args: [args.tokenAddress as `0x${string}`], + })) as [string, string, string, bigint, string, string, bigint]; + + if (tokenInfo[6] === 0n) { + return `Error: ${args.tokenAddress} is not a valid PumpClaw token (not found in registry).`; + } + + const [name, symbol, imageUrl, totalSupply, creator, pool, createdAt] = tokenInfo; + + // Get decimals + const decimals = (await walletProvider.readContract({ + address: args.tokenAddress as `0x${string}`, + abi: ERC20_ABI, + functionName: "decimals", + args: [], + })) as number; + + const formattedSupply = formatUnits(totalSupply, decimals); + + return `Token Information for ${args.tokenAddress}: + +Name: ${name} +Symbol: ${symbol} +Image URL: ${imageUrl} +Total Supply: ${formattedSupply} ${symbol} +Creator: ${creator} +Pool Address: ${pool} +Created: ${new Date(Number(createdAt) * 1000).toISOString()} + +PumpClaw features: +- FREE deployment (0 ETH) +- 80% creator fees +- LP locked forever`; + } catch (error) { + return `Error getting token information: ${error}`; + } + } + + /** + * Lists all tokens created on PumpClaw. + * + * @param walletProvider - The wallet provider to list tokens from. + * @param args - The input arguments for the action. + * @returns A message containing the list of tokens. + */ + @CreateAction({ + name: "list_tokens", + description: ` +This tool lists all tokens created on PumpClaw. + +Inputs: +- Offset: starting index (default: 0) +- Limit: number of tokens to return (default: 10, max: 100) + +Returns a list of token contract addresses. + +Important notes: +- Tokens are returned in creation order (oldest first) +- Use offset for pagination +- Supported on Base mainnet only`, + schema: PumpclawListTokensInput, + }) + async listTokens( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const factoryAddress = getFactoryAddress(walletProvider.getNetwork().networkId!); + + const tokenCount = (await walletProvider.readContract({ + address: factoryAddress as `0x${string}`, + abi: PUMPCLAW_FACTORY_ABI, + functionName: "getTokenCount", + args: [], + })) as bigint; + + if (tokenCount === 0n) { + return "No PumpClaw tokens have been created yet."; + } + + const tokens = (await walletProvider.readContract({ + address: factoryAddress as `0x${string}`, + abi: PUMPCLAW_FACTORY_ABI, + functionName: "getTokens", + args: [BigInt(args.offset), BigInt(args.limit)], + })) as string[]; + + if (tokens.length === 0) { + return `No tokens found at offset ${args.offset}. Total token count: ${tokenCount.toString()}`; + } + + let result = `PumpClaw Tokens (showing ${tokens.length} of ${tokenCount.toString()} total):\n\n`; + + for (let i = 0; i < tokens.length; i++) { + result += `${args.offset + i + 1}. ${tokens[i]}\n`; + } + + return result; + } catch (error) { + return `Error listing tokens: ${error}`; + } + } + + /** + * Buys PumpClaw tokens with ETH via SwapRouter. + * + * @param walletProvider - The wallet provider to buy tokens with. + * @param args - The input arguments for the action. + * @returns A message containing the purchase details. + */ + @CreateAction({ + name: "buy_token", + description: ` +This tool buys PumpClaw tokens with ETH via SwapRouter on Base. +Do not use this tool for buying other types of tokens. + +Inputs: +- Token contract address +- Amount of ETH to spend (in wei) +- Minimum tokens to receive (in wei, for slippage protection) + +Important notes: +- Amounts are in wei (no decimal points). 1 ETH = 10^18 wei. +- The minTokensOut protects against slippage — the transaction reverts if received amount is less. +- 80% of fees go to the token creator +- Only supported on Base mainnet`, + schema: PumpclawBuyTokenInput, + }) + async buyToken( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const swapRouterAddress = getSwapRouterAddress( + walletProvider.getNetwork().networkId!, + ); + + const buyData = encodeFunctionData({ + abi: PUMPCLAW_SWAPROUTER_ABI, + functionName: "buyTokens", + args: [args.tokenAddress as `0x${string}`, BigInt(args.minTokensOut)], + }); + + const txHash = await walletProvider.sendTransaction({ + to: swapRouterAddress as `0x${string}`, + data: buyData, + value: BigInt(args.ethAmount), + }); + + await walletProvider.waitForTransactionReceipt(txHash); + + return `Successfully bought PumpClaw tokens. Transaction hash: ${txHash}`; + } catch (error) { + return `Error buying PumpClaw tokens: ${error}`; + } + } + + /** + * Sells PumpClaw tokens for ETH via SwapRouter. + * + * @param walletProvider - The wallet provider to sell tokens from. + * @param args - The input arguments for the action. + * @returns A message containing the sale details. + */ + @CreateAction({ + name: "sell_token", + description: ` +This tool sells PumpClaw tokens for ETH via SwapRouter on Base. +Do not use this tool for selling other types of tokens. + +Inputs: +- Token contract address +- Amount of tokens to sell (in wei) +- Minimum ETH to receive (in wei, for slippage protection) + +Important notes: +- Amounts are in wei (no decimal points). 1 token = 10^decimals wei. +- The minEthOut protects against slippage — the transaction reverts if received amount is less. +- Token approval for the SwapRouter is handled automatically. +- 80% of fees go to the token creator +- Only supported on Base mainnet`, + schema: PumpclawSellTokenInput, + }) + async sellToken( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const swapRouterAddress = getSwapRouterAddress( + walletProvider.getNetwork().networkId!, + ); + + // Check balance + const balance = (await walletProvider.readContract({ + address: args.tokenAddress as `0x${string}`, + abi: ERC20_ABI, + functionName: "balanceOf", + args: [walletProvider.getAddress() as `0x${string}`], + })) as bigint; + + if (balance < BigInt(args.tokensIn)) { + return `Error: Insufficient balance. You have ${balance.toString()} wei but are trying to sell ${args.tokensIn} wei.`; + } + + // Check and handle token approval + const allowance = (await walletProvider.readContract({ + address: args.tokenAddress as `0x${string}`, + abi: ERC20_ABI, + functionName: "allowance", + args: [ + walletProvider.getAddress() as `0x${string}`, + swapRouterAddress as `0x${string}`, + ], + })) as bigint; + + if (allowance < BigInt(args.tokensIn)) { + const approveData = encodeFunctionData({ + abi: ERC20_ABI, + functionName: "approve", + args: [swapRouterAddress as `0x${string}`, BigInt(args.tokensIn)], + }); + + const approveTxHash = await walletProvider.sendTransaction({ + to: args.tokenAddress as `0x${string}`, + data: approveData, + }); + + await walletProvider.waitForTransactionReceipt(approveTxHash); + } + + const sellData = encodeFunctionData({ + abi: PUMPCLAW_SWAPROUTER_ABI, + functionName: "sellTokens", + args: [ + args.tokenAddress as `0x${string}`, + BigInt(args.tokensIn), + BigInt(args.minEthOut), + ], + }); + + const txHash = await walletProvider.sendTransaction({ + to: swapRouterAddress as `0x${string}`, + data: sellData, + }); + + await walletProvider.waitForTransactionReceipt(txHash); + + return `Successfully sold PumpClaw tokens. Transaction hash: ${txHash}`; + } catch (error) { + return `Error selling PumpClaw tokens: ${error}`; + } + } + + /** + * Updates the image URL of a PumpClaw token (creator only). + * + * @param walletProvider - The wallet provider to update the token from. + * @param args - The input arguments for the action. + * @returns A message containing the update details. + */ + @CreateAction({ + name: "set_image_url", + description: ` +This tool updates the image URL of a PumpClaw token on Base. + +Inputs: +- Token contract address +- New image URL + +Important notes: +- Only the token creator can update the image URL +- The transaction will revert if called by a non-creator +- Only supported on Base mainnet`, + schema: PumpclawSetImageUrlInput, + }) + async setImageUrl( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const factoryAddress = getFactoryAddress(walletProvider.getNetwork().networkId!); + + const setImageData = encodeFunctionData({ + abi: PUMPCLAW_FACTORY_ABI, + functionName: "setImageUrl", + args: [args.tokenAddress as `0x${string}`, args.imageUrl], + }); + + const txHash = await walletProvider.sendTransaction({ + to: factoryAddress as `0x${string}`, + data: setImageData, + }); + + await walletProvider.waitForTransactionReceipt(txHash); + + return `Successfully updated image URL for token ${args.tokenAddress}. Transaction hash: ${txHash}`; + } catch (error) { + return `Error updating image URL: ${error}. Note: Only the token creator can update the image URL.`; + } + } + + /** + * Checks if the PumpClaw action provider supports the given network. + * + * @param network - The network to check. + * @returns True if the network is supported, false otherwise. + */ + supportsNetwork = (network: Network) => + network.protocolFamily === "evm" && + SUPPORTED_NETWORKS.includes(network.networkId!); +} + +export const pumpclawActionProvider = () => new PumpclawActionProvider(); diff --git a/typescript/agentkit/src/action-providers/pumpclaw/schemas.ts b/typescript/agentkit/src/action-providers/pumpclaw/schemas.ts new file mode 100644 index 000000000..ba1e8126c --- /dev/null +++ b/typescript/agentkit/src/action-providers/pumpclaw/schemas.ts @@ -0,0 +1,145 @@ +import { z } from "zod"; +import { isAddress } from "viem"; + +const ethereumAddress = z.custom<`0x${string}`>( + (val) => typeof val === "string" && isAddress(val), + "Invalid Ethereum address", +); + +/** + * Input schema for creating a token. + */ +export const PumpclawCreateTokenInput = z + .object({ + name: z + .string() + .min(1) + .describe("The name of the token to create (e.g., 'My Token')"), + symbol: z + .string() + .min(1) + .describe("The symbol of the token to create (e.g., 'MTK')"), + imageUrl: z + .string() + .url() + .describe("The image URL for the token (must be a valid URL)"), + totalSupply: z + .string() + .regex(/^\d+$/, "Must be a valid wei amount (no decimals)") + .default("1000000000000000000000000000") + .describe( + "Total supply in wei (default: 1B tokens = 1000000000000000000000000000)", + ), + initialFdv: z + .string() + .regex(/^\d+$/, "Must be a valid wei amount (no decimals)") + .default("10000000000000000000") + .describe( + "Initial FDV in wei (default: 10 ETH = 10000000000000000000)", + ), + creator: ethereumAddress + .optional() + .describe( + "Address of the token creator (defaults to sender if omitted)", + ), + }) + .strip() + .describe("Instructions for creating a new PumpClaw token"); + +/** + * Input schema for getting token information. + */ +export const PumpclawGetTokenInfoInput = z + .object({ + tokenAddress: ethereumAddress.describe( + "The PumpClaw token contract address to get information for", + ), + }) + .strip() + .describe("Instructions for getting PumpClaw token information"); + +/** + * Input schema for listing tokens. + */ +export const PumpclawListTokensInput = z + .object({ + offset: z + .number() + .int() + .min(0) + .default(0) + .describe("Starting index for token list (default: 0)"), + limit: z + .number() + .int() + .min(1) + .max(100) + .default(10) + .describe("Maximum number of tokens to return (default: 10, max: 100)"), + }) + .strip() + .describe("Instructions for listing PumpClaw tokens"); + +/** + * Input schema for buying tokens. + */ +export const PumpclawBuyTokenInput = z + .object({ + tokenAddress: ethereumAddress.describe( + "The PumpClaw token contract address to buy", + ), + ethAmount: z + .string() + .regex(/^\d+$/, "Must be a valid wei amount (no decimals)") + .describe("Amount of ETH to spend in wei (e.g., '1000000000000000000' for 1 ETH)"), + minTokensOut: z + .string() + .regex(/^\d+$/, "Must be a valid wei amount (no decimals)") + .default("0") + .describe( + "Minimum tokens to receive in wei (slippage protection, default: 0)", + ), + }) + .strip() + .describe("Instructions for buying PumpClaw tokens with ETH"); + +/** + * Input schema for selling tokens. + */ +export const PumpclawSellTokenInput = z + .object({ + tokenAddress: ethereumAddress.describe( + "The PumpClaw token contract address to sell", + ), + tokensIn: z + .string() + .regex(/^\d+$/, "Must be a valid wei amount (no decimals)") + .describe( + "Amount of tokens to sell in wei (e.g., '1000000000000000000' for 1 token with 18 decimals)", + ), + minEthOut: z + .string() + .regex(/^\d+$/, "Must be a valid wei amount (no decimals)") + .default("0") + .describe( + "Minimum ETH to receive in wei (slippage protection, default: 0)", + ), + }) + .strip() + .describe("Instructions for selling PumpClaw tokens for ETH"); + +/** + * Input schema for setting image URL. + */ +export const PumpclawSetImageUrlInput = z + .object({ + tokenAddress: ethereumAddress.describe( + "The PumpClaw token contract address to update", + ), + imageUrl: z + .string() + .url() + .describe("The new image URL for the token (must be a valid URL)"), + }) + .strip() + .describe("Instructions for updating PumpClaw token image URL"); From 2ac4922476052ff4c1c7d28d9b765419ea582b4a Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 14 Feb 2026 01:37:21 +0900 Subject: [PATCH 2/3] fix: correct URL to pumpclaw.com, fix export alphabetical order --- typescript/agentkit/src/action-providers/index.ts | 2 +- typescript/agentkit/src/action-providers/pumpclaw/README.md | 2 +- .../src/action-providers/pumpclaw/pumpclawActionProvider.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 6d577c64f..77dbbb13d 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -18,8 +18,8 @@ export * from "./erc721"; export * from "./farcaster"; export * from "./jupiter"; export * from "./messari"; -export * from "./pyth"; export * from "./pumpclaw"; +export * from "./pyth"; export * from "./moonwell"; export * from "./morpho"; export * from "./opensea"; diff --git a/typescript/agentkit/src/action-providers/pumpclaw/README.md b/typescript/agentkit/src/action-providers/pumpclaw/README.md index f00bcda0f..7c94110c6 100644 --- a/typescript/agentkit/src/action-providers/pumpclaw/README.md +++ b/typescript/agentkit/src/action-providers/pumpclaw/README.md @@ -51,4 +51,4 @@ The PumpClaw provider supports Base mainnet only. PumpClaw is a free token launcher that uses Uniswap V4 to provide deep liquidity and fair token launches. The protocol is designed to prevent rug pulls by locking liquidity forever and rewarding creators with a significant share of trading fees. -For more information on the **PumpClaw protocol**, visit [PumpClaw](https://pumpclaw.fun). +For more information on the **PumpClaw protocol**, visit [PumpClaw](https://pumpclaw.com). diff --git a/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts b/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts index 0c682c821..6a21ea062 100644 --- a/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts +++ b/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts @@ -27,7 +27,7 @@ import { * PumpClaw is a free token launcher on Base using Uniswap V4. It allows anyone to * create tokens with 0 ETH cost, 80% creator fees, and LP locked forever. * - * @see https://pumpclaw.fun + * @see https://pumpclaw.com */ export class PumpclawActionProvider extends ActionProvider { /** From db93d1a98be5c3c18f3a8688c07c752059f5cf1c Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 14 Feb 2026 01:40:41 +0900 Subject: [PATCH 3/3] refactor: remove promotional language, keep descriptions technical --- .../src/action-providers/pumpclaw/README.md | 11 ++----- .../pumpclaw/pumpclawActionProvider.ts | 30 ++++--------------- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/typescript/agentkit/src/action-providers/pumpclaw/README.md b/typescript/agentkit/src/action-providers/pumpclaw/README.md index 7c94110c6..fb42c8523 100644 --- a/typescript/agentkit/src/action-providers/pumpclaw/README.md +++ b/typescript/agentkit/src/action-providers/pumpclaw/README.md @@ -35,13 +35,6 @@ To add new PumpClaw actions: The PumpClaw provider supports Base mainnet only. -## PumpClaw Advantages - -- **FREE deployment**: 0 ETH cost to create tokens -- **80% creator fees**: Creators earn 80% of all trading fees -- **LP locked forever**: Liquidity cannot be rugged -- **Uniswap V4**: Built on the latest Uniswap infrastructure - ## Contract Addresses - **Factory**: `0xe5bCa0eDe9208f7Ee7FCAFa0415Ca3DC03e16a90` (Base mainnet) @@ -49,6 +42,6 @@ The PumpClaw provider supports Base mainnet only. ## Notes -PumpClaw is a free token launcher that uses Uniswap V4 to provide deep liquidity and fair token launches. The protocol is designed to prevent rug pulls by locking liquidity forever and rewarding creators with a significant share of trading fees. +PumpClaw is a token launcher on Base that uses Uniswap V4 for liquidity provisioning. Liquidity is locked at creation time and trading fees are split between the token creator and protocol. -For more information on the **PumpClaw protocol**, visit [PumpClaw](https://pumpclaw.com). +For more information, visit [pumpclaw.com](https://pumpclaw.com). diff --git a/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts b/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts index 6a21ea062..e085c3f1c 100644 --- a/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts +++ b/typescript/agentkit/src/action-providers/pumpclaw/pumpclawActionProvider.ts @@ -24,8 +24,8 @@ import { /** * PumpclawActionProvider is an action provider for PumpClaw protocol interactions. * - * PumpClaw is a free token launcher on Base using Uniswap V4. It allows anyone to - * create tokens with 0 ETH cost, 80% creator fees, and LP locked forever. + * PumpClaw is a token launcher on Base that deploys ERC20 tokens with + * full-range Uniswap V4 liquidity. Liquidity is locked at creation time. * * @see https://pumpclaw.com */ @@ -53,19 +53,14 @@ Inputs: - Token name and symbol - Image URL for the token - Total supply (default: 1B tokens) -- Initial FDV (default: 10 ETH) +- Initial FDV in ETH (default: 10 ETH) - Creator address (optional, defaults to sender) -PumpClaw advantages: -- FREE deployment (0 ETH cost) -- 80% creator fees on all trades -- LP locked forever (cannot rug) -- Built on Uniswap V4 for deep liquidity - Important notes: - Amounts are in wei (no decimal points) - Default total supply: 1,000,000,000 tokens (1B) - Default initial FDV: 10 ETH +- Liquidity is locked at creation time - Only supported on Base mainnet`, schema: PumpclawCreateTokenInput, }) @@ -101,12 +96,6 @@ Important notes: Transaction hash: ${txHash} -Key features: -- FREE deployment (0 ETH) -- 80% creator fees -- LP locked forever -- Built on Uniswap V4 - The token contract address can be found in the transaction logs (TokenCreated event).`; } catch (error) { return `Error creating PumpClaw token: ${error}`; @@ -132,7 +121,7 @@ Returns token details including name, symbol, image URL, total supply, creator address, pool address, and creation timestamp. Important notes: -- Only works with PumpClaw tokens +- Only works with tokens created via PumpClaw factory - Supported on Base mainnet only`, schema: PumpclawGetTokenInfoInput, }) @@ -174,12 +163,7 @@ Image URL: ${imageUrl} Total Supply: ${formattedSupply} ${symbol} Creator: ${creator} Pool Address: ${pool} -Created: ${new Date(Number(createdAt) * 1000).toISOString()} - -PumpClaw features: -- FREE deployment (0 ETH) -- 80% creator fees -- LP locked forever`; +Created: ${new Date(Number(createdAt) * 1000).toISOString()}`; } catch (error) { return `Error getting token information: ${error}`; } @@ -271,7 +255,6 @@ Inputs: Important notes: - Amounts are in wei (no decimal points). 1 ETH = 10^18 wei. - The minTokensOut protects against slippage — the transaction reverts if received amount is less. -- 80% of fees go to the token creator - Only supported on Base mainnet`, schema: PumpclawBuyTokenInput, }) @@ -326,7 +309,6 @@ Important notes: - Amounts are in wei (no decimal points). 1 token = 10^decimals wei. - The minEthOut protects against slippage — the transaction reverts if received amount is less. - Token approval for the SwapRouter is handled automatically. -- 80% of fees go to the token creator - Only supported on Base mainnet`, schema: PumpclawSellTokenInput, })