Writing a Custom Provider
Community providers plug into DeFiRuntime through ProtocolProvider interfaces (SwapProvider, YieldProvider, or BridgeProvider).
1) Implement provider interface
Section titled “1) Implement provider interface”Example: swap provider implementing SwapProvider:
import type { ProviderCapability, Quote, QuoteRequest, SwapProvider, UnsignedTransaction,} from '@stendar/core';
export class MySwapProvider implements SwapProvider { readonly id = 'my-swap'; readonly chains = ['ethereum-mainnet']; readonly capabilities: ProviderCapability[] = ['swap'];
async getQuote(request: QuoteRequest): Promise<Quote> { // Call upstream API and map to runtime quote format. return { provider: this.id, inputToken: request.inputToken, outputToken: request.outputToken, inAmount: request.amount, outAmount: request.amount, priceImpactPct: 0, }; }
async buildSwapTransaction(quote: Quote, userAddress: string): Promise<UnsignedTransaction> { // Return chain-specific unsigned tx payload. return { chainType: 'evm', chain: 'ethereum-mainnet', to: '0x0000000000000000000000000000000000000000', data: '0x', value: 0n, meta: { userAddress, quoteProvider: quote.provider }, }; }}Bridge integrations follow the same pattern with BridgeProvider:
import type { BridgeProvider, BridgeQuote, BridgeQuoteRequest, BridgeResultV2, BridgeStatus, ProviderCapability, UnsignedTransaction, WalletAdapter,} from '@stendar/core';
export class MyBridgeProvider implements BridgeProvider { readonly id = 'my-bridge'; readonly chains = ['ethereum-mainnet', 'solana-mainnet']; readonly capabilities: ProviderCapability[] = ['bridge'];
async getBridgeQuote(request: BridgeQuoteRequest): Promise<BridgeQuote> { // Map upstream bridge quote into Stendar's BridgeQuote format. return {} as BridgeQuote; }
async executeBridge(_quote: BridgeQuote, _wallet: WalletAdapter): Promise<BridgeResultV2> { return { bridgeId: 'bridge-123', status: 'pending' as BridgeStatus }; }
async getBridgeStatus(_bridgeId: string): Promise<BridgeStatus> { return 'pending'; }
async buildBridgeTransaction(_quote: BridgeQuote, _userAddress: string): Promise<UnsignedTransaction> { // Return chain-specific unsigned transaction payload. return { chainType: 'evm', chain: 'ethereum-mainnet', to: '0x0000000000000000000000000000000000000000', data: '0x', value: 0n, }; }}2) Add plugin manifest
Section titled “2) Add plugin manifest”import { validatePlugin, type PluginManifest } from '@stendar/core';
const manifest: PluginManifest = { id: 'my-swap', name: 'My Swap Provider', version: '1.0.0', type: 'swap', chains: ['ethereum-mainnet'], author: 'community', description: 'Example community swap provider',};
const provider = new MySwapProvider();const validation = validatePlugin(manifest, provider);if (!validation.valid) { throw new Error(validation.errors.join('; '));}3) Register with runtime
Section titled “3) Register with runtime”import { ConsoleLogger, DeFiRuntime, PluginRegistry } from '@stendar/core';
const logger = new ConsoleLogger({ component: 'plugin' });const registry = new PluginRegistry(logger);registry.registerPlugin(manifest, provider);
const runtime = new DeFiRuntime({ chains, providers: [provider], wallet,});If you only need local wiring, you can skip registerPlugin() and pass provider instances directly in runtime config.