High-level DeFi actions for Solana with a simple, composable API. Uses pluggable adapters to avoid vendor lock-in.
pnpm install @pipeit/actions @pipeit/core @solana/kitimport { pipe } from '@pipeit/actions';
import { jupiter } from '@pipeit/actions/adapters';
import { createSolanaRpc, createSolanaRpcSubscriptions } from '@solana/kit';
const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com');
// Swap SOL for USDC using Jupiter
const result = await pipe({
rpc,
rpcSubscriptions,
signer,
adapters: { swap: jupiter() },
})
.swap({
inputMint: 'So11111111111111111111111111111111111111112', // SOL
outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
amount: 10_000_000n, // 0.1 SOL
slippageBps: 50, // 0.5%
})
.execute();
console.log('Transaction:', result.signature);The pipe() function creates a fluent builder for composing DeFi actions into atomic transactions.
interface PipeConfig {
rpc: Rpc<ActionsRpcApi>;
rpcSubscriptions: RpcSubscriptions<ActionsRpcSubscriptionsApi>;
signer: TransactionSigner;
adapters?: {
swap?: SwapAdapter;
};
priorityFee?: PriorityFeeLevel | PriorityFeeConfig;
computeUnits?: 'auto' | number;
autoRetry?: boolean | { maxAttempts: number; backoff: 'linear' | 'exponential' };
logLevel?: 'silent' | 'minimal' | 'verbose';
}pipe({ rpc, rpcSubscriptions, signer, adapters: { swap: jupiter() } }).swap({
inputMint: 'So11111111111111111111111111111111111111112',
outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount: 10_000_000n,
slippageBps: 50, // Optional, default: 50 (0.5%)
});pipe({ rpc, rpcSubscriptions, signer }).add(async ctx => ({
instructions: [myCustomInstruction],
computeUnits: 200_000, // Optional hint
addressLookupTableAddresses: ['...'], // Optional ALT addresses
data: { custom: 'data' }, // Optional metadata
}));// Basic execution
const result = await pipe({ rpc, rpcSubscriptions, signer, adapters: { swap: jupiter() } })
.swap({ ... })
.execute();
console.log('Signature:', result.signature);
console.log('Action results:', result.actionResults);
// With options
const result = await pipe({ rpc, rpcSubscriptions, signer, adapters: { swap: jupiter() } })
.swap({ ... })
.execute({
commitment: 'confirmed',
abortSignal: abortController.signal
});Test action sequences before execution:
const simulation = await pipe({ rpc, rpcSubscriptions, signer, adapters: { swap: jupiter() } })
.swap({ ... })
.simulate();
if (simulation.success) {
console.log('Estimated compute units:', simulation.unitsConsumed);
console.log('Logs:', simulation.logs);
} else {
console.error('Simulation failed:', simulation.error);
}Monitor action execution progress:
pipe({ rpc, rpcSubscriptions, signer, adapters: { swap: jupiter() } })
.swap({ ... })
.onActionStart((index) => console.log(`Starting action ${index}`))
.onActionComplete((index, result) => {
console.log(`Action ${index} completed with ${result.instructions.length} instructions`);
})
.onActionError((index, error) => {
console.error(`Action ${index} failed:`, error);
})
.execute();All actions in a pipe execute atomically in a single transaction:
const result = await pipe({ rpc, rpcSubscriptions, signer, adapters: { swap: jupiter() } })
.swap({ inputMint: SOL, outputMint: USDC, amount: 10_000_000n })
.add(async ctx => ({
instructions: [transferInstruction],
}))
.swap({ inputMint: USDC, outputMint: BONK, amount: 5_000_000n })
.execute();Adapters provide protocol-specific implementations for actions. Pipeit includes built-in adapters and supports custom adapters.
Jupiter adapter for token swaps across all Solana DEXs:
import { jupiter } from '@pipeit/actions/adapters';
// Default configuration
const adapter = jupiter();
// Custom configuration
const adapter = jupiter({
apiUrl: 'https://lite-api.jup.ag/swap/v1', // Default
wrapAndUnwrapSol: true, // Default: auto-wrap/unwrap SOL
dynamicComputeUnitLimit: true, // Default: use Jupiter's CU estimate
prioritizationFeeLamports: 'auto', // Default: use Jupiter's fee estimate
});Configuration Options:
apiUrl- Base URL for Jupiter API (default:https://lite-api.jup.ag/swap/v1)wrapAndUnwrapSol- Automatically wrap/unwrap SOL (default:true)dynamicComputeUnitLimit- Use Jupiter's compute unit estimate (default:true)prioritizationFeeLamports- Priority fee in lamports or'auto'(default:'auto')
Implement the SwapAdapter interface:
import type { SwapAdapter, SwapParams, ActionContext } from '@pipeit/actions';
const mySwapAdapter: SwapAdapter = {
swap: (params: SwapParams) => async (ctx: ActionContext) => {
// Call your DEX API
const quote = await fetchQuote(params);
const instructions = await buildSwapInstructions(quote, ctx.signer.address);
return {
instructions,
computeUnits: 300_000, // Optional
addressLookupTableAddresses: ['...'], // Optional
data: {
inputAmount: BigInt(quote.inAmount),
outputAmount: BigInt(quote.outAmount),
priceImpactPct: quote.priceImpact,
},
};
},
};
// Use your custom adapter
pipe({ rpc, rpcSubscriptions, signer, adapters: { swap: mySwapAdapter } })
.swap({ ... })
.execute();// Preset levels
pipe({
rpc,
rpcSubscriptions,
signer,
adapters: { swap: jupiter() },
priorityFee: 'high', // none | low | medium | high | veryHigh
});
// Custom configuration
pipe({
rpc,
rpcSubscriptions,
signer,
adapters: { swap: jupiter() },
priorityFee: {
strategy: 'percentile',
percentile: 75,
},
});// Auto (collects from actions or uses default)
pipe({
rpc,
rpcSubscriptions,
signer,
adapters: { swap: jupiter() },
computeUnits: 'auto',
});
// Fixed limit
pipe({
rpc,
rpcSubscriptions,
signer,
adapters: { swap: jupiter() },
computeUnits: 400_000,
});// Default retry (3 attempts, exponential backoff)
pipe({
rpc,
rpcSubscriptions,
signer,
adapters: { swap: jupiter() },
autoRetry: true,
});
// Custom retry configuration
pipe({
rpc,
rpcSubscriptions,
signer,
adapters: { swap: jupiter() },
autoRetry: {
maxAttempts: 5,
backoff: 'exponential', // or 'linear'
},
});
// No retry
pipe({
rpc,
rpcSubscriptions,
signer,
adapters: { swap: jupiter() },
autoRetry: false,
});pipe({
rpc,
rpcSubscriptions,
signer,
adapters: { swap: jupiter() },
logLevel: 'verbose', // silent | minimal | verbose
});Actions can return address lookup table addresses, which are automatically fetched and used for transaction compression:
const result = await pipe({ rpc, rpcSubscriptions, signer, adapters: { swap: jupiter() } })
.swap({ ... })
.execute();
// Jupiter adapter automatically includes ALT addresses if needed
// Pipe fetches and applies them automaticallyimport {
NoActionsError,
NoAdapterError,
ActionExecutionError,
isNoActionsError,
isNoAdapterError,
isActionExecutionError
} from '@pipeit/actions';
try {
const result = await pipe({ rpc, rpcSubscriptions, signer, adapters: { swap: jupiter() } })
.swap({ ... })
.execute();
} catch (error) {
if (isNoActionsError(error)) {
console.error('No actions added to pipe');
} else if (isNoAdapterError(error)) {
console.error(`Adapter not configured: ${error.adapterName}`);
} else if (isActionExecutionError(error)) {
console.error(`Action ${error.actionIndex} failed:`, error.cause);
}
}Pipe- Fluent builder class
pipe- Create a new pipe instance
PipeConfig- Configuration for creating a pipePipeResult- Result from executing a pipeExecuteOptions- Options for executionPipeHooks- Lifecycle hooks
ActionContext- Context passed to actionsActionExecutor- Function that executes an actionActionFactory- Factory function for creating action executorsActionResult- Result returned by an action
SwapParams- Parameters for swap actionSwapResult- Extended result for swap actionsSwapAdapter- Interface for swap adapters
NoActionsError- No actions added to pipeNoAdapterError- Required adapter not configuredActionExecutionError- Action execution failed
PriorityFeeLevel- Priority fee level typePriorityFeeConfig- Priority fee configurationActionsRpcApi- Minimum RPC API requiredActionsRpcSubscriptionsApi- Minimum RPC subscriptions API required
MIT