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.jsv1.x@solana/spl-tokenv0.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
| Network | RPC URL | Best For |
|---|---|---|
| Mainnet | https://api.mainnet-beta.solana.com | Production |
| Devnet | https://api.devnet.solana.com | Development, testing |
| Testnet | https://api.testnet.solana.com | Staging, 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
- Mainnet:
Common SPL Tokens
| Token | Mint Address | Decimals |
|---|---|---|
| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | 6 |
| USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB | 6 |
| 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
| Action | Ethereum | Solana |
|---|---|---|
| Simple transfer | ~$5-50 | ~$0.00025 |
| Token swap | ~$20-100 | ~$0.001 |
| NFT mint | ~$50-200 | ~$0.01 |
Solana vs EVM Chains
| Feature | Solana | Ethereum/Base |
|---|---|---|
| Speed | 400ms blocks | 12s (Eth), 2s (Base) |
| Fees | $0.00025 | $1-50 (Eth), $0.01 (Base) |
| Smart contracts | Rust/C | Solidity |
| Account model | Account-based | Account-based |
| Ecosystem | Growing | Largest |
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)