Coinbase CDP + OnchainKit
Two distinct things from the same vendor:
- CDP Server Wallets — keys held by our backend, so an agent can transact.
- OnchainKit — React components for Base dApps.
Portal: https://portal.cdp.coinbase.com · Docs: https://docs.cdp.coinbase.com
When CDP instead of Privy
| Need | Use |
|---|---|
| A person logs in and gets a wallet | Privy (skill) |
| An agent/backend signs autonomously | CDP server wallet |
| Base-native UI components | OnchainKit (works alongside either) |
An agent app needs CDP when its LangGraph agent books and moves USDC without a human clicking. That is the only justification for a server-held key — if a human is in the loop, use Privy.
Environment
CDP_API_KEY_ID= # SECRET
CDP_API_KEY_SECRET= # SECRET
NEXT_PUBLIC_CDP_PROJECT_ID= # public
AGENT_WALLET_ID= # SECRET — the persisted wallet id
Server wallets
import { CdpClient } from "@coinbase/cdp-sdk";
const cdp = new CdpClient({
apiKeyId: process.env.CDP_API_KEY_ID!,
apiKeySecret: process.env.CDP_API_KEY_SECRET!,
});
const account = await cdp.evm.getOrCreateAccount({ name: "rental-agent" });
const { transactionHash } = await cdp.evm.sendTransaction({
address: account.address,
network: "base-sepolia",
transaction: { to: USDC, data: encodedTransfer, value: 0n },
});
Persist the wallet id. A server wallet that is re-created on each deploy is a new
address with no funds and no history — and the old one's balance is stranded. Store
AGENT_WALLET_ID in the environment and look it up, never create blindly.
Fund the agent wallet with a float, not a treasury. It signs without human approval by definition; cap the blast radius.
AgentKit
@coinbase/agentkit + @coinbase/agentkit-langchain expose onchain actions as LLM
tools, driven by @langchain/langgraph.
const agentkit = await AgentKit.from({ walletProvider });
const tools = await getLangChainTools(agentkit);
const agent = createReactAgent({ llm, tools, checkpointSaver: memory });
Note this is the one place in the portfolio using LangChain and OpenAI — inherited from the AgentKit starter. Everything else is the Anthropic SDK directly (skill). On a rebuild, prefer Claude with native tool use over the LangChain layer; the abstraction earns little here.
Constrain the tool set. AgentKit ships transfer and trade actions by default. An
agent with an unbounded transfer tool and a funded wallet is one prompt-injection
away from being drained. Register only the actions the product needs.
OnchainKit
import { OnchainKitProvider } from "@coinbase/onchainkit";
import { base } from "viem/chains";
<OnchainKitProvider apiKey={process.env.NEXT_PUBLIC_CDP_PROJECT_ID} chain={base}>
{children}
</OnchainKitProvider>
Components worth using rather than rebuilding: <Wallet /> (connect + dropdown),
<Identity /> / <Name /> / <Avatar /> (ENS + Basename resolution),
<Swap />, <Checkout />, <Transaction /> (handles the receipt-waiting and status
UI we otherwise hand-roll).
Add @farcaster/miniapp-wagmi-connector to run as a Base App /
Farcaster miniapp — the connector replaces the normal wallet connection when the app
is embedded in a Farcaster client.
OnchainKit needs its own stylesheet: import "@coinbase/onchainkit/styles.css".
Missing it renders unstyled components, which reads as broken rather than unstyled.
Gotchas
- Re-created server wallets strand funds. Persist the id.
- Unbounded agent tools + funded wallet = drainable. Constrain.
styles.cssimport for OnchainKit.- Identity components need an L1 RPC for ENS. Basenames resolve on Base;
.ethdoes not. - CDP network names are strings (
"base-sepolia"), not chain ids.