
Why transparency alone isn’t enough for modern blockchains
Blockchain Was Built to Be Transparent and That Strength Needs a Complement

Prediction Markets as Truth Seeking Systems
How markets transform belief into measurable probability

Build a Whale-Watching On-Chain Analytics Tool with the Coinbase SQL API (Node.js Tutorial)
From concept to code — why the SQL API matters and how to use it to track the biggest transfers on Base.

Why transparency alone isn’t enough for modern blockchains
Blockchain Was Built to Be Transparent and That Strength Needs a Complement

Prediction Markets as Truth Seeking Systems
How markets transform belief into measurable probability

Build a Whale-Watching On-Chain Analytics Tool with the Coinbase SQL API (Node.js Tutorial)
From concept to code — why the SQL API matters and how to use it to track the biggest transfers on Base.
<100 subscribers
<100 subscribers


Automating on-chain actions on Solana can feel risky: one bad key leak or buggy transaction and funds are gone. Traditional guards like code reviews and unit tests help, but they don’t stop a live transaction.
Today, you’ll fix that with Coinbase Developer Platform (CDP) Server Wallets v2. We’ll create policies — API-level firewalls that allow only the transactions you intend. By the end, you’ll have a runnable TypeScript script that enforces SOL/SPL allowlists and a strict IDL-based policy for Jupiter v6 swaps.
🔐 Create policies that only allow SOL transfers to trusted addresses
Restrict SPL token transfers to approved mints (USDC)
🔎 Validate instruction data with a custom IDL (Jupiter v6 “route”)
🚀 Assemble a single script that creates all policies in one go
Dev Script (TypeScript)
│ uses
▼
CDP SDK (Policies API)
│ creates
▼
Policy Objects (scope: account)
│ enforced at
▼
Server Wallets v2 ──> Solana RuntimeCDP SDK — Programmatic entry to create/update policies.
Policies — Declarative rules (allowlists, mint restrictions, IDL checks) that gate transactions before signing.
Server Wallets v2 — Managed, programmatic wallets that must satisfy active policies.
Solana — Actual execution layer; transactions only flow if they pass the policy engine.
You’ll need
Node.js v18+
A CDP account, API Key (id + secret), and Wallet Secret from the CDP Portal
Create & init
# Create your project
mkdir solana-policy-engine-tutorial
cd solana-policy-engine-tutorial
# Initialize Node.js project
npm init -yInstall deps
npm install @coinbase/cdp-sdk dotenv typescript ts-node @types/nodeInitialize TypeScript
npx tsc --init --rootDir src --outDir dist --esModuleInterop --resolveJsonModule --module commonjs --moduleResolution nodeScaffold files
mkdir src
touch src/main.ts
touch .envAdd secrets to .env
# .env - Get these from the CDP Portal
CDP_API_KEY_ID="your_api_key_id"
CDP_API_KEY_SECRET="your_api_key_secret"
CDP_WALLET_SECRET="your_wallet_secret"Create src/main.ts:
// src/main.ts
import { CdpClient } from "@coinbase/cdp-sdk";
import dotenv from "dotenv";
dotenv.config();
const cdp = new CdpClient();
console.log("CDP Client Initialized Successfully.");
async function main() {
console.log("\n--- Starting Policy Creation Script ---");
// We'll call our policy functions here.
console.log("\n--- ✅ All Policies Created Successfully ---");
}
main().catch((error) => {
console.error("\nAn error occurred during policy creation:", error);
process.exit(1);
});Only allow SOL transfers to a trusted set of addresses.
/**
* Policy 1: SOL Address Allowlist
* This policy only permits sending SOL to a specific list of trusted addresses.
* Any attempt to send SOL elsewhere will be blocked at the API level.
*/
async function createSolAllowlistPolicy(cdp: CdpClient) {
console.log("\nCreating a SOL address allowlist policy...");
const policy = await cdp.policies.createPolicy({
policy: {
description: "Allow SOL transfers to treasury addresses",
scope: "account", // This policy will be applied to a specific account
rules: [
{
action: "accept", // Required: accept or deny
operation: "signSolTransaction", // Required: Solana transaction operation
criteria: [
{
type: "solAddress",
addresses: [
"DtdSSG8ZJRZVv5Jx7K1MeWp7Zxcu19GD5wQRGRpQ9uMF", // Valid Treasury Address 1
"HpabPRRCFbBKSuJr5PdkVvQc85FyxyTWkFM2obBRSvHT", // Valid Treasury Address 2
],
operator: "in" // The 'to' address must be IN this list
}
]
}
]
}
});
console.log(`Successfully created SOL Allowlist Policy with ID: ${policy.id}`);
return policy.id;
}Permit transfers only for approved mints (USDC shown below).
/**
* Policy 2: SPL Token Mint Restriction
* This policy only allows transfers of specific SPL tokens (USDC in this case)
* by checking the token's mint address.
*/
async function createStablecoinOnlyPolicy(cdp: CdpClient) {
console.log("\nCreating a stablecoin-only (USDC) transfer policy...");
const USDC_MINT_ADDRESS = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const policy = await cdp.policies.createPolicy({
policy: {
description: "Allow USDC SPL Token transfers only",
scope: "account",
rules: [
{
action: "accept",
operation: "signSolTransaction",
criteria: [
{
type: "mintAddress",
addresses: [USDC_MINT_ADDRESS],
operator: "in"
}
]
}
]
}
});
console.log(`Successfully created Stablecoin-Only Policy with ID: ${policy.id}`);
return policy.id;
}Validate instruction data using the program’s IDL so only intended Jupiter “route” swaps are allowed — and cap the size.
Save the Jupiter v6 IDL JSON as src/jupiter-v6-idl.json (from explorer/IDL source).
Add the policy creator:
/**
* Policy 3: Advanced Jupiter Swap Policy (solData + minimal IDL)
* - Enforces a maximum input amount for Jupiter `route` swaps
* - Validates instruction data against a minimal inline IDL built from
* `src/jupiter-v6-idl.json`
* - Effect: Only allows signing Jupiter `route` swaps when `in_amount` <= 1 SOL;
* other transactions won't satisfy this rule
*/
async function createJupiterSwapPolicy(cdp: CdpClient) {
console.log("\nCreating an advanced policy for Jupiter swaps...");
const MAX_SWAP_AMOUNT_LAMPORTS = "1000000000"; // 1 SOL (lamports, as string)
const JUPITER_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
// Build a minimal inline IDL for the `route` instruction for the API
const routeIx = (jupiterIdl.instructions as any[]).find((ix) => ix?.name === 'route');
if (!routeIx) {
throw new Error('Jupiter IDL does not include route instruction');
}
const jupiterRouteIdl = {
address: JUPITER_PROGRAM_ID,
instructions: [
{
name: 'route',
discriminator: routeIx.discriminator,
args: [
{ name: 'route_plan', type: 'vec<defined:RoutePlanStep>' },
{ name: 'in_amount', type: 'u64' },
{ name: 'quoted_out_amount', type: 'u64' },
{ name: 'slippage_bps', type: 'u16' },
{ name: 'platform_fee_bps', type: 'u8' },
],
},
],
};
const policy = await cdp.policies.createPolicy({
policy: {
description: "Allow SOL USDC swaps on Jupiter max 1 SOL",
scope: "account",
rules: [
{
action: "accept",
operation: "signSolTransaction",
criteria: [
{
type: "solData",
idls: [jupiterRouteIdl],
conditions: [
{
instruction: "route",
// Guardrail: limit maximum input amount for swaps
params: [
{ name: "in_amount", operator: "<=", value: MAX_SWAP_AMOUNT_LAMPORTS }
]
}
]
}
]
}
]
}
});
console.log(`Successfully created Jupiter Swap Policy with ID: ${policy.id}`);
return policy.id;
}Update the bottom of src/main.ts to create all policies:
/**
* Main: create three policies in sequence
*/
async function main() {
console.log("\n--- Starting Policy Creation Script ---");
try {
// Create foundational policies
await createSolAllowlistPolicy(cdp);
await createStablecoinOnlyPolicy(cdp);
// Create advanced, IDL-based policy
await createJupiterSwapPolicy(cdp);
console.log("\n--- ✅ All Policies Created Successfully ---");
} catch (error) {
console.error("\nAn error occurred during policy creation:", error);
throw error;
}
}
// Execute the main function with proper error handling
main().catch(error => {
console.error("\nFatal error in policy creation:", error);
process.exit(1);
});Run it
npx tsc && node dist/main.jsWatch the console for policy IDs. These policies now protect your account-scoped server wallet operations.
You just stood up a practical Solana policy layer: SOL allowlists, SPL mint restrictions, and a Jupiter v6 IDL policy that inspects instruction data and caps swap size. This is the backbone of safer backends — especially for bots, treasuries, and automated services.
Expand the allowlists/mints for your app
Add program-specific IDL rules for other protocols
Combine with alerting & CI to treat policies as code
Coinbase Developer Docs: https://docs.cdp.coinbase.com
Join the Coinbase Developer Discord to swap patterns and ask questions
Jupiter — (find program/IDL references via explorers)
CDP Wallets v2 Docs:
CDP SDK Github:
Demo Github:
HeimLabs:
Clap if this saved you hours, and share what you’re building with the CDP stack!
Follow HeimLabs for unapologetically practical Web3 dev content.
Twitter, LinkedIn.
Happy Building 🚀
Automating on-chain actions on Solana can feel risky: one bad key leak or buggy transaction and funds are gone. Traditional guards like code reviews and unit tests help, but they don’t stop a live transaction.
Today, you’ll fix that with Coinbase Developer Platform (CDP) Server Wallets v2. We’ll create policies — API-level firewalls that allow only the transactions you intend. By the end, you’ll have a runnable TypeScript script that enforces SOL/SPL allowlists and a strict IDL-based policy for Jupiter v6 swaps.
🔐 Create policies that only allow SOL transfers to trusted addresses
Restrict SPL token transfers to approved mints (USDC)
🔎 Validate instruction data with a custom IDL (Jupiter v6 “route”)
🚀 Assemble a single script that creates all policies in one go
Dev Script (TypeScript)
│ uses
▼
CDP SDK (Policies API)
│ creates
▼
Policy Objects (scope: account)
│ enforced at
▼
Server Wallets v2 ──> Solana RuntimeCDP SDK — Programmatic entry to create/update policies.
Policies — Declarative rules (allowlists, mint restrictions, IDL checks) that gate transactions before signing.
Server Wallets v2 — Managed, programmatic wallets that must satisfy active policies.
Solana — Actual execution layer; transactions only flow if they pass the policy engine.
You’ll need
Node.js v18+
A CDP account, API Key (id + secret), and Wallet Secret from the CDP Portal
Create & init
# Create your project
mkdir solana-policy-engine-tutorial
cd solana-policy-engine-tutorial
# Initialize Node.js project
npm init -yInstall deps
npm install @coinbase/cdp-sdk dotenv typescript ts-node @types/nodeInitialize TypeScript
npx tsc --init --rootDir src --outDir dist --esModuleInterop --resolveJsonModule --module commonjs --moduleResolution nodeScaffold files
mkdir src
touch src/main.ts
touch .envAdd secrets to .env
# .env - Get these from the CDP Portal
CDP_API_KEY_ID="your_api_key_id"
CDP_API_KEY_SECRET="your_api_key_secret"
CDP_WALLET_SECRET="your_wallet_secret"Create src/main.ts:
// src/main.ts
import { CdpClient } from "@coinbase/cdp-sdk";
import dotenv from "dotenv";
dotenv.config();
const cdp = new CdpClient();
console.log("CDP Client Initialized Successfully.");
async function main() {
console.log("\n--- Starting Policy Creation Script ---");
// We'll call our policy functions here.
console.log("\n--- ✅ All Policies Created Successfully ---");
}
main().catch((error) => {
console.error("\nAn error occurred during policy creation:", error);
process.exit(1);
});Only allow SOL transfers to a trusted set of addresses.
/**
* Policy 1: SOL Address Allowlist
* This policy only permits sending SOL to a specific list of trusted addresses.
* Any attempt to send SOL elsewhere will be blocked at the API level.
*/
async function createSolAllowlistPolicy(cdp: CdpClient) {
console.log("\nCreating a SOL address allowlist policy...");
const policy = await cdp.policies.createPolicy({
policy: {
description: "Allow SOL transfers to treasury addresses",
scope: "account", // This policy will be applied to a specific account
rules: [
{
action: "accept", // Required: accept or deny
operation: "signSolTransaction", // Required: Solana transaction operation
criteria: [
{
type: "solAddress",
addresses: [
"DtdSSG8ZJRZVv5Jx7K1MeWp7Zxcu19GD5wQRGRpQ9uMF", // Valid Treasury Address 1
"HpabPRRCFbBKSuJr5PdkVvQc85FyxyTWkFM2obBRSvHT", // Valid Treasury Address 2
],
operator: "in" // The 'to' address must be IN this list
}
]
}
]
}
});
console.log(`Successfully created SOL Allowlist Policy with ID: ${policy.id}`);
return policy.id;
}Permit transfers only for approved mints (USDC shown below).
/**
* Policy 2: SPL Token Mint Restriction
* This policy only allows transfers of specific SPL tokens (USDC in this case)
* by checking the token's mint address.
*/
async function createStablecoinOnlyPolicy(cdp: CdpClient) {
console.log("\nCreating a stablecoin-only (USDC) transfer policy...");
const USDC_MINT_ADDRESS = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const policy = await cdp.policies.createPolicy({
policy: {
description: "Allow USDC SPL Token transfers only",
scope: "account",
rules: [
{
action: "accept",
operation: "signSolTransaction",
criteria: [
{
type: "mintAddress",
addresses: [USDC_MINT_ADDRESS],
operator: "in"
}
]
}
]
}
});
console.log(`Successfully created Stablecoin-Only Policy with ID: ${policy.id}`);
return policy.id;
}Validate instruction data using the program’s IDL so only intended Jupiter “route” swaps are allowed — and cap the size.
Save the Jupiter v6 IDL JSON as src/jupiter-v6-idl.json (from explorer/IDL source).
Add the policy creator:
/**
* Policy 3: Advanced Jupiter Swap Policy (solData + minimal IDL)
* - Enforces a maximum input amount for Jupiter `route` swaps
* - Validates instruction data against a minimal inline IDL built from
* `src/jupiter-v6-idl.json`
* - Effect: Only allows signing Jupiter `route` swaps when `in_amount` <= 1 SOL;
* other transactions won't satisfy this rule
*/
async function createJupiterSwapPolicy(cdp: CdpClient) {
console.log("\nCreating an advanced policy for Jupiter swaps...");
const MAX_SWAP_AMOUNT_LAMPORTS = "1000000000"; // 1 SOL (lamports, as string)
const JUPITER_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
// Build a minimal inline IDL for the `route` instruction for the API
const routeIx = (jupiterIdl.instructions as any[]).find((ix) => ix?.name === 'route');
if (!routeIx) {
throw new Error('Jupiter IDL does not include route instruction');
}
const jupiterRouteIdl = {
address: JUPITER_PROGRAM_ID,
instructions: [
{
name: 'route',
discriminator: routeIx.discriminator,
args: [
{ name: 'route_plan', type: 'vec<defined:RoutePlanStep>' },
{ name: 'in_amount', type: 'u64' },
{ name: 'quoted_out_amount', type: 'u64' },
{ name: 'slippage_bps', type: 'u16' },
{ name: 'platform_fee_bps', type: 'u8' },
],
},
],
};
const policy = await cdp.policies.createPolicy({
policy: {
description: "Allow SOL USDC swaps on Jupiter max 1 SOL",
scope: "account",
rules: [
{
action: "accept",
operation: "signSolTransaction",
criteria: [
{
type: "solData",
idls: [jupiterRouteIdl],
conditions: [
{
instruction: "route",
// Guardrail: limit maximum input amount for swaps
params: [
{ name: "in_amount", operator: "<=", value: MAX_SWAP_AMOUNT_LAMPORTS }
]
}
]
}
]
}
]
}
});
console.log(`Successfully created Jupiter Swap Policy with ID: ${policy.id}`);
return policy.id;
}Update the bottom of src/main.ts to create all policies:
/**
* Main: create three policies in sequence
*/
async function main() {
console.log("\n--- Starting Policy Creation Script ---");
try {
// Create foundational policies
await createSolAllowlistPolicy(cdp);
await createStablecoinOnlyPolicy(cdp);
// Create advanced, IDL-based policy
await createJupiterSwapPolicy(cdp);
console.log("\n--- ✅ All Policies Created Successfully ---");
} catch (error) {
console.error("\nAn error occurred during policy creation:", error);
throw error;
}
}
// Execute the main function with proper error handling
main().catch(error => {
console.error("\nFatal error in policy creation:", error);
process.exit(1);
});Run it
npx tsc && node dist/main.jsWatch the console for policy IDs. These policies now protect your account-scoped server wallet operations.
You just stood up a practical Solana policy layer: SOL allowlists, SPL mint restrictions, and a Jupiter v6 IDL policy that inspects instruction data and caps swap size. This is the backbone of safer backends — especially for bots, treasuries, and automated services.
Expand the allowlists/mints for your app
Add program-specific IDL rules for other protocols
Combine with alerting & CI to treat policies as code
Coinbase Developer Docs: https://docs.cdp.coinbase.com
Join the Coinbase Developer Discord to swap patterns and ask questions
Jupiter — (find program/IDL references via explorers)
CDP Wallets v2 Docs:
CDP SDK Github:
Demo Github:
HeimLabs:
Clap if this saved you hours, and share what you’re building with the CDP stack!
Follow HeimLabs for unapologetically practical Web3 dev content.
Twitter, LinkedIn.
Happy Building 🚀
Share Dialog
Share Dialog
1 comment
Stop backend key leaks. Build @solana bots that only sign approved txs with @coinbasedev Wallets v2 Policies. 3 rules, 1 SOL cap, full TypeScript demo: