Skip to main content

Solana Adapter

Use SpendSafe with Solana Web3.js, the official Solana blockchain library.

Overview

Best for: Solana blockchain applications, SOL and SPL token transfers Chains: Solana Mainnet, Devnet, Testnet Bundle size: ~200kB Performance: High-speed transactions, low fees

Installation

npm install @spendsafe/sdk @solana/web3.js @solana/spl-token

Peer dependencies:

  • @solana/web3.js v1.x
  • @solana/spl-token v0.3.x (for SPL token transfers)

Basic Setup

1. Import the adapter

import { PolicyWallet, createSolanaAdapter } from '@spendsafe/sdk';
import { Keypair } from '@solana/web3.js';

2. Create the adapter

// From private key (Uint8Array of 64 bytes)
const secretKey = Uint8Array.from(JSON.parse(process.env.SOLANA_PRIVATE_KEY!));
const keypair = Keypair.fromSecretKey(secretKey);

const adapter = await createSolanaAdapter(
keypair,
process.env.SOLANA_RPC_URL! // e.g., https://api.devnet.solana.com
);

3. Wrap with PolicyWallet

const wallet = new PolicyWallet(adapter, {
apiKey: process.env.SPENDSAFE_API_KEY,
});

4. Send transactions

// Native SOL transfer
await wallet.send({
to: 'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK',
amount: '1000000000', // 1 SOL in lamports (1 SOL = 1e9 lamports)
});

// SPL token transfer (e.g., USDC on Solana)
await wallet.send({
to: 'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK',
amount: '1000000', // 1 USDC (6 decimals)
asset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC SPL token mint
});

Configuration

Environment Variables

# Required
SOLANA_PRIVATE_KEY=[1,2,3,...] # Keypair secret key as JSON array
SOLANA_RPC_URL=https://... # Your Solana RPC endpoint
SPENDSAFE_API_KEY=sk_... # Your SpendSafe API key

# Optional
SOLANA_NETWORK=devnet # mainnet-beta, devnet, testnet

Network Configuration

// Mainnet
const adapter = await createSolanaAdapter(
keypair,
'https://api.mainnet-beta.solana.com'
);

// Devnet (recommended for testing)
const adapter = await createSolanaAdapter(
keypair,
'https://api.devnet.solana.com'
);

// Testnet
const adapter = await createSolanaAdapter(
keypair,
'https://api.testnet.solana.com'
);

// Custom RPC (e.g., Helius, QuickNode)
const adapter = await createSolanaAdapter(
keypair,
'https://rpc.helius.xyz/?api-key=YOUR-KEY'
);

Keypair Setup

From secret key array:

import { Keypair } from '@solana/web3.js';

const secretKey = Uint8Array.from(JSON.parse(process.env.SOLANA_PRIVATE_KEY!));
const keypair = Keypair.fromSecretKey(secretKey);

From seed phrase (mnemonic):

import { Keypair } from '@solana/web3.js';
import { derivePath } from 'ed25519-hd-key';
import * as bip39 from 'bip39';

const mnemonic = process.env.SOLANA_SEED_PHRASE!;
const seed = await bip39.mnemonicToSeed(mnemonic);
const derivedSeed = derivePath("m/44'/501'/0'/0'", seed.toString('hex')).key;
const keypair = Keypair.fromSeed(derivedSeed);

Generate new keypair (development only):

import { Keypair } from '@solana/web3.js';

const keypair = Keypair.generate();
console.log('Public key:', keypair.publicKey.toBase58());
console.log('Secret key:', JSON.stringify(Array.from(keypair.secretKey)));

Complete Example

import { PolicyWallet, createSolanaAdapter } from '@spendsafe/sdk';
import { Keypair } from '@solana/web3.js';
import * as dotenv from 'dotenv';

dotenv.config();

async function main() {
// 1. Create Solana keypair
const secretKey = Uint8Array.from(
JSON.parse(process.env.SOLANA_PRIVATE_KEY!)
);
const keypair = Keypair.fromSecretKey(secretKey);

// 2. Create Solana adapter
const adapter = await createSolanaAdapter(
keypair,
process.env.SOLANA_RPC_URL!
);

// 3. Create PolicyWallet
const wallet = new PolicyWallet(adapter, {
apiKey: process.env.SPENDSAFE_API_KEY!,
});

try {
// 4. Send SOL with policy enforcement
const result = await wallet.send({
to: 'DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK',
amount: '1000000000', // 1 SOL
});

console.log('✅ Transaction sent:', result.hash);
console.log('⛽ Transaction fee:', result.fee);
} catch (error) {
if (error.code === 'POLICY_VIOLATION') {
console.log('❌ Policy violation:', error.message);
console.log(' Reason:', error.details.reason);
console.log(' Remaining today:', error.details.remainingDaily);
} else {
throw error;
}
}
}

main();

Advanced Usage

SPL Token Transfers

// USDC transfer (6 decimals)
await wallet.send({
to: recipientAddress,
amount: '1000000', // 1 USDC
asset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC mint
});

// USDT transfer (6 decimals)
await wallet.send({
to: recipientAddress,
amount: '5000000', // 5 USDT
asset: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', // USDT mint
});

// Custom SPL token (e.g., project token with 9 decimals)
await wallet.send({
to: recipientAddress,
amount: '1000000000', // 1 token (9 decimals)
asset: 'YourTokenMintAddressHere',
});

Error Handling

import { PolicyError } from '@spendsafe/sdk';

try {
await wallet.send({ to: recipient, amount: '1000000000' });
} catch (error) {
if (error instanceof PolicyError) {
// Policy violation
console.log('Policy blocked transaction:');
console.log('- Reason:', error.details.reason);
console.log('- Daily limit:', error.details.limits.dailyLimit);
console.log('- Remaining:', error.details.counters.remainingDaily);
} else if (error.message.includes('insufficient funds')) {
// Not enough SOL balance
console.log('Insufficient SOL balance');
} else if (error.message.includes('TokenAccountNotFoundError')) {
// Recipient doesn't have token account
console.log('Recipient needs to create token account first');
} else {
// Other error (network, RPC, etc.)
console.error('Transaction failed:', error.message);
}
}

Balance Checks

// Get SOL balance
const balance = await adapter.getBalance();
console.log('Wallet balance:', balance, 'lamports');

// Convert to SOL
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
const balanceSol = Number(balance) / LAMPORTS_PER_SOL;
console.log('Balance:', balanceSol, 'SOL');

Transaction Fees

Solana transactions have low, predictable fees:

const result = await wallet.send({
to: recipient,
amount: '1000000000', // 1 SOL
});

// Fee is in lamports (typically ~5000 lamports = 0.000005 SOL)
console.log('Transaction fee:', result.fee, 'lamports');

// Convert to SOL
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
const feeSol = Number(result.fee) / LAMPORTS_PER_SOL;
console.log('Fee in SOL:', feeSol); // Usually ~0.000005 SOL

Priority Fees (Congestion)

During network congestion, add priority fees:

// Note: Priority fee configuration will be supported in future versions
// For now, transactions use standard fees

const result = await wallet.send({
to: recipient,
amount: '1000000000',
// Future: priorityFee: '10000' (for faster confirmation)
});

Multiple Networks

// Mainnet wallet
const mainnetAdapter = await createSolanaAdapter(
keypair,
'https://api.mainnet-beta.solana.com'
);
const mainnetWallet = new PolicyWallet(mainnetAdapter, { apiKey: 'key-1' });

// Devnet wallet
const devnetAdapter = await createSolanaAdapter(
keypair,
'https://api.devnet.solana.com'
);
const devnetWallet = new PolicyWallet(devnetAdapter, { apiKey: 'key-2' });

// Send on different networks
await mainnetWallet.send({ to: '...', amount: '1000000000' });
await devnetWallet.send({ to: '...', amount: '1000000000' });

Network Configuration

Solana Networks

NetworkRPC URLBest For
Mainnethttps://api.mainnet-beta.solana.comProduction
Devnethttps://api.devnet.solana.comDevelopment, testing
Testnethttps://api.testnet.solana.comStaging, pre-production

RPC Providers

Recommended providers:

  • Helius - https://www.helius.dev/

    • High reliability, dedicated infrastructure
    • URL: https://rpc.helius.xyz/?api-key=YOUR-KEY
  • QuickNode - https://www.quicknode.com/

    • Global edge network
    • URL: https://your-endpoint.solana-mainnet.quiknode.pro/
  • Triton - https://triton.one/

    • RPC optimised for trading bots
    • URL: https://your-endpoint.mainnet.rpcpool.com/
  • Public RPCs - Free but rate-limited

    • Mainnet: https://api.mainnet-beta.solana.com
    • Devnet: https://api.devnet.solana.com

Common SPL Tokens

TokenMint AddressDecimals
USDCEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v6
USDTEs9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB6
SOL(Native)9

Troubleshooting

"Invalid keypair" error

Problem: Keypair format is incorrect

Solution: Ensure keypair is Uint8Array of 64 bytes:

// ✅ Correct
const secretKey = Uint8Array.from(JSON.parse(process.env.SOLANA_PRIVATE_KEY!));
const keypair = Keypair.fromSecretKey(secretKey);

// ❌ Wrong
const keypair = Keypair.fromSecretKey(process.env.SOLANA_PRIVATE_KEY);

"Insufficient funds for rent" error

Problem: Account doesn't have enough SOL for rent-exemption

Solution: Ensure wallet has at least 0.001 SOL for account creation:

// Check SOL balance first
const balance = await adapter.getBalance();
const balanceSol = Number(balance) / LAMPORTS_PER_SOL;

if (balanceSol < 0.001) {
console.log('Need at least 0.001 SOL for transaction fees and rent');
}

"TokenAccountNotFoundError" error

Problem: Recipient doesn't have a token account for the SPL token

Solution: Recipient needs to create associated token account first:

// This is handled automatically by most wallets
// If error persists, recipient needs to:
// 1. Open Phantom/Solflare wallet
// 2. Add the token to their wallet
// 3. This creates the associated token account

"RPC request failed" error

Problem: RPC endpoint is down or rate-limited

Solution: Use reliable RPC provider:

// ✅ Good - Paid provider
const rpcUrl = 'https://rpc.helius.xyz/?api-key=YOUR-KEY';

// ❌ Risky - Public RPC (rate limits)
const rpcUrl = 'https://api.mainnet-beta.solana.com';

"Transaction simulation failed" error

Problem: Transaction would fail on-chain

Solution: Common causes:

  • Insufficient balance
  • Invalid recipient address
  • Token account not created
  • Network congestion (retry with priority fee)
// Check balance before sending
const balance = await adapter.getBalance();
console.log('Balance:', Number(balance) / LAMPORTS_PER_SOL, 'SOL');

// Verify recipient address is valid
import { PublicKey } from '@solana/web3.js';
try {
new PublicKey(recipientAddress);
} catch {
console.log('Invalid recipient address');
}

Best Practises

1. Use Devnet for development

// Development: Use Devnet
const adapter = await createSolanaAdapter(
keypair,
'https://api.devnet.solana.com'
);

// Production: Use Mainnet with reliable RPC
const adapter = await createSolanaAdapter(
keypair,
'https://rpc.helius.xyz/?api-key=YOUR-KEY'
);

2. Get free Devnet SOL

# Airdrop SOL on Devnet (for testing)
solana airdrop 1 YOUR_ADDRESS --url devnet

Or use Solana faucet: https://faucet.solana.com/

3. Handle SPL token decimals correctly

// USDC has 6 decimals
const usdcAmount = '1000000'; // 1 USDC

// SOL has 9 decimals (lamports)
const solAmount = '1000000000'; // 1 SOL

// Always check token decimals before sending

4. Monitor transaction fees

const result = await wallet.send({ to, amount });

// Log fees for monitoring
console.log('Fee:', Number(result.fee) / LAMPORTS_PER_SOL, 'SOL');

// Alert if fees spike unexpectedly
if (Number(result.fee) > 10000) {
console.warn('High transaction fee detected');
}

5. Use environment variables for secrets

// ✅ Good
const secretKey = Uint8Array.from(JSON.parse(process.env.SOLANA_PRIVATE_KEY!));

// ❌ Never hardcode
const secretKey = new Uint8Array([1, 2, 3, ...]); // DON'T DO THIS

Why Choose Solana?

Speed & Cost

  • Block time: ~400ms (250x faster than Ethereum)
  • Transaction fee: ~$0.00025 (1000x cheaper than Ethereum)
  • Throughput: 65,000 TPS theoretical

Use Cases

Perfect for:

  • High-frequency trading bots
  • Micro-transactions (gaming, social)
  • DeFi arbitrage
  • Payment processing

Less ideal for:

  • Smart contract complexity (Ethereum better)
  • Maximum decentralisation (more validators on Ethereum)

Cost Comparison

ActionEthereumSolana
Simple transfer~$5-50~$0.00025
Token swap~$20-100~$0.001
NFT mint~$50-200~$0.01

Solana vs EVM Chains

FeatureSolanaEthereum/Base
Speed400ms blocks12s (Eth), 2s (Base)
Fees$0.00025$1-50 (Eth), $0.01 (Base)
Smart contractsRust/CSolidity
Account modelAccount-basedAccount-based
EcosystemGrowingLargest

Use Solana if:

  • Speed and cost are critical
  • Building high-frequency applications
  • Targeting Solana ecosystem users

Use EVM chains if:

  • Maximum ecosystem compatibility
  • Complex smart contract interactions
  • Targeting Ethereum/Base users

Next Steps

API Reference

createSolanaAdapter()

function createSolanaAdapter(
keypair: Keypair,
rpcUrl: string
): Promise<WalletAdapter>

Parameters:

  • keypair - Solana Keypair object (from @solana/web3.js)
  • rpcUrl - Solana RPC endpoint URL

Returns: Promise resolving to WalletAdapter

Example:

import { Keypair } from '@solana/web3.js';

const secretKey = Uint8Array.from([1, 2, 3, ...]); // Your secret key
const keypair = Keypair.fromSecretKey(secretKey);

const adapter = await createSolanaAdapter(
keypair,
'https://api.devnet.solana.com'
);

Keypair Methods

// Generate new keypair
const keypair = Keypair.generate();

// From secret key (64 bytes)
const keypair = Keypair.fromSecretKey(secretKey);

// From seed (32 bytes)
const keypair = Keypair.fromSeed(seed);

// Get public key
const publicKey = keypair.publicKey.toBase58();

// Get secret key
const secretKey = keypair.secretKey; // Uint8Array(64)