Home Getting Started Adapter SDK guide

Adapter SDK guide

Last updated on May 16, 2026

Adapter SDK guide

Build a typed source adapter in a day. This guide walks through writing a new connector for OrangeRails so end users can connect to your service.

OrangeRails ships with 102 source adapters out of the box: 4 native (Strike, BTCPay, Blink, Bitcoin xpub via Stealth Sync) plus 98 exchanges via the CCXT bridge. If your service isn't in that list, write an adapter and it will be.


What an adapter does

An adapter is one TypeScript module that:

  1. Declares its slug (the value stored in the connections.provider_type column).
  2. Declares its credential shape (what fields the OR Connect widget should render: API key, secret, OAuth token, xpub, etc.).
  3. Implements discoverWallets — pure pass-through that returns the wallets the credential can see. No DB writes.
  4. Implements syncByWallets — fetches the latest transactions for the wallets the user selected, returns them in the canonical NormalizedTransaction shape.

The edge functions handle everything else: auth, persistence, encryption at rest, scheduling, dedup. Your adapter just speaks upstream.


The interface

// supabase/functions/_shared/providers/types.ts
export interface ProviderAdapter {
  slug: string;
  displayName: string;
  description: string;
  category: 'lightning_wallet' | 'on_chain_wallet' | 'payment_processor'
          | 'exchange' | 'card' | 'mining' | 'bank' | 'lender';
  tags?: string[];
  popularity?: number;
  multiWallet: boolean;
  credentialFields: CredentialField[];

  discoverWallets(credentials: Record<string, unknown>): Promise<DiscoveredWallet[]>;

  syncByWallets(
    credentials: Record<string, unknown>,
    walletIds: string[],
    cursor: string | undefined,
  ): Promise<SyncResult>;
}

NormalizedTransaction (what your adapter returns):

{
  id: string;                          // provider-side stable id (dedup key)
  adapter: string;                     // matches your slug
  direction: 'in' | 'out';
  type: 'lightning' | 'onchain' | 'trade' | 'deposit' | 'withdrawal' | 'fee';
  amount_sats?: number;                // when source is BTC-denominated
  amount?: number;                     // when source is USD/EUR/etc
  currency?: string;
  description?: string | null;
  counterparty?: string | null;
  status?: string;
  timestamp: string;                   // ISO 8601
  external_payload?: Record<string, unknown>;  // raw provider object, opaque
}

Walk through: Strike adapter

The simplest real adapter in the repo. ~150 lines.

supabase/functions/_shared/providers/strike.ts:

  • Declares slug: 'strike', category: 'payment_processor'.
  • Credential is a single API key with read-only scopes (partner.account.profile.read + partner.invoice.read) generated at dashboard.strike.me.
  • discoverWallets returns one synthetic wallet — Strike accounts are single-account, multi-currency.
  • syncByWallets pages through GET /v1/invoices?$filter=state in ('PAID','PENDING') and created ge '<cursor>' and emits one NormalizedTransaction per PAID/PENDING invoice. UNPAID and CANCELLED skipped.

Cursor is the highest created timestamp seen in the batch. Next sync passes it as created ge '<iso>' so Strike does the filtering server-side.

Adapters must be self-contained: no DB calls, no platform-auth concerns. The edge functions handle that layer.


How to add a new adapter

1. Create the file

supabase/functions/_shared/providers/<your-slug>.ts

Implement the ProviderAdapter interface. Use strike.ts or btcpay.ts as templates.

2. Register in the dispatch table

supabase/functions/_shared/providers/dispatch.ts — import your adapter and add it to the PROVIDERS array:

import { yourAdapter } from './<your-slug>.ts';
// ...
const PROVIDERS = [
  blinkAdapter,
  xpubAdapter,
  btcpayAdapter,
  strikeAdapter,
  yourAdapter,         // ← here
  ...ccxtAdapters,
];

That's it for registration. All three edge functions (or-providers, or-discover-wallets, or-sync) auto-discover it via the registry.

3. Submit a PR

Open against dev in MorningRevolution/orangerails. CI runs bun run build + the providers stress test.


Special-case: CCXT-backed exchanges

If your target is one of the 100+ exchanges CCXT supports, you don't write an adapter at all. Add an entry to _ccxt-manifest.ts (or regenerate via scripts/generate-ccxt-manifest.mjs) and the CCXT base adapter handles it.

Three credential shapes are supported today: apiKey+secret, apiKey+password+secret, apiKey+secret+uid. Exchanges using exotic shapes (DEX wallets, signed messages) need a custom adapter.


What's coming

  • Adapter-side webhook ingestion (today everything is poll-based)
  • Adapter-side error categorization (so the UI can render "credentials expired" vs "rate limit" differently)
  • A standalone @orangerails/adapter-sdk npm package so adapters can live in their own repos and OR auto-discovers them from a manifest

These are Phase 1 (α) and Phase 2 (β) work. PRs welcome.


See also