# Outblock Documentation Welcome to the Outblock documentation. Choose a project to get started. # Wallet The FlowIndex Wallet is a passkey-based, self-custody wallet for the [Flow blockchain](https://flow.com). It uses WebAuthn (passkeys) to derive cryptographic keys directly from your device's biometric sensor or security key, eliminating the need for seed phrases, private key management, or browser extensions. Core Features [#core-features] * **No seed phrases** -- Your private key material never leaves the authenticator hardware. There is nothing to write down, store, or lose. * **WebAuthn / passkey authentication** -- Leverages the W3C Web Authentication standard (FIDO2) supported by all major browsers and operating systems. * **Cross-device support** -- Passkeys synced via iCloud Keychain, Google Password Manager, or Windows Hello work seamlessly across your devices. * **FLIP-264 compatible signing** -- Transaction signatures include WebAuthn authenticator data and client data JSON as extension data, following the [FLIP-264](https://github.com/onflow/flips/pull/264) specification for passkey-based signing on Flow. * **Self-custody** -- The wallet creates a real Flow account on-chain. You hold the only signing key; no custodian or intermediary can move your funds. * **Dual-network provisioning** -- A single passkey can provision Flow accounts on both mainnet and testnet. * **FCL integration** -- A drop-in authorization function (`createPasskeyAuthz`) lets you sign Flow transactions with your passkey using the Flow Client Library. How It Works [#how-it-works] The wallet system spans three packages and a server-side edge function: | Component | Package | Role | | ----------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | WebAuthn + signing primitives | `@flowindex/flow-passkey` | Pure browser-side WebAuthn credential creation, assertion, FLIP-264 encoding, and FCL-compatible signing | | Auth core library | `@flowindex/auth-core` | JWT handling, token storage, GoTrue integration, and passkey auth client that orchestrates registration/login flows | | React UI components | `@flowindex/auth-ui` | `AuthProvider`, `useAuth`, `usePasskeyAuth` hooks, and `LoginModal` component | | Passkey auth edge function | `passkey-auth` (Supabase edge function) | Server-side WebAuthn verification using `@simplewebauthn/server`, challenge management, credential storage, and Flow account provisioning | Authentication Flow [#authentication-flow] 1. **Sign in** with GitHub, Google, email magic link, or an existing passkey. 2. **Register a passkey** -- The browser's WebAuthn API creates an ES256 (P-256) credential. The public key is stored server-side in COSE and SEC1 formats. 3. **Provision a Flow account** -- The SEC1 public key is submitted to Flow's account creation API. A new on-chain account is created with your passkey's public key as its sole signing key (weight 1000, ECDSA\_P256 + SHA2\_256). 4. **Sign transactions** -- When you send a Flow transaction, the browser prompts for biometric/PIN verification. The WebAuthn assertion signature is converted from DER to raw P-256 (r || s) format and wrapped with FLIP-264 extension data. Signing Details [#signing-details] Flow transactions are encoded using RLP with a domain tag (`FLOW-V0.0-transaction`). The encoded message is SHA-256 hashed and used as the WebAuthn challenge. The resulting signature includes: * **signature** -- 64-byte raw P-256 signature (r || s), hex-encoded * **extensionData** -- FLIP-264 format: `0x01 || RLP([authenticatorData, clientDataJSON])`, hex-encoded Tech Stack [#tech-stack] | Layer | Technology | | ---------------- | --------------------------------------------------------------------- | | Cryptography | WebAuthn (FIDO2), ES256 / ECDSA P-256, SHA-256 | | Standards | W3C Web Authentication, FLIP-264 | | Client | TypeScript, `@simplewebauthn/browser` (via `@flowindex/flow-passkey`) | | Server | Deno, `@simplewebauthn/server` v11, Supabase (GoTrue + PostgREST) | | Flow integration | `@onflow/fcl`, `@onflow/rlp`, Lilico/FRW OpenAPI for account creation | | UI | React 18+, Framer Motion, Lucide icons | Security Model [#security-model] * **Hardware-bound keys** -- The passkey private key is generated and stored inside the platform authenticator (Secure Enclave, TPM, or security key). It is never exposed to JavaScript. * **Challenge-response** -- Every registration and login uses a unique server-generated challenge with a 5-minute TTL. Challenges are single-use and deleted after verification. * **Rate limiting** -- The edge function enforces per-IP rate limits on registration and login attempts. * **Audit logging** -- All passkey operations (registration, authentication, removal) are recorded in an audit log with timestamps, IP addresses, and credential identifiers. * **Multi-origin support** -- WebAuthn verification accepts requests from any authorized subdomain under the relying party ID. # Getting Started This guide walks through integrating the FlowIndex passkey wallet into a React application. By the end, your users will be able to register passkeys, provision Flow accounts, and sign transactions with biometric authentication. Prerequisites [#prerequisites] * **Node.js 18+** or **Bun** * **React 18+** application * A running Supabase instance with GoTrue (for auth tokens) and the `passkey-auth` edge function deployed * A domain served over HTTPS (WebAuthn requires a secure context) Install Packages [#install-packages] ```bash # Using bun bun add @flowindex/auth-core @flowindex/auth-ui @flowindex/flow-passkey # Using npm npm install @flowindex/auth-core @flowindex/auth-ui @flowindex/flow-passkey ``` Peer dependencies for `@flowindex/auth-ui` (install if not already present): ```bash bun add react react-dom framer-motion lucide-react input-otp ``` Set Up the AuthProvider [#set-up-the-authprovider] Wrap your application with `AuthProvider` from `@flowindex/auth-ui`. This provides authentication state, passkey operations, and token management to all child components. ```tsx import { AuthProvider } from '@flowindex/auth-ui'; function App() { return ( ); } ``` Configuration Options [#configuration-options] | Option | Type | Description | | ----------------------- | --------- | --------------------------------------------------------------------------- | | `gotrueUrl` | `string` | Base URL of your GoTrue auth endpoint | | `passkeyAuthUrl` | `string` | URL of the `passkey-auth` edge function | | `rpId` | `string` | WebAuthn relying party ID (your domain, without protocol) | | `rpName` | `string` | Human-readable name shown in passkey prompts | | `cookieDomain` | `string` | Cookie domain for cross-subdomain session sharing (e.g., `.yourdomain.com`) | | `enableLogoutDetection` | `boolean` | Detect logout from other tabs/subdomains via cookie polling | | `enableRoles` | `boolean` | Parse role/team claims from JWT tokens (default: `true`) | | `callbackPath` | `string` | OAuth callback path or full URL (default: `/developer/callback`) | Add the Login Modal [#add-the-login-modal] The `LoginModal` component provides a complete sign-in UI with support for GitHub, Google, email magic links, and passkey login. ```tsx import { useState } from 'react'; import { LoginModal, useAuth } from '@flowindex/auth-ui'; function Header() { const { user, signOut } = useAuth(); const [showLogin, setShowLogin] = useState(false); return (
{user ? (
{user.email}
) : ( )} setShowLogin(false)} />
); } ``` Register a Passkey [#register-a-passkey] After the user signs in (via any method), they can register a passkey. This creates a WebAuthn credential and stores the public key server-side. ```tsx import { usePasskeyAuth } from '@flowindex/auth-ui'; function RegisterPasskey() { const passkey = usePasskeyAuth(); async function handleRegister() { const { credentialId, publicKeySec1Hex } = await passkey.register('My Wallet'); console.log('Passkey registered:', credentialId); console.log('Public key (SEC1):', publicKeySec1Hex); } if (!passkey.hasSupport) { return

Passkeys are not supported on this device.

; } return ; } ``` Provision a Flow Account [#provision-a-flow-account] Once a passkey is registered, provision an on-chain Flow account. This submits the passkey's P-256 public key to Flow's account creation service, which creates a new account with that key as its sole signer. ```tsx import { usePasskeyAuth } from '@flowindex/auth-ui'; function ProvisionAccount() { const passkey = usePasskeyAuth(); const account = passkey.selectedAccount; async function handleProvision() { if (!account) return; // Start provisioning on both mainnet and testnet const result = await passkey.provisionAccounts(account.credentialId); // Poll for each network's transaction to seal for (const [network, info] of Object.entries(result.networks)) { if (info.txId) { const address = await passkey.pollProvisionTx( info.txId, network as 'mainnet' | 'testnet', ); await passkey.saveProvisionedAddress(account.credentialId, network, address); console.log(`${network} account created: 0x${address}`); } } // Refresh state to pick up new addresses await passkey.refreshState(); } return (
{account?.flowAddress ? (

Flow address: 0x{account.flowAddress}

) : ( )}
); } ``` Sign Flow Transactions [#sign-flow-transactions] Use `getFlowAuthz` to create an FCL-compatible authorization function, then pass it to `fcl.mutate` or `fcl.send`. ```tsx import * as fcl from '@onflow/fcl'; import { usePasskeyAuth } from '@flowindex/auth-ui'; function SendTransaction() { const passkey = usePasskeyAuth(); async function handleSend() { const account = passkey.selectedAccount; if (!account?.flowAddress) return; const authz = passkey.getFlowAuthz(account.flowAddress, 0); const txId = await fcl.mutate({ cadence: ` transaction { prepare(signer: &Account) { log(signer.address) } } `, proposer: authz, payer: authz, authorizations: [authz], }); console.log('Transaction submitted:', txId); const result = await fcl.tx(txId).onceSealed(); console.log('Transaction sealed:', result.status); } return ; } ``` Sign Without FCL [#sign-without-fcl] If you are not using FCL, you can sign raw messages directly using the `sign` method on the passkey state: ```tsx import { usePasskeyAuth } from '@flowindex/auth-ui'; function SignMessage() { const passkey = usePasskeyAuth(); async function handleSign() { const messageHex = '48656c6c6f'; // "Hello" in hex const { signature, extensionData } = await passkey.sign(messageHex); console.log('Signature:', signature); console.log('Extension data:', extensionData); } return ; } ``` Using the Low-Level SDK Directly [#using-the-low-level-sdk-directly] For non-React applications or custom integrations, use `@flowindex/flow-passkey` and `@flowindex/auth-core` directly. Create a Passkey Client [#create-a-passkey-client] ```ts import { createPasskeyAuthClient } from '@flowindex/auth-core'; const passkeyClient = createPasskeyAuthClient({ passkeyAuthUrl: 'https://your-supabase-domain/functions/v1/passkey-auth', rpId: 'yourdomain.com', rpName: 'Your App Name', }); ``` Register and Login [#register-and-login] ```ts // Register a new passkey (requires an authenticated session token) const { credentialId, publicKeySec1Hex } = await passkeyClient.register(accessToken); // Login with a passkey (no session required -- returns a token hash) const { tokenHash, email } = await passkeyClient.login(); ``` Sign a Transaction [#sign-a-transaction] ```ts import { signFlowTransaction, createPasskeyAuthz } from '@flowindex/flow-passkey'; // Direct signing const { signature, extensionData } = await signFlowTransaction({ messageHex: '...', // RLP-encoded transaction with domain tag credentialId: 'base64url-credential-id', rpId: 'yourdomain.com', }); // FCL authorization function const authz = createPasskeyAuthz({ address: '0x1234abcd', keyIndex: 0, credentialId: 'base64url-credential-id', rpId: 'yourdomain.com', }); ``` Browser Support [#browser-support] Passkeys require WebAuthn support. The wallet checks for `window.PublicKeyCredential` at runtime and exposes `passkey.hasSupport` so you can show appropriate fallback UI. | Browser | Minimum Version | | ------- | --------------- | | Chrome | 67+ | | Safari | 14+ | | Firefox | 60+ | | Edge | 18+ | Discoverable credentials (resident keys) and cross-device passkey sync require more recent versions (Chrome 108+, Safari 16+, etc.). # FlowIndex FlowIndex is a full-stack blockchain explorer and indexer purpose-built for the [Flow blockchain](https://flow.com). It provides real-time block ingestion, comprehensive historical backfill, a rich REST API, WebSocket live updates, and a modern frontend UI. Core Features [#core-features] * **Real-time block indexing** -- A forward ingester tracks the chain head and indexes new blocks within seconds of finalization. * **Historical backfill** -- A backward ingester fills the complete chain history, with spork-aware node routing for seamless cross-spork coverage. * **Flow-EVM support** -- Automatic detection and indexing of EVM transactions embedded in Flow, including EVM hash mapping, sender/receiver extraction, and gas tracking. * **WebSocket live updates** -- Frontend clients receive new blocks and transactions in real time via a WebSocket broadcast hub. * **REST API** -- A comprehensive API covering blocks, transactions, accounts, fungible tokens, NFTs, contracts, staking, DeFi, and analytics. * **17+ background workers** -- Specialized processors derive token transfers, NFT ownership, account metadata, staking events, DeFi swaps, daily statistics, and more. * **Cursor-based pagination** -- All list endpoints use efficient cursor pagination to avoid expensive offset scans. * **Partitioned storage** -- Raw blockchain data is stored in PostgreSQL range-partitioned tables for scalable querying at terabyte scale. Tech Stack [#tech-stack] | Layer | Technology | | ---------- | --------------------------------------------------------------------- | | Backend | Go 1.24+, PostgreSQL (pgx), Flow SDK, Gorilla Mux/WebSocket | | Frontend | React 19, TanStack Start (SSR via Nitro), TanStack Router, TypeScript | | UI | TailwindCSS, Shadcn/UI (Radix), Recharts, Framer Motion, Lucide icons | | Auth | Self-hosted Supabase (GoTrue + PostgREST) | | Deployment | Docker, Docker Compose | Architecture at a Glance [#architecture-at-a-glance] ```mermaid flowchart LR FAN["Flow Access Nodes"] --> FI["Forward Ingester"] FAN --> BI["Backward Ingester"] FI --> DB["PostgreSQL"] BI --> DB DB --> W["Workers & Derivers"] W --> DB DB --> API["REST API + WebSocket"] API --> FE["Frontend UI"] ``` The system follows a **dual ingester + deriver** pattern: 1. **Ingesters** fetch raw blocks, transactions, and events from Flow Access Nodes and store them in partitioned `raw.*` tables. 2. **Derivers and workers** read raw data and produce derived tables in `app.*` -- token transfers, account metadata, NFT ownership, staking state, and more. 3. **The API server** queries both raw and app tables to serve the frontend and external consumers. See the [Architecture](/docs/flowindex/architecture) page for a detailed breakdown. Project Structure [#project-structure] ``` backend/ Go indexer + API server frontend/ TanStack Start SSR app (React 19, TypeScript) supabase/ Self-hosted Supabase auth stack devportal/ Developer portal (Fumadocs + Scalar) scripts/ Utility scripts ``` Next Steps [#next-steps] * [Getting Started](/docs/flowindex/getting-started) -- Run FlowIndex locally with Docker Compose * [Architecture](/docs/flowindex/architecture) -- Understand the ingestion pipeline and worker system * [API Reference](/docs/flowindex/api-reference) -- Explore REST endpoints and WebSocket events * [Configuration](/docs/flowindex/guides/configuration) -- Environment variables reference * [EVM Support](/docs/flowindex/guides/evm-support) -- How Flow-EVM transactions are indexed # API Reference The FlowIndex API is served by the backend on port 8080 by default. All endpoints return JSON and support CORS. API routes are available under the `/flow/v1/` prefix (also accessible at `/api/v1/flow/v1/` for compatibility). Base Endpoints [#base-endpoints] Health Check [#health-check] ``` GET /health ``` Returns the service health status. ```json {"status": "ok"} ``` Indexing Status [#indexing-status] ``` GET /status ``` Returns comprehensive indexing status including block heights, worker progress, and configuration. **Query parameters:** | Parameter | Type | Description | | ---------------- | ------- | -------------------------------------------- | | `include_ranges` | boolean | Include indexed block ranges (heavier query) | **Response:** ```json { "chain_id": "flow", "latest_height": 142610500, "indexed_height": 142610498, "history_height": 85000000, "min_height": 85000000, "max_height": 142610498, "total_blocks": 57610498, "total_transactions": 450000000, "total_events": 2100000000, "total_addresses": 35000000, "total_contracts": 12000, "progress": "99.98%", "behind": 2, "status": "ok" } ``` OpenAPI Specification [#openapi-specification] ``` GET /openapi.yaml GET /openapi.json ``` Returns the OpenAPI specification in YAML or JSON format. *** Blocks [#blocks] List Blocks [#list-blocks] ``` GET /flow/block ``` | Parameter | Type | Description | | --------- | ------- | -------------------------------------------------- | | `limit` | integer | Number of blocks to return (default: 20, max: 100) | | `cursor` | string | Pagination cursor (block height) | **Example response:** ```json { "data": [ { "height": 142610500, "id": "abc123...", "parent_id": "def456...", "timestamp": "2025-01-15T12:00:00Z", "tx_count": 15, "event_count": 120 } ], "has_more": true, "cursor": "142610480" } ``` Get Block [#get-block] ``` GET /flow/block/{height} ``` Returns a single block by height, including collections and transaction summaries. Block Transactions [#block-transactions] ``` GET /flow/block/{height}/transaction ``` Returns all transactions in a block. Block Service Events [#block-service-events] ``` GET /flow/block/{height}/service-event ``` Returns system service events for a block (epoch transitions, protocol state changes). *** Transactions [#transactions] List Transactions [#list-transactions] ``` GET /flow/transaction ``` | Parameter | Type | Description | | --------- | ------- | ------------------------------------------------- | | `limit` | integer | Number of results (default: 20) | | `cursor` | string | Pagination cursor (`block_height:tx_index:tx_id`) | Get Transaction [#get-transaction] ``` GET /flow/transaction/{id} ``` Returns a single transaction by its Flow transaction ID. Also resolves EVM transaction hashes. *** Accounts [#accounts] List Accounts [#list-accounts] ``` GET /flow/account ``` Get Account [#get-account] ``` GET /flow/account/{address} ``` Returns account details including balance, key count, contract count, and labels. Account Transactions [#account-transactions] ``` GET /flow/account/{address}/transaction ``` Returns transactions involving the given address (as payer, proposer, or authorizer). Account FT Vaults [#account-ft-vaults] ``` GET /flow/account/{address}/ft ``` Returns all fungible token vaults (balances) for an account. Account FT Transfers [#account-ft-transfers] ``` GET /flow/account/{address}/ft/transfer ``` Account NFT Collections [#account-nft-collections] ``` GET /flow/account/{address}/nft ``` Account NFT Transfers [#account-nft-transfers] ``` GET /flow/account/{address}/nft/transfer ``` Account Balance History [#account-balance-history] ``` GET /flow/account/{address}/balance/history ``` Account Storage [#account-storage] ``` GET /flow/account/{address}/storage ``` Returns the account's on-chain storage contents. Account Contract Code [#account-contract-code] ``` GET /flow/account/{address}/contract/{name} ``` Returns the source code of a specific contract deployed to the account. Account Labels [#account-labels] ``` GET /flow/account/{address}/labels ``` Returns known labels/tags for the account (e.g., exchange, service, etc.). Tax Report [#tax-report] ``` GET /flow/account/{address}/tax-report ``` Generates a tax report for the account's token activity. *** Fungible Tokens [#fungible-tokens] List FT Tokens [#list-ft-tokens] ``` GET /flow/ft ``` Returns all known fungible tokens with metadata (name, symbol, decimals, icon). Get FT Token [#get-ft-token] ``` GET /flow/ft/{token} ``` FT Transfers [#ft-transfers] ``` GET /flow/ft/transfer ``` Global fungible token transfer feed. FT Token Stats [#ft-token-stats] ``` GET /flow/ft/stats ``` FT Token Prices [#ft-token-prices] ``` GET /flow/ft/prices ``` FT Holdings by Token [#ft-holdings-by-token] ``` GET /flow/ft/{token}/holding ``` Top FT Accounts [#top-ft-accounts] ``` GET /flow/ft/{token}/top-account ``` *** NFTs [#nfts] List NFT Collections [#list-nft-collections] ``` GET /flow/nft ``` Get NFT Collection [#get-nft-collection] ``` GET /flow/nft/{nft_type} ``` NFT Transfers [#nft-transfers] ``` GET /flow/nft/transfer ``` Global NFT transfer feed. NFT Collection Items [#nft-collection-items] ``` GET /flow/nft/{nft_type}/item ``` NFT Item Detail [#nft-item-detail] ``` GET /flow/nft/{nft_type}/item/{id} ``` NFT Item Transfers [#nft-item-transfers] ``` GET /flow/nft/{nft_type}/item/{id}/transfer ``` NFT Search [#nft-search] ``` GET /flow/nft/search ``` *** Contracts [#contracts] List Contracts [#list-contracts] ``` GET /flow/contract ``` Get Contract [#get-contract] ``` GET /flow/contract/{identifier} ``` The `identifier` format is `A.{address}.{name}` (e.g., `A.1654653399040a61.FlowToken`). Contract Transactions [#contract-transactions] ``` GET /flow/contract/{identifier}/transaction ``` Contract Event Types [#contract-event-types] ``` GET /flow/contract/{identifier}/events ``` Contract Versions [#contract-versions] ``` GET /flow/contract/{identifier}/version ``` Contract Dependencies [#contract-dependencies] ``` GET /flow/contract/{identifier}/dependencies ``` *** EVM [#evm] List EVM Transactions [#list-evm-transactions] ``` GET /flow/evm/transaction ``` Get EVM Transaction [#get-evm-transaction] ``` GET /flow/evm/transaction/{hash} ``` Look up an EVM transaction by its EVM hash. Returns both the EVM details and the parent Cadence transaction. List EVM Tokens [#list-evm-tokens] ``` GET /flow/evm/token ``` Get EVM Token [#get-evm-token] ``` GET /flow/evm/token/{address} ``` EVM Address Tokens [#evm-address-tokens] ``` GET /flow/evm/address/{address}/token ``` *** Staking [#staking] List Nodes [#list-nodes] ``` GET /flow/node ``` Get Node [#get-node] ``` GET /flow/node/{node_id} ``` Staking Delegators [#staking-delegators] ``` GET /staking/delegator ``` Epoch Stats [#epoch-stats] ``` GET /staking/epoch/stats ``` Epoch Nodes [#epoch-nodes] ``` GET /staking/epoch/{epoch}/nodes ``` Staking Rewards [#staking-rewards] ``` GET /staking/rewards/paid GET /staking/rewards/staking ``` Staking Tokenomics [#staking-tokenomics] ``` GET /staking/tokenomics ``` *** DeFi [#defi] List DeFi Assets [#list-defi-assets] ``` GET /defi/asset ``` List DeFi Pairs [#list-defi-pairs] ``` GET /defi/pair ``` DeFi Events [#defi-events] ``` GET /defi/events ``` Latest DeFi Swap [#latest-defi-swap] ``` GET /defi/latest-swap ``` *** Analytics [#analytics] Daily Metrics [#daily-metrics] ``` GET /insights/daily ``` Returns daily aggregate metrics (transactions, accounts, events). Daily Module Metrics [#daily-module-metrics] ``` GET /insights/daily/module/{module} ``` Transfer Analytics [#transfer-analytics] ``` GET /insights/transfers/daily ``` Big Transfers [#big-transfers] ``` GET /insights/big-transfers ``` Top Contracts [#top-contracts] ``` GET /insights/top-contracts ``` Token Volume [#token-volume] ``` GET /insights/token-volume ``` *** Network Status [#network-status] Network Statistics [#network-statistics] ``` GET /status/stat GET /status/stat/{timescale}/trend ``` Flow Statistics [#flow-statistics] ``` GET /status/flow/stat ``` Epoch Status [#epoch-status] ``` GET /status/epoch/status GET /status/epoch/stat ``` Price Data [#price-data] ``` GET /status/price GET /status/price/at GET /status/price/history GET /status/prices ``` Node Count [#node-count] ``` GET /status/nodes ``` *** Events [#events] Search Events [#search-events] ``` GET /flow/events/search ``` Search for events by type across all indexed blocks. *** Public Key Lookup [#public-key-lookup] ``` GET /flow/key/{publicKey} ``` Find accounts associated with a given public key. *** COA (Cadence-Owned Account) Mapping [#coa-cadence-owned-account-mapping] ``` GET /flow/coa/{address} ``` Look up the COA mapping for a Flow-EVM address. *** WebSocket [#websocket] Live Updates [#live-updates] ``` ws://localhost:8080/ws ``` Connect to receive real-time broadcasts of new blocks and transactions. Messages are JSON-encoded with a `type` field: new_block [#new_block] Broadcast when a new block is indexed. ```json { "type": "new_block", "payload": { "height": 142610500, "id": "abc123...", "timestamp": "2025-01-15T12:00:00Z", "tx_count": 15, "event_count": 120 } } ``` new_transaction [#new_transaction] Broadcast for each transaction in a new block. ```json { "type": "new_transaction", "payload": { "id": "def456...", "block_height": 142610500, "status": "SEALED", "payer_address": "e467b9dd11fa00df", "proposer_address": "e467b9dd11fa00df", "timestamp": "2025-01-15T12:00:00Z", "execution_status": "SUCCESS", "is_evm": false, "script_hash": "a1b2c3...", "template_category": "token_transfer", "template_label": "Transfer FLOW", "tags": ["FT_TRANSFER"] } } ``` Status WebSocket [#status-websocket] ``` ws://localhost:8080/ws/status ``` Streams the indexing status payload every 3 seconds (same format as `GET /status`). *** Pagination [#pagination] All list endpoints use **cursor-based pagination**. The response includes: | Field | Type | Description | | ---------- | ------- | --------------------------------------- | | `data` | array | Result items | | `has_more` | boolean | Whether more results exist | | `cursor` | string | Pass as `?cursor=` to get the next page | Cursor formats vary by endpoint: * Blocks: `height` * Transactions: `block_height:tx_index:tx_id` * Account transactions: `block_height:tx_id` * Token transfers: `block_height:tx_id:event_index` # Getting Started This guide walks you through running a local FlowIndex instance with Docker Compose. By the end you will have the backend indexer, frontend UI, and PostgreSQL database running on your machine. Prerequisites [#prerequisites] * [Docker](https://docs.docker.com/get-docker/) (v20.10+) * [Docker Compose](https://docs.docker.com/compose/install/) (v2.0+) * At least 4 GB of free RAM (8 GB recommended) Quick Start [#quick-start] 1. Clone the repository [#1-clone-the-repository] ```bash git clone https://github.com/Outblock/flowindex.git cd flowindex ``` 2. Start all services [#2-start-all-services] ```bash docker compose up -d --build ``` This starts the following core services: | Service | Port | Description | | ---------- | --------------------------------------- | --------------------------- | | `frontend` | [localhost:5173](http://localhost:5173) | Web UI (TanStack Start SSR) | | `backend` | [localhost:8080](http://localhost:8080) | Go API server + indexer | | `db` | localhost:5432 | PostgreSQL database | Additional services (Supabase auth stack, developer portal, etc.) are also started automatically. 3. Verify the backend is running [#3-verify-the-backend-is-running] ```bash curl http://localhost:8080/health ``` Expected response: ```json {"status":"ok"} ``` 4. Check indexing status [#4-check-indexing-status] ```bash curl http://localhost:8080/status ``` This returns a JSON object with indexing progress, including: ```json { "chain_id": "flow", "latest_height": 142610500, "indexed_height": 142610498, "progress": "99.98%", "behind": 2, "total_blocks": 1500000, "total_transactions": 3200000, "status": "ok" } ``` 5. Open the frontend [#5-open-the-frontend] Navigate to [http://localhost:5173](http://localhost:5173) in your browser. You should see the FlowIndex explorer with blocks appearing in real time. Viewing Logs [#viewing-logs] ```bash # Backend logs (indexer + API) docker compose logs -f backend # Frontend logs docker compose logs -f frontend # Database logs docker compose logs -f db ``` Stopping Services [#stopping-services] ```bash docker compose down ``` To also remove stored data (database volumes): ```bash docker compose down -v ``` Local Development (Without Docker) [#local-development-without-docker] Backend [#backend] ```bash cd backend go mod download && go mod tidy # Build CGO_CFLAGS="-std=gnu99" CGO_ENABLED=1 go build -o indexer main.go # Run (requires a running PostgreSQL instance) DB_URL=postgres://flowscan:secretpassword@localhost:5432/flowscan \ FLOW_ACCESS_NODE=access-001.mainnet28.nodes.onflow.org:9000 \ PORT=8080 \ ./indexer ``` Frontend [#frontend] ```bash cd frontend # Install dependencies bun install # Start dev server bun run dev ``` The frontend dev server starts at [http://localhost:3000](http://localhost:3000) and connects to the backend at `http://localhost:8080` by default. Database [#database] If running outside Docker, create the database and apply the schema: ```bash createdb flowscan psql postgres://flowscan:secretpassword@localhost:5432/flowscan < backend/schema_v2.sql ``` Or use the built-in migration command: ```bash ./indexer migrate ``` Next Steps [#next-steps] * [Architecture](/docs/flowindex/architecture) -- Understand how the ingestion pipeline works * [Configuration](/docs/flowindex/guides/configuration) -- Tune environment variables for your setup * [API Reference](/docs/flowindex/api-reference) -- Explore the REST API # Architecture FlowIndex follows a **dual ingester + deriver** architecture designed for high-throughput blockchain indexing with near-real-time derived data. System Overview [#system-overview] ```mermaid flowchart LR FAN["Flow Access Nodes"] --> FI["Forward Ingester"] FAN --> BI["Backward Ingester"] FI -->|raw blocks, txs, events| DB[("PostgreSQL")] BI -->|raw blocks, txs, events| DB DB --> LD["Live Derivers"] LD -->|derived data| DB DB --> HD["History Deriver"] HD -->|derived data| DB DB --> API["API Server"] API -->|REST + WebSocket| FE["Frontend UI"] ``` Dual Ingester Pattern [#dual-ingester-pattern] Two independent ingesters run concurrently, each optimized for its workload: Forward Ingester (main_ingester) [#forward-ingester-main_ingester] Tracks the chain head in real time. Processes blocks in **ascending** order. 1. Reads its checkpoint from the database 2. Fetches the latest sealed block height from a Flow Access Node 3. Fills a batch using a concurrent worker pool 4. Performs **reorg detection** by verifying parent hash continuity 5. Saves the batch atomically in a single database transaction 6. Broadcasts new blocks and transactions via WebSocket 7. Triggers the forward Live Deriver for near-real-time derived data Backward Ingester (history_ingester) [#backward-ingester-history_ingester] Backfills historical data in **descending** order, silently filling the chain from the present back to genesis. 1. Walks backwards from its checkpoint 2. Handles **spork boundaries** automatically -- when a node returns "not found", the ingester adjusts the floor to the spork root height 3. Supports per-spork node configuration via `FLOW_HISTORIC_ACCESS_NODES` 4. No WebSocket broadcasts (silent backfill) 5. Triggers the history Live Deriver for immediate derived data Data Flow [#data-flow] ```mermaid sequenceDiagram participant FI as Forward Ingester participant BI as Backward Ingester participant Raw as raw.* tables participant LD as Live Derivers participant App as app.* tables participant API as API Server FI->>Raw: Save blocks, txs, events FI->>LD: NotifyRange(from, to) BI->>Raw: Save blocks, txs, events BI->>LD: NotifyRange(from, to) LD->>Raw: Read raw data LD->>App: Write derived data API->>Raw: Query raw data API->>App: Query derived data ``` Derivers and Workers [#derivers-and-workers] Derivers transform raw blockchain data into queryable derived tables. They operate in two phases per chunk of blocks: Phase 1 -- Independent Processors (parallel) [#phase-1----independent-processors-parallel] | Worker | Purpose | Output Tables | | --------------------- | --------------------------------------------- | --------------------------------------------------------------- | | `token_worker` | Parse FT/NFT events into transfers | `ft_transfers`, `nft_transfers`, `ft_tokens`, `nft_collections` | | `evm_worker` | Decode EVM transactions from Flow events | `evm_tx_hashes` | | `tx_contracts_worker` | Extract contract imports, tag transactions | `tx_contracts`, `tx_tags` | | `accounts_worker` | Catalog accounts from creation events | `accounts`, `coa_accounts` | | `meta_worker` | Build address-transaction index, extract keys | `address_transactions`, `account_keys`, `smart_contracts` | | `tx_metrics_worker` | Compute per-transaction metrics | `tx_metrics` | | `staking_worker` | Parse staking and epoch events | `staking_events`, `staking_nodes`, `epoch_stats` | | `defi_worker` | Parse DEX swap events | `defi_events`, `defi_pairs` | Phase 2 -- Token-Dependent Processors (parallel, after Phase 1) [#phase-2----token-dependent-processors-parallel-after-phase-1] | Worker | Purpose | Output Tables | | ---------------------- | ----------------------------------- | ---------------- | | `ft_holdings_worker` | Update FT balances from transfers | `ft_holdings` | | `nft_ownership_worker` | Update NFT ownership from transfers | `nft_ownership` | | `daily_balance_worker` | Aggregate daily FT deltas | `daily_balances` | Queue-Based Workers (independent) [#queue-based-workers-independent] These operate outside the deriver pipeline, finding their own work from derived tables: | Worker | Purpose | | -------------------------- | ------------------------------------------ | | `nft_item_metadata_worker` | Fetch per-NFT metadata via Cadence scripts | | `nft_ownership_reconciler` | Verify NFT ownership against chain state | | `token_metadata_worker` | Fetch on-chain FT/NFT collection metadata | Live Deriver vs. History Deriver [#live-deriver-vs-history-deriver] | | Live Deriver | History Deriver | | -------------- | ------------------------------- | --------------------------- | | **Trigger** | Ingester callback on each batch | Periodic background scan | | **Chunk size** | 10 blocks (configurable) | 1,000 blocks (configurable) | | **Latency** | Near-real-time (\~1 second) | Background safety net | | **Instances** | Two (forward + history) | One | | **Purpose** | Primary processing path | Catch missed ranges | The **forward Live Deriver** is triggered by the forward ingester after each batch commit, ensuring derived data is available within seconds of a block being sealed. The **history Live Deriver** is triggered by the backward ingester, processing newly backfilled blocks immediately. The **History Deriver** runs as a safety net, scanning for any raw blocks that were not yet processed by the derivers. Database Schema [#database-schema] The database uses a dual-layer design: Raw Layer (raw.*) [#raw-layer-raw] Stores blockchain data exactly as received from Flow Access Nodes. Append-only and partitioned by block height. | Table | Contents | | ------------------ | ----------------------------------------------------------------- | | `raw.blocks` | Block headers (height, ID, parent ID, timestamp, tx/event counts) | | `raw.transactions` | Full transaction data (script, arguments, authorizers, status) | | `raw.events` | All events emitted by transactions | | `raw.tx_lookup` | Transaction ID to block height mapping | | `raw.block_lookup` | Block ID to height mapping | | `raw.scripts` | Deduplicated transaction scripts (script\_hash to script\_text) | App Layer (app.*) [#app-layer-app] Stores worker-derived projections optimized for queries. | Table | Contents | | -------------------------- | ---------------------------------------------------------- | | `app.ft_transfers` | Fungible token transfers (sender, receiver, amount, token) | | `app.nft_transfers` | NFT transfers (sender, receiver, collection, NFT ID) | | `app.ft_holdings` | Current FT balances per account per token | | `app.nft_ownership` | Current NFT ownership | | `app.accounts` | Known accounts with creation metadata | | `app.smart_contracts` | Deployed contracts with code and metadata | | `app.account_keys` | Flow account public keys | | `app.address_transactions` | Address-to-transaction index for fast lookups | | `app.evm_tx_hashes` | Cadence transaction to EVM hash mappings | | `app.staking_nodes` | Staking node state and delegation info | | `app.defi_pairs` | DEX trading pairs and liquidity pools | | `app.indexing_checkpoints` | Resumability state for all workers | | `app.worker_leases` | Lease-based concurrency control | Partitioning Strategy [#partitioning-strategy] Raw tables are range-partitioned by `block_height` with partition sizes of 5-10 million rows. Lookup tables (`tx_lookup`, `block_lookup`) avoid expensive cross-partition scans. ```mermaid flowchart TB P["raw.transactions"] --> P1["Partition 0 - 5M"] P --> P2["Partition 5M - 10M"] P --> P3["Partition 10M - 15M"] P --> P4["..."] ``` Resumability [#resumability] All ingesters and workers track progress via `app.indexing_checkpoints`. On restart, each component resumes from its last committed checkpoint. Writes are idempotent (upsert-based), so retries are safe. Workers use a lease mechanism (`app.worker_leases`) to prevent duplicate processing across concurrent instances. Failed leases are automatically retried up to 20 times before being flagged for manual intervention. Reorg Handling [#reorg-handling] The forward ingester performs parent hash verification on each batch. If a chain reorganization is detected: 1. Affected blocks are surgically deleted (not truncated) 2. Worker checkpoints are clamped to the rollback height 3. Worker leases overlapping the rollback range are deleted for re-derivation # Packages Packages [#packages] FlowIndex publishes a set of TypeScript packages that provide authentication, wallet, and UI functionality for Flow blockchain applications. All packages are published under the `@flowindex` scope on npm. Package Overview [#package-overview] | Package | Description | Install | | -------------------------------------------------------- | ----------------------------------------------------------------------- | --------------------------------- | | [`@flowindex/agent-wallet`](/docs/packages/agent-wallet) | MCP server for AI agent wallets on Flow | `bun add @flowindex/agent-wallet` | | [`@flowindex/auth-core`](/docs/packages/auth-core) | JWT, cookie, token refresh, and passkey auth primitives | `bun add @flowindex/auth-core` | | [`@flowindex/auth-ui`](/docs/packages/auth-ui) | React components for authentication (AuthProvider, useAuth, LoginModal) | `bun add @flowindex/auth-ui` | | [`@flowindex/flow-passkey`](/docs/packages/flow-passkey) | WebAuthn passkey SDK for Flow transaction signing (FLIP-264) | `bun add @flowindex/flow-passkey` | | [`@flowindex/flow-ui`](/docs/packages/flow-ui) | Shared React UI components, Radix primitives, and Flow utilities | `bun add @flowindex/flow-ui` | Dependency Graph [#dependency-graph] The packages have the following dependency relationships: ``` @flowindex/auth-ui -> @flowindex/auth-core -> @flowindex/flow-passkey @flowindex/auth-core -> @flowindex/flow-passkey @flowindex/agent-wallet (standalone MCP server) @flowindex/flow-ui (standalone UI library) ``` * **`flow-passkey`** is the lowest-level package, providing WebAuthn primitives and Flow transaction signing. * **`auth-core`** builds on `flow-passkey` to add JWT parsing, GoTrue integration, and cookie management. * **`auth-ui`** is the highest-level auth package, providing React components that use both `auth-core` and `flow-passkey`. * **`agent-wallet`** and **`flow-ui`** are standalone packages with no internal dependencies. Quick Start [#quick-start] For a typical React application that needs authentication with passkey support: ```bash bun add @flowindex/auth-ui ``` This transitively installs `@flowindex/auth-core` and `@flowindex/flow-passkey`. For an AI agent that needs to interact with the Flow blockchain: ```bash bun add @flowindex/agent-wallet ``` For shared UI components in a Flow-based application: ```bash bun add @flowindex/flow-ui ``` # Run Run is an interactive Cadence development environment that lets you write, execute, and deploy smart contracts on the Flow blockchain directly from your browser. It combines a full-featured code editor with wallet integration, AI assistance, and GitHub-based deployment workflows. Core Features [#core-features] * **Monaco code editor** -- A rich editing experience with syntax highlighting, inline diagnostics, and auto-completion powered by the Cadence Language Server. * **Script and transaction execution** -- Execute Cadence scripts (read-only queries) and transactions (state-changing operations) against Flow mainnet or testnet. * **Contract deployment** -- Deploy or update Cadence smart contracts on any Flow account you control, with automatic add-vs-update detection. * **Cadence Language Server (LSP)** -- Real-time type checking, hover documentation, go-to-definition, and auto-complete powered by a server-side LSP instance of the Flow CLI. * **Wallet integration** -- Connect any FCL-compatible wallet (Lilico, Blocto, etc.) to sign transactions, or use custodial keys managed through the built-in key manager. * **Passkey-based signing** -- Create and manage Flow account keys backed by WebAuthn passkeys for passwordless transaction signing. * **Cloud projects** -- Save, load, and share multi-file Cadence projects with unique shareable URLs. Projects are stored via Supabase and support public/private visibility. * **AI assistant** -- An integrated AI chat panel that understands Cadence and can help write, debug, and explain smart contract code. * **GitHub integration** -- Connect projects to GitHub repositories for version-controlled contract deployment with environment management. * **Dependency resolution** -- Automatically fetches and caches imported contract dependencies from mainnet or testnet so the LSP can provide full type checking. * **Multi-file projects** -- Organize code into files and folders with a file explorer, tabbed editing, and project templates. * **Deploy dashboard** -- A dedicated view for managing contract deployments across multiple addresses, with dependency graphs, audit information, and deployment history. Tech Stack [#tech-stack] | Layer | Technology | | ---------- | -------------------------------------------------------- | | Frontend | React 19, Vite, TypeScript, TailwindCSS | | Editor | Monaco Editor with Cadence TextMate grammar | | LSP | Flow CLI `cadence language-server` (server-side process) | | Flow | `@onflow/fcl` (Flow Client Library) | | Auth | Supabase (GoTrue) with passkey support | | AI | Vercel AI SDK (`ai` + `@ai-sdk/react`) | | Server | Node.js (Express + WebSocket) | | Production | nginx (static files) + supervisord (process management) | How It Works [#how-it-works] Run consists of two main components: 1. **Frontend** -- A Vite-built React SPA that provides the Monaco editor, file explorer, wallet UI, and execution controls. It communicates with Flow Access Nodes via FCL for script/transaction execution. 2. **Backend server** -- A Node.js process that hosts the Cadence Language Server via WebSocket and provides HTTP endpoints for GitHub integration. The LSP manages per-network dependency workspaces and translates between the browser editor and the Flow CLI language server. In production, nginx serves the static frontend files and proxies `/lsp` WebSocket connections to the Node.js server and `/functions` requests to the Supabase edge function gateway for project persistence. # Getting Started This guide walks you through running the Cadence Runner locally for development. By the end you will have the editor, LSP server, and all supporting services running on your machine. Prerequisites [#prerequisites] * [Docker](https://docs.docker.com/get-docker/) (v20.10+) and [Docker Compose](https://docs.docker.com/compose/install/) (v2.0+) * [Bun](https://bun.sh/) (v1.0+) -- for local frontend development outside Docker * [Node.js](https://nodejs.org/) (v22+) -- for the LSP backend server * [Flow CLI](https://docs.onflow.org/flow-cli/) (v2.x) -- required by the LSP server to run the Cadence language server Quick Start with Docker Compose [#quick-start-with-docker-compose] The simplest way to run everything is via Docker Compose from the repository root: ```bash docker compose up -d --build runner ``` This starts the Runner service along with its dependencies (Supabase auth stack, edge functions). | Service | Port | Description | | ------------------ | --------------------------------------- | -------------------------------- | | `runner` | [localhost:8081](http://localhost:8081) | Runner UI + LSP server | | `supabase-gateway` | localhost:54321 | Auth and project persistence | | `runner-projects` | (internal) | Project management edge function | Open [http://localhost:8081](http://localhost:8081) in your browser. You should see the Cadence editor with a default script template. Verify the LSP is working [#verify-the-lsp-is-working] 1. Type some Cadence code in the editor (e.g., `import FungibleToken from 0xf233dcee88fe0abe`) 2. After a moment, you should see syntax highlighting and no red error underlines for valid code 3. Try hovering over a type name to see documentation If diagnostics appear and hover works, the Language Server is connected. Local Development (Without Docker) [#local-development-without-docker] For faster iteration during development, you can run the frontend and server separately. 1. Install dependencies [#1-install-dependencies] From the repository root: ```bash cd runner bun install ``` The `postinstall` script automatically copies the Cadence Language Server WASM files and worker scripts into the `public/` directory. 2. Start the LSP backend server [#2-start-the-lsp-backend-server] In a separate terminal: ```bash cd runner/server bun install bun run build node dist/index.js ``` The server starts two listeners: * **Port 3002** -- WebSocket endpoint for Cadence LSP communication * **Port 3003** -- HTTP server for GitHub integration API 3. Start the frontend dev server [#3-start-the-frontend-dev-server] ```bash cd runner bun run dev ``` The Vite dev server starts on [http://localhost:5173](http://localhost:5173) and proxies LSP WebSocket connections to the backend server automatically. 4. Build for production [#4-build-for-production] ```bash cd runner bun run build ``` The production build outputs static files to `runner/dist/`. Environment Variables [#environment-variables] | Variable | Default | Description | | ------------------------ | ------------------------ | ------------------------------------------------- | | `VITE_AI_CHAT_URL` | `http://localhost:3004` | AI chat service endpoint | | `VITE_SUPABASE_URL` | `http://localhost:54321` | Supabase gateway URL for auth and project storage | | `VITE_SUPABASE_ANON_KEY` | (none) | Supabase anonymous key | | `VITE_GOTRUE_URL` | `http://localhost:9999` | GoTrue auth endpoint | | `LSP_PORT` | `3002` | WebSocket port for the LSP server | | `HTTP_PORT` | `3003` | HTTP port for GitHub integration API | | `FLOW_COMMAND` | `flow` | Path to the Flow CLI binary | | `GITHUB_APP_ID` | (none) | GitHub App ID for repository integration | | `GITHUB_APP_PRIVATE_KEY` | (none) | GitHub App private key | Project Templates [#project-templates] When you first open the editor, you can choose from built-in templates: * **Hello World** -- A simple script that returns a greeting * **FT Transfer** -- Transfer fungible tokens between accounts * **NFT Minting** -- Mint an NFT from a collection * **Custom** -- Start with a blank file You can also load shared projects by their slug URL or fork public projects from other users. # Architecture Run is composed of a static frontend SPA, a Node.js backend server, and a Supabase edge function for project persistence. In production, nginx ties them together and supervisord manages the processes. System Overview [#system-overview] ```mermaid flowchart LR Browser["Browser"] -->|static files| Nginx["nginx"] Browser -->|WebSocket /lsp| Server["Node.js Server"] Browser -->|FCL| FAN["Flow Access Nodes"] Browser -->|HTTP /functions| SB["Supabase Gateway"] Server -->|stdio| LSP["Flow CLI LSP"] Server -->|flow deps install| FlowCLI["Flow CLI"] SB --> EF["runner-projects Edge Function"] EF --> DB[("Supabase DB")] ``` Frontend [#frontend] The frontend is a Vite-built React 19 SPA with two main routes: * `/editor` -- The primary Cadence editor and execution environment * `/deploy/*` -- The contract deploy dashboard Editor [#editor] The editor uses Monaco Editor with a custom Cadence TextMate grammar for syntax highlighting. It supports: * **Multi-file projects** with a file explorer sidebar and tabbed editing * **Diff view** for comparing code changes (used during GitHub sync) * **Parameter detection** that parses `fun main(...)` or `transaction(...)` signatures and renders input fields automatically * **Code type detection** that determines whether the active file is a script, transaction, or contract based on its content Execution Engine [#execution-engine] The execution engine (`src/flow/execute.ts`) handles three types of operations: 1. **Scripts** -- Read-only queries executed via `fcl.query()`. These do not require wallet authentication. 2. **Transactions** -- State-changing operations executed via `fcl.mutate()`. These require a connected wallet or custodial key for signing. 3. **Contract deployments** -- A specialized transaction flow that encodes the contract source as hex and submits an add-or-update transaction. It auto-detects whether the contract already exists on the target account. All execution goes directly from the browser to Flow Access Nodes via FCL. The backend server is not involved in script or transaction execution. Wallet and Signing [#wallet-and-signing] Run supports multiple signing methods: | Method | Description | | ------------------- | ----------------------------------------------------------------------------- | | **FCL wallet** | Connect any FCL-compatible wallet (Lilico, Blocto, etc.) via the Discovery UI | | **Custodial keys** | Use locally-managed or server-stored keys with custom `signingFunction` | | **Passkey signing** | WebAuthn-backed keys for passwordless transaction signing | The `SignerSelector` component lets users choose which signing method and key to use for each transaction. Network Configuration [#network-configuration] FCL is configured per-network with pre-mapped contract addresses for common Flow contracts (FungibleToken, NonFungibleToken, FlowToken, EVM, etc.). The editor supports switching between **mainnet** and **testnet** at any time, which reconfigures FCL and reconnects the LSP. Backend Server [#backend-server] The Node.js backend (`runner/server/`) runs two services in a single process: WebSocket LSP Proxy (port 3002) [#websocket-lsp-proxy-port-3002] The LSP proxy bridges the browser Monaco editor to a native Flow CLI Cadence Language Server process. Each network (mainnet/testnet) gets its own LSP instance. **Connection lifecycle:** 1. Browser opens a WebSocket to `/lsp` 2. Browser sends `{ type: "init", network: "mainnet" }` to select the target network 3. Server returns `{ type: "ready" }` once the LSP client is initialized 4. Browser sends standard LSP JSON-RPC messages (`textDocument/didOpen`, `textDocument/completion`, etc.) 5. Server translates URIs between the browser's virtual filesystem and the server's dependency workspace, then forwards messages to the LSP process 6. LSP responses are translated back and sent to the browser **Key capabilities:** * **Automatic dependency resolution** -- When the editor opens a file with `import X from 0xABCDEF`, the server intercepts the message, runs `flow dependencies install` to fetch the contract source, and rewrites the import to a string-based import that the LSP understands * **Go-to-definition fallback** -- If the native LSP returns an invalid definition result for imported symbols, the server performs its own symbol lookup across cached dependency sources * **Shared LSP instances** -- All browser connections to the same network share a single LSP process. Per-connection state (open documents, resolved dependencies) is tracked independently * **Deploy event subscriptions** -- WebSocket connections can subscribe to `subscribe:deploy` messages to receive real-time deployment status updates HTTP Server (port 3003) [#http-server-port-3003] The Express HTTP server provides endpoints for GitHub App integration: * OAuth callback handling for GitHub App installation * Repository file listing and content retrieval via the GitHub API * Webhook processing for deployment events * Repository secret management for automated deployments Dependency Workspace [#dependency-workspace] The `DepsWorkspace` class manages a per-network temporary directory that acts as a Flow project. It: 1. Creates a `flow.json` with network configuration on initialization 2. Pre-installs core Flow contracts (FungibleToken, NonFungibleToken, FlowToken, MetadataViews, EVM, etc.) in the background 3. Installs additional contracts on-demand when the editor imports them 4. Caches installed contracts across connections -- once a contract is fetched, it is available to all users on that network 5. Rewrites address-based imports (`import X from 0xABCDEF`) to string-based imports (`import "X"`) in cached files so the LSP resolves them correctly Project Persistence [#project-persistence] Cloud project storage is handled by the `runner-projects` Supabase edge function (Deno). It exposes a single POST endpoint that dispatches based on an `endpoint` field in the request body: | Endpoint | Auth | Description | | ------------------ | ---------------------------------- | --------------------------------------------- | | `/projects/list` | Required | List the authenticated user's projects | | `/projects/get` | Public projects readable by anyone | Fetch a project and its files by slug | | `/projects/save` | Required | Create or update a project (upserts files) | | `/projects/delete` | Required | Delete a project and all its files | | `/projects/fork` | Required | Fork a public project into the user's account | Projects are stored across two database tables: * **`user_projects`** -- Project metadata (name, slug, network, visibility, active file, open files, folders) * **`project_files`** -- Individual file content keyed by `(project_id, path)` Each project gets a unique slug used for shareable URLs. GitHub Integration [#github-integration] The GitHub integration enables connecting a Runner project to a GitHub repository for version-controlled contract deployment: 1. **Connect** -- Install the GitHub App on a repository and link it to a Runner project 2. **Environments** -- Configure deployment environments (e.g., testnet, mainnet) with target Flow addresses and network settings 3. **Secrets** -- Store Flow account private keys as GitHub repository secrets for automated signing 4. **Deploy** -- Push commits to trigger deployment workflows that compile and deploy contracts to the configured environments 5. **Status** -- Real-time deployment status updates streamed via WebSocket Production Architecture [#production-architecture] In production, the Runner container runs three processes managed by supervisord: 1. **nginx** -- Serves the static frontend build, proxies `/lsp` to the Node.js server, proxies `/auth/` to GoTrue, and proxies `/functions/` to the Supabase gateway 2. **Node.js server** -- Runs the LSP WebSocket proxy and GitHub HTTP API 3. **Flow CLI** -- Spawned as a child process by the Node.js server for each network's LSP instance The container includes the Flow CLI binary for running the Cadence Language Server and resolving contract dependencies. # FlowIndex AI FlowIndex AI [#flowindex-ai] FlowIndex AI is an intelligent chat assistant that lets you query, analyze, and visualize data from the Flow blockchain using natural language. It combines text-to-SQL generation, live Cadence script execution, and EVM RPC calls into a single conversational interface. Core Features [#core-features] * **Natural Language to SQL** -- Ask questions in plain English (or Chinese) and get back structured results from the FlowIndex and Blockscout databases. * **Dual Database Access** -- Queries both the FlowIndex database (native Flow/Cadence data) and the Blockscout database (Flow EVM data). * **Live Cadence Execution** -- Runs read-only Cadence scripts on Flow mainnet to fetch live on-chain state such as balances, vault contents, and NFT ownership. * **EVM RPC Integration** -- Calls Flow EVM JSON-RPC methods (`eth_getBalance`, `eth_call`, `eth_getLogs`, etc.) for live EVM state. * **Data Visualization** -- Generates bar, line, pie, doughnut, and horizontal bar charts from query results. * **Web Search** -- Falls back to web search for real-time information not stored in databases (prices, news, protocol updates). * **API Fetch** -- Fetches data from a curated whitelist of public APIs including the Flow Access API, Blockscout REST API, CoinGecko, and Increment Finance. * **Skills System** -- Loads specialized knowledge on demand (e.g., Cadence syntax, Flow staking) to provide deeper answers. * **MCP Server** -- Exposes tools via the Model Context Protocol, allowing external AI agents to query Flow data. * **Multi-Model Support** -- Three chat modes: Fast (Haiku), Balanced (Sonnet), and Deep (Opus with extended thinking). Tech Stack [#tech-stack] | Component | Technology | | --------------------- | ----------------------------------------------------------- | | Chat Backend (Python) | FastAPI, Vanna v2, Anthropic SDK, psycopg | | MCP Server | FastMCP (streamable HTTP transport) | | Web Frontend | Next.js 16, React 19, Vercel AI SDK, Shadcn/UI, TailwindCSS | | LLM | Anthropic Claude (configurable model) | | Databases | PostgreSQL (FlowIndex + Blockscout, read-only) | | Vector Store | ChromaDB (optional, for agent memory) | | Deployment | Docker (multi-stage), Supervisor, nginx | How It Works [#how-it-works] 1. A user types a natural language question in the web chat interface. 2. The Next.js frontend streams the request to the Anthropic API with a system prompt containing database schemas, documentation, and example queries. 3. The LLM decides which tools to call -- SQL queries, Cadence scripts, EVM RPC, API fetches, or web search. 4. Tool results flow back through the conversation, and the LLM synthesizes a human-readable answer. 5. If the data is suitable for visualization, the LLM calls the `createChart` tool to render an interactive chart. Service Ports [#service-ports] | Service | Default Port | | ------------------------------------ | ------------ | | Python backend (Vanna UI + REST API) | 8084 | | MCP server | 8085 | | Next.js web frontend | 3001 | | nginx reverse proxy (Docker) | 80 | # Getting Started Getting Started [#getting-started] This guide walks through setting up the FlowIndex AI service for local development. Prerequisites [#prerequisites] * **Python 3.12+** -- for the backend server and MCP server * **Node.js 22+** and **Bun** -- for the Next.js web frontend * **PostgreSQL** -- a running FlowIndex database (the AI service connects read-only) * **Anthropic API key** -- required for LLM-powered SQL generation and chat Optional: * **Blockscout database** -- for Flow EVM queries (the service works without it, but EVM SQL tools will be unavailable) Project Structure [#project-structure] ``` ai/ └── chat/ ├── server.py # FastAPI backend (Vanna v2 agent + REST API) ├── mcp_server.py # MCP server (tool exposure for external agents) ├── client.py # Python client library ├── config.py # Environment variable configuration ├── db.py # Database query execution layer ├── train.py # System prompt builder (DDL + docs + examples) ├── training_data/ # Schema DDL, documentation, example queries │ ├── ddl/ # SQL table definitions │ ├── docs/ # Flow and EVM documentation │ └── queries/ # Example question-to-SQL pairs ├── web/ # Next.js chat frontend ├── requirements.txt # Python dependencies ├── Dockerfile # Multi-stage Docker build ├── nginx.conf # Reverse proxy config └── supervisord.conf # Process manager config ``` Environment Variables [#environment-variables] Copy the example file and fill in your values: ```bash cd ai/chat cp .env.example .env ``` Required Variables [#required-variables] | Variable | Description | Default | | ------------------------ | ------------------------------------------------------- | -------------------------------------------------------------- | | `ANTHROPIC_API_KEY` | Your Anthropic API key | (none) | | `FLOWINDEX_DATABASE_URL` | PostgreSQL connection string for the FlowIndex database | `postgresql://flowscan:secretpassword@localhost:5432/flowscan` | Optional Variables [#optional-variables] | Variable | Description | Default | | ------------------------- | ------------------------------------------------------------------- | ---------------------------- | | `BLOCKSCOUT_DATABASE_URL` | PostgreSQL connection string for the Blockscout (Flow EVM) database | (empty -- EVM SQL disabled) | | `LLM_PROVIDER` | LLM provider (`anthropic` or `openai`) | `anthropic` | | `LLM_MODEL` | Model identifier | `claude-sonnet-4-5-20250929` | | `QUERY_TIMEOUT_S` | SQL statement timeout in seconds | `30` | | `MAX_RESULT_ROWS` | Maximum rows returned per query | `500` | | `HOST` | Server bind address | `0.0.0.0` | | `PORT` | Python backend port | `8084` | | `MCP_PORT` | MCP server port | `8085` | | `API_TOKEN` | Bearer token for REST API authentication | (empty -- no auth) | | `MCP_AUTH_ENABLED` | Enable API key auth on the MCP server | `true` | | `MCP_ADMIN_KEY` | Admin API key for MCP (bypasses rate limits) | (empty) | | `MCP_RATE_LIMIT` | MCP requests per minute per key | `60` | | `BACKEND_URL` | FlowIndex Go backend URL (for developer key validation) | `http://localhost:8080` | | `CHROMA_PERSIST_DIR` | ChromaDB persistence directory | `./chroma_data` | Web Frontend Variables [#web-frontend-variables] | Variable | Description | Default | | ----------------- | ---------------------- | ----------------------------------------- | | `MCP_SERVER_URL` | URL of the MCP server | `http://localhost:8085/mcp` | | `CADENCE_MCP_URL` | Cadence MCP server URL | `https://cadence-mcp.up.railway.app/mcp` | | `EVM_MCP_URL` | EVM MCP server URL | `https://flow-evm-mcp.up.railway.app/mcp` | Running Locally [#running-locally] 1. Start the Python Backend [#1-start-the-python-backend] ```bash cd ai/chat pip install -r requirements.txt python server.py ``` This starts the Vanna v2 agent server on port 8084 with: * A web UI at `http://localhost:8084` * REST API endpoints at `http://localhost:8084/api/v1/` * Auto-generated API docs at `http://localhost:8084/docs` 2. Start the MCP Server [#2-start-the-mcp-server] In a separate terminal: ```bash cd ai/chat python mcp_server.py ``` This starts the MCP server on port 8085, exposing tools (`run_flowindex_sql`, `run_evm_sql`, `run_cadence`) and resources (database schemas, documentation). 3. Start the Web Frontend [#3-start-the-web-frontend] ```bash cd ai/chat/web bun install bun run dev ``` The Next.js chat interface starts on `http://localhost:3001`. It connects to the MCP server and external MCP services (Cadence MCP, EVM MCP) to provide the full tool suite. Running with Docker [#running-with-docker] The service ships as a single Docker image that runs all four processes (Python backend, MCP server, Next.js frontend, nginx) via Supervisor: ```bash # From the repository root docker build -f ai/chat/Dockerfile -t flowindex-ai . docker run -p 80:80 \ -e ANTHROPIC_API_KEY=sk-ant-... \ -e FLOWINDEX_DATABASE_URL=postgresql://... \ flowindex-ai ``` The nginx reverse proxy on port 80 routes traffic to the appropriate backend service. Using the REST API [#using-the-rest-api] The Python backend exposes a REST API for programmatic access: ```bash # Ask a question (generates SQL, executes it, returns results) curl -X POST http://localhost:8084/api/v1/ask \ -H "Content-Type: application/json" \ -d '{"question": "What are the 10 most recent blocks?"}' # Generate SQL only (no execution) curl -X POST http://localhost:8084/api/v1/generate_sql \ -H "Content-Type: application/json" \ -d '{"question": "How many transactions happened today?"}' # Execute raw SQL (SELECT only) curl -X POST http://localhost:8084/api/v1/run_sql \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT height, timestamp FROM raw.blocks ORDER BY height DESC LIMIT 5"}' # View query history curl http://localhost:8084/api/v1/history ``` Using the Python Client [#using-the-python-client] ```python from client import FlowEVMQuery q = FlowEVMQuery(base_url="http://localhost:8084") result = q.ask("What are the top 10 FLOW holders?") print(result["sql"]) for row in result["results"]: print(row) ``` # Architecture Architecture [#architecture] FlowIndex AI runs as a set of cooperating processes inside a single Docker container, managed by Supervisor and fronted by nginx. System Overview [#system-overview] ``` ┌──────────────────────────────────────────────┐ │ Docker Container │ │ │ User ──► nginx:80 ───┤──► Next.js frontend :3001 │ │ │ │ │ ├──► Anthropic API (LLM) │ │ ├──► MCP Server :8085 │ │ ├──► Cadence MCP (external) │ │ └──► EVM MCP (external) │ │ │ │ Python backend :8084 (Vanna v2 + REST) │ │ └──► PostgreSQL (read-only) │ │ │ │ MCP Server :8085 (FastMCP) │ │ ├──► PostgreSQL (FlowIndex, read-only) │ │ ├──► PostgreSQL (Blockscout, read-only)│ │ └──► Flow Access API (Cadence scripts) │ └──────────────────────────────────────────────┘ ``` Components [#components] Next.js Web Frontend [#nextjs-web-frontend] The chat interface is a Next.js 16 app using the Vercel AI SDK (`ai` package) for streaming LLM responses. It runs on port 3001. **Chat flow:** 1. The user sends a message from the browser. 2. The Next.js API route (`/api/chat`) creates MCP client connections to the local MCP server and two external MCP servers (Cadence MCP, EVM MCP). 3. It calls `streamText()` from the Vercel AI SDK with the Anthropic model, system prompt, and all available tools. 4. The LLM streams its response, invoking tools as needed. Tool results are fed back into the conversation for up to 15 steps. 5. The streamed response is sent to the browser in real time. **Chat modes:** | Mode | Model | Extended Thinking | | -------- | ------------- | ---------------------- | | Fast | Claude Haiku | No | | Balanced | Claude Sonnet | No | | Deep | Claude Opus | Yes (10k token budget) | Python Backend (Vanna v2) [#python-backend-vanna-v2] A FastAPI server on port 8084 that provides: * **Vanna v2 Agent** -- An AI agent built on the Vanna framework with Anthropic Claude as the LLM. It uses a system prompt constructed from database DDL, documentation, and example query pairs. * **REST API** -- Endpoints for programmatic access (`/api/v1/ask`, `/api/v1/generate_sql`, `/api/v1/run_sql`, `/api/v1/history`). * **Query history** -- An in-memory store of recent queries (up to 200 items) for the server's lifetime. The backend connects to PostgreSQL using psycopg (v3 for FlowIndex, v2 for Blockscout) and enforces read-only access by rejecting any non-SELECT SQL. MCP Server [#mcp-server] A FastMCP server on port 8085 that exposes Flow blockchain tools via the Model Context Protocol (streamable HTTP transport). This allows any MCP-compatible AI agent to query Flow data. **Tools:** | Tool | Description | | ------------------- | --------------------------------------------------------------------------------------------------------------------- | | `run_flowindex_sql` | Execute read-only SQL against the FlowIndex database (blocks, transactions, events, tokens, accounts, staking, stats) | | `run_evm_sql` | Execute read-only SQL against the Blockscout database (EVM blocks, transactions, tokens, smart contracts, logs) | | `run_cadence` | Execute a read-only Cadence script on Flow mainnet via the Flow Access API | **Resources (schema context):** | URI | Content | | ------------------------- | ------------------------------------- | | `schema://flowindex-ddl` | FlowIndex database table definitions | | `schema://blockscout-ddl` | Blockscout database table definitions | | `schema://docs` | Flow EVM database documentation | | `schema://cadence` | Cadence script reference | **Authentication:** The MCP server supports API key authentication with two tiers: * **Admin keys** -- bypass rate limits entirely. * **Developer keys** -- validated against the FlowIndex Go backend (`/auth/verify-key`), subject to a sliding-window rate limit (default: 60 requests/minute per key). Local requests (from `127.0.0.1`) bypass authentication, allowing the Next.js frontend to call the MCP server without a key inside the Docker container. LLM Integration [#llm-integration] System Prompt Construction [#system-prompt-construction] The system prompt is built at startup by `train.py`, which assembles: 1. **DDL** -- `CREATE TABLE` statements from `training_data/ddl/` (FlowIndex and Blockscout schemas). 2. **Documentation** -- Markdown files from `training_data/docs/` covering Flow EVM specifics, Cadence syntax, and core contract addresses. 3. **Example queries** -- Question-to-SQL pairs from `training_data/queries/` that serve as few-shot examples. This prompt is cached in memory and shared across all requests. Tool Selection [#tool-selection] The Next.js frontend provides the LLM with tools from three MCP servers plus built-in tools: | Source | Tools | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Local MCP (`localhost:8085`) | `run_flowindex_sql`, `run_evm_sql`, `run_cadence` | | Cadence MCP (external) | `cadence_check`, `search_docs`, `get_doc`, `browse_docs`, `cadence_hover`, `cadence_definition`, `cadence_symbols` | | EVM MCP (external) | `evm_rpc`, `evm_getBalance`, `evm_call`, `evm_getLogs` | | Built-in | `fetch_api`, `web_search`, `createChart`, `loadSkill` | The LLM chooses which tools to call based on the user's question. The system prompt includes a decision matrix mapping question types to the appropriate tool. Skills System [#skills-system] Skills are specialized knowledge modules stored as `SKILL.md` files in the `skills/` directory. Each skill has YAML frontmatter with a name and description. When a user's question matches a skill topic, the LLM calls the `loadSkill` tool to inject that skill's content into the conversation context. This keeps the base system prompt lean while allowing deep expertise on specific topics. Data Sources [#data-sources] FlowIndex Database [#flowindex-database] The primary data source, containing indexed native Flow blockchain data: * **Raw data** (`raw.*`) -- Blocks, transactions, events, scripts, lookup tables * **Derived data** (`app.*`) -- FT/NFT transfers, token holdings, smart contracts, accounts, staking nodes, DeFi events, daily stats, market prices * **Analytics** (`analytics.*`) -- Aggregated daily metrics Addresses are stored as lowercase text strings (e.g., `0x1654653399040a61`). Blockscout Database [#blockscout-database] Contains indexed Flow EVM data from the Blockscout explorer: * EVM blocks, transactions, and internal transactions * Token transfers and balances * Smart contract source code and verification data * Logs and event decoding Addresses are stored as `bytea`. Displayed as hex using `'0x' || encode(col, 'hex')`. Flow Access API [#flow-access-api] Used by the `run_cadence` tool for live on-chain queries. Cadence scripts are base64-encoded and sent to the Flow REST API (`/v1/scripts`). Results come back as JSON-Cadence values. Security [#security] * **Read-only SQL** -- All SQL queries are validated against a regex that rejects `INSERT`, `UPDATE`, `DELETE`, `DROP`, `ALTER`, `CREATE`, `TRUNCATE`, `GRANT`, `REVOKE`, `EXEC`, and `EXECUTE` statements. * **Statement timeout** -- SQL queries are limited to a configurable timeout (default 30 seconds). * **Row limit** -- Query results are capped at a configurable maximum (default 500 rows). * **API fetch whitelist** -- The `fetch_api` tool only allows HTTPS requests to a curated list of domains. * **MCP authentication** -- External MCP access requires a valid API key, with per-key rate limiting. * **Cadence scripts are read-only** -- They cannot modify on-chain state. Deployment [#deployment] In production, all four processes run inside a single Docker container: | Process | Manager | Command | | ---------------- | ---------- | ------------------------------------ | | Python backend | Supervisor | `python server.py` | | MCP server | Supervisor | `python mcp_server.py` | | Next.js frontend | Supervisor | `node server.js` (standalone output) | | nginx | Supervisor | `nginx -g "daemon off;"` | The Docker image is a multi-stage build: 1. **Stage 1** (Node) -- Installs dependencies, builds shared packages, builds the Next.js app into a standalone output. 2. **Stage 2** (Python) -- Installs Python dependencies, copies the backend code and the built frontend, runs `train.py` to validate the system prompt, and configures nginx + Supervisor. Exposed ports: 80 (nginx), 8084 (Python backend), 8085 (MCP), 3001 (Next.js). # Configuration The FlowIndex backend is configured entirely through environment variables. This page documents the key variables organized by function. Core [#core] | Variable | Default | Description | | ------------- | ------------ | ------------------------------------------------------------------------------ | | `DB_URL` | *(required)* | PostgreSQL connection string (e.g., `postgres://user:pass@host:5432/flowscan`) | | `PORT` | `8080` | HTTP port for the API server | | `START_BLOCK` | `0` | Block height to begin forward ingestion from | | `RAW_ONLY` | `false` | When `true`, disables all workers and derivers -- only raw ingestion runs | Flow Access Nodes [#flow-access-nodes] | Variable | Default | Description | | ---------------------------- | --------- | ------------------------------------------------------------------------------ | | `FLOW_ACCESS_NODE` | mainnet28 | Primary Flow gRPC endpoint | | `FLOW_ACCESS_NODES` | -- | Comma-separated gRPC endpoints for live ingestion (node pool) | | `FLOW_HISTORIC_ACCESS_NODES` | -- | Comma-separated gRPC endpoints for history backfill (supports per-spork nodes) | | `FLOW_ARCHIVE_NODE` | -- | Archive node endpoint for pre-spork data | Forward Ingester [#forward-ingester] | Variable | Default | Description | | ------------------------- | ------- | --------------------------------------------- | | `ENABLE_FORWARD_INGESTER` | `true` | Enable or disable the forward (live) ingester | | `LATEST_WORKER_COUNT` | `2` | Number of concurrent workers fetching blocks | | `LATEST_BATCH_SIZE` | `1` | Number of blocks per batch | Backward Ingester [#backward-ingester] | Variable | Default | Description | | ------------------------- | -------------- | ------------------------------------------------- | | `ENABLE_HISTORY_INGESTER` | `true` | Enable or disable the backward (history) ingester | | `HISTORY_WORKER_COUNT` | `5` | Number of concurrent workers for backfill | | `HISTORY_BATCH_SIZE` | `20` | Number of blocks per backfill batch | | `HISTORY_STOP_HEIGHT` | `0` (disabled) | Stop the backward ingester at this height | Derivers [#derivers] | Variable | Default | Description | | ------------------------------------ | ------- | -------------------------------------------------- | | `ENABLE_LIVE_DERIVERS` | `true` | Enable the forward and history Live Derivers | | `LIVE_DERIVERS_CHUNK` | `10` | Number of blocks processed per Live Deriver chunk | | `LIVE_DERIVERS_HEAD_BACKFILL_BLOCKS` | `100` | Blocks to seed on startup for immediate UI data | | `ENABLE_HISTORY_DERIVERS` | `true` | Enable the History Deriver safety-net scanner | | `HISTORY_DERIVERS_CHUNK` | `1000` | Blocks per History Deriver chunk | | `HISTORY_DERIVERS_SLEEP_MS` | `0` | Throttle delay (ms) between History Deriver chunks | Worker Toggles [#worker-toggles] Each worker can be individually enabled or disabled: | Variable | Default | Worker | | --------------------------------- | ------- | --------------------------------------------- | | `ENABLE_TOKEN_WORKER` | `true` | FT/NFT token transfers | | `ENABLE_EVM_WORKER` | `true` | EVM transaction mapping | | `ENABLE_META_WORKER` | `true` | Address transactions, account keys, contracts | | `ENABLE_ACCOUNTS_WORKER` | `true` | Account catalog | | `ENABLE_FT_HOLDINGS_WORKER` | `true` | FT balance tracking | | `ENABLE_NFT_OWNERSHIP_WORKER` | `true` | NFT ownership tracking | | `ENABLE_TOKEN_METADATA_WORKER` | `true` | On-chain FT/NFT metadata | | `ENABLE_TX_CONTRACTS_WORKER` | `true` | Transaction contract tagging | | `ENABLE_TX_METRICS_WORKER` | `true` | Per-transaction metrics | | `ENABLE_STAKING_WORKER` | `true` | Staking and epoch events | | `ENABLE_DEFI_WORKER` | `true` | DEX swap events | | `ENABLE_DAILY_STATS_WORKER` | `true` | Daily aggregate statistics | | `ENABLE_DAILY_BALANCE_WORKER` | `true` | Daily balance aggregation | | `ENABLE_NFT_ITEM_METADATA_WORKER` | `true` | Per-NFT metadata fetching | | `ENABLE_NFT_OWNERSHIP_RECONCILER` | `true` | NFT ownership verification | | `ENABLE_ANALYTICS_DERIVER_WORKER` | `true` | Analytics derivation | Worker Concurrency [#worker-concurrency] Each worker has configurable concurrency and range size: | Variable | Default | Description | | ----------------------------- | ------- | ---------------------------------- | | `TOKEN_WORKER_CONCURRENCY` | `1` | Parallel token worker instances | | `TOKEN_WORKER_RANGE` | `1000` | Block range per processing unit | | `EVM_WORKER_CONCURRENCY` | `1` | Parallel EVM worker instances | | `EVM_WORKER_RANGE` | `1000` | Block range per processing unit | | `META_WORKER_CONCURRENCY` | `1` | Parallel meta worker instances | | `META_WORKER_RANGE` | `1000` | Block range per processing unit | | `ACCOUNTS_WORKER_CONCURRENCY` | `1` | Parallel accounts worker instances | | `STAKING_WORKER_CONCURRENCY` | `1` | Parallel staking worker instances | | `DEFI_WORKER_CONCURRENCY` | `1` | Parallel DeFi worker instances | The pattern `{WORKER_NAME}_CONCURRENCY` and `{WORKER_NAME}_RANGE` applies to all workers. Rate Limiting [#rate-limiting] | Variable | Default | Description | | ------------------ | ------- | ---------------------------------------- | | `FLOW_RPC_RPS` | -- | Requests per second to Flow Access Nodes | | `FLOW_RPC_BURST` | -- | Burst allowance for RPC rate limiter | | `API_RATE_LIMIT_*` | -- | API rate limiting configuration | Database Pool [#database-pool] | Variable | Default | Description | | ------------------- | ------- | --------------------------------- | | `DB_MAX_OPEN_CONNS` | -- | Maximum open database connections | | `DB_MAX_IDLE_CONNS` | -- | Maximum idle database connections | Reorg Protection [#reorg-protection] | Variable | Default | Description | | ----------------- | ------- | -------------------------------------------------------- | | `MAX_REORG_DEPTH` | `1000` | Maximum rollback depth for chain reorganization handling | Market Data [#market-data] | Variable | Default | Description | | ------------------- | ------- | --------------------------------- | | `ENABLE_PRICE_FEED` | `false` | Enable CoinGecko price feed | | `PRICE_REFRESH_MIN` | -- | Price refresh interval in minutes | Authentication [#authentication] | Variable | Default | Description | | --------------------- | ------- | ------------------------------------------- | | `SUPABASE_DB_URL` | -- | Supabase PostgreSQL connection string | | `SUPABASE_JWT_SECRET` | -- | JWT secret for Supabase auth verification | | `ADMIN_TOKEN` | -- | Static admin API token | | `ADMIN_JWT_SECRET` | -- | JWT secret for admin authentication | | `ADMIN_ALLOWED_ROLES` | -- | Comma-separated list of allowed admin roles | Webhooks [#webhooks] | Variable | Default | Description | | ----------------- | ------- | ---------------------------------------------- | | `SVIX_AUTH_TOKEN` | -- | Svix authentication token for webhook delivery | | `SVIX_SERVER_URL` | -- | Svix server URL | Frontend [#frontend] | Variable | Default | Description | | ------------------- | ----------------------- | --------------------------------------- | | `VITE_API_URL` | `http://localhost:8080` | Backend API URL used by the frontend | | `VITE_SUPABASE_URL` | -- | Supabase gateway URL for authentication | # EVM Support Flow supports running EVM transactions natively through its Cadence smart contract layer. FlowIndex automatically detects and indexes these EVM transactions, making them searchable by both their Flow transaction ID and their EVM hash. How EVM Transactions Work on Flow [#how-evm-transactions-work-on-flow] On Flow, EVM transactions are executed through Cadence transactions that import the `EVM` contract. When a Cadence transaction calls into the EVM environment, it emits an `EVM.TransactionExecuted` event containing the full EVM transaction details. This means every EVM transaction on Flow has two identities: 1. A **Flow transaction ID** (the Cadence transaction that wrapped the EVM call) 2. An **EVM transaction hash** (the standard Ethereum-style hash) Detection [#detection] FlowIndex detects EVM transactions during ingestion by checking for `import EVM` in the transaction script content. When detected: * The transaction's `is_evm` flag is set to `true` in `raw.transactions` * The `evm_worker` processes the block to extract detailed EVM data EVM Worker Pipeline [#evm-worker-pipeline] The `evm_worker` runs as a Phase 1 processor in the deriver pipeline, processing blocks in parallel with other workers. For each block, it: 1. Scans events for `EVM.TransactionExecuted` event types 2. Decodes the event payload to extract: * **EVM transaction hash** * **From address** (EVM sender) * **To address** (EVM recipient) * **Value** (amount transferred) * **Gas used** * **EVM transaction status** (success/failure) 3. Maps the EVM hash back to the parent Cadence transaction ID 4. Stores the mapping in `app.evm_tx_hashes` A single Cadence transaction can contain **multiple EVM transactions**, so the mapping is one-to-many (one Flow tx ID to many EVM hashes). Database Schema [#database-schema] app.evm_tx_hashes [#appevm_tx_hashes] Maps Cadence transaction IDs to EVM transaction hashes. | Column | Type | Description | | -------------- | -------- | ------------------------------------------------ | | `block_height` | bigint | Block height | | `tx_id` | text | Flow (Cadence) transaction ID | | `evm_hash` | text | EVM transaction hash (lowercase, no `0x` prefix) | | `evm_from` | text | EVM sender address | | `evm_to` | text | EVM recipient address | | `evm_value` | numeric | Transfer value | | `evm_gas_used` | bigint | Gas consumed | | `evm_status` | smallint | Execution status | Storage Conventions [#storage-conventions] * EVM hashes are stored **lowercase without the `0x` prefix** * EVM addresses follow the same convention (lowercase, no `0x`) * The `raw.tx_lookup` table stores EVM hashes in the `evm_hash` column for fast lookups API Endpoints [#api-endpoints] List EVM Transactions [#list-evm-transactions] ``` GET /flow/evm/transaction ``` Returns a paginated list of EVM transactions with their Flow and EVM identifiers. Get EVM Transaction by Hash [#get-evm-transaction-by-hash] ``` GET /flow/evm/transaction/{hash} ``` Look up an EVM transaction by its EVM hash. Returns the EVM details along with the parent Cadence transaction. The `hash` parameter accepts EVM hashes both with and without the `0x` prefix. Get Transaction by ID [#get-transaction-by-id] ``` GET /flow/transaction/{id} ``` This endpoint also resolves EVM hashes. If you pass an EVM hash as the `{id}`, it will find the corresponding Cadence transaction. EVM Tokens [#evm-tokens] ``` GET /flow/evm/token GET /flow/evm/token/{address} GET /flow/evm/address/{address}/token ``` Query EVM token contracts and address token holdings. WebSocket [#websocket] EVM transactions are included in the real-time WebSocket feed. The `new_transaction` message includes an `is_evm` field and an `EVM` tag: ```json { "type": "new_transaction", "payload": { "id": "abc123...", "block_height": 142610500, "is_evm": true, "tags": ["EVM"], "status": "SEALED", "execution_status": "SUCCESS" } } ``` COA (Cadence-Owned Account) Mapping [#coa-cadence-owned-account-mapping] Flow-EVM uses Cadence-Owned Accounts (COAs) to bridge between the Cadence and EVM environments. FlowIndex tracks COA creation events and provides a lookup endpoint: ``` GET /flow/coa/{address} ``` Returns the mapping between a Flow address and its associated EVM COA address. # @flowindex/auth-ui @flowindex/auth-ui [#flowindexauth-ui] React components and hooks for authentication. Provides `AuthProvider`, `useAuth`, and `LoginModal` with support for OAuth (GitHub, Google), magic links, OTP verification, and passkey-based login and signing. ```bash bun add @flowindex/auth-ui ``` **Peer dependencies:** `react >= 18.0.0`, `react-dom >= 18.0.0` **Optional peer dependencies:** `framer-motion >= 11.0.0`, `lucide-react >= 0.300.0`, `input-otp >= 1.0.0` (required for `LoginModal`) Quick Start [#quick-start] Wrap your application with `AuthProvider` and configure it with your GoTrue URL: ```tsx import { AuthProvider } from '@flowindex/auth-ui'; function App() { return ( ); } ``` Use the `useAuth` hook in any child component: ```tsx import { useAuth } from '@flowindex/auth-ui'; function Profile() { const { user, loading, signOut, passkey } = useAuth(); if (loading) return
Loading...
; if (!user) return
Not logged in
; return (

Welcome, {user.email}

{passkey?.selectedAccount && (

Flow Address: {passkey.selectedAccount.flowAddress}

)}
); } ``` AuthProvider [#authprovider] The `AuthProvider` component manages the full auth lifecycle: session restoration from cookies, automatic token refresh, logout detection, and passkey state management. Configuration [#configuration] ```ts interface AuthConfig { gotrueUrl: string; // GoTrue auth endpoint URL (required) passkeyAuthUrl?: string; // Passkey auth edge function URL (enables passkey features) cookieDomain?: string; // Cross-subdomain cookie domain (e.g., '.yourdomain.com') enableLogoutDetection?: boolean; // Detect logout from other tabs/windows enableRoles?: boolean; // Parse roles/teams from JWT claims (default: true) rpId?: string; // WebAuthn Relying Party ID (default: 'flowindex.io') rpName?: string; // WebAuthn Relying Party name (default: 'FlowIndex') callbackPath?: string; // OAuth callback path (default: '/developer/callback') } ``` Session Behavior [#session-behavior] 1. On mount, loads tokens from the `fi_auth` cookie (with localStorage fallback) 2. If the access token is valid, restores the session immediately 3. If the access token is expired, attempts a silent refresh using the refresh token 4. Schedules automatic token refresh 60 seconds before expiry 5. When `enableLogoutDetection` is true, periodically checks the cookie and clears the session if it was removed (e.g., logout from another tab) useAuth Hook [#useauth-hook] ```ts const { user, // AuthUser | null accessToken, // string | null loading, // boolean signInWithProvider, // (provider: 'github' | 'google', redirectTo?: string) => void sendMagicLink, // (email: string, redirectTo?: string) => Promise verifyOtp, // (email: string, token: string) => Promise signOut, // () => void handleCallback, // (hash: string) => void applyTokenData, // (data: { access_token, refresh_token }) => void passkey, // PasskeyState | undefined } = useAuth(); ``` Passkey State [#passkey-state] When `passkeyAuthUrl` is configured, the `passkey` object is available with: ```ts interface PasskeyState { hasSupport: boolean; // WebAuthn is available in this browser hasBoundPasskey: boolean; // User has at least one registered passkey accounts: PasskeyAccount[]; // All passkey-linked wallet accounts passkeys: PasskeyInfo[]; // Registered passkey metadata selectedAccount: PasskeyAccount | null; loading: boolean; selectAccount(credentialId: string): void; register(walletName?: string): Promise<{ credentialId: string; publicKeySec1Hex: string }>; login(): Promise; startConditionalLogin(onSuccess?: () => void): AbortController; sign(messageHex: string): Promise; getFlowAuthz(address: string, keyIndex: number): (account: any) => any; provisionAccounts(credentialId: string): Promise; pollProvisionTx(txId: string, network: 'mainnet' | 'testnet'): Promise; saveProvisionedAddress(credentialId: string, network: string, address: string): Promise; refreshState(): Promise; } ``` The `getFlowAuthz` method returns an FCL-compatible authorization function, allowing passkey-signed Flow transactions: ```ts import * as fcl from '@onflow/fcl'; const { passkey } = useAuth(); const account = passkey.selectedAccount; const authz = passkey.getFlowAuthz(account.flowAddress, 0); const txId = await fcl.mutate({ cadence: `transaction { execute { log("Hello") } }`, proposer: authz, payer: authz, authorizations: [authz], }); ``` LoginModal [#loginmodal] A pre-built, animated login modal with support for OAuth, magic links, OTP, and passkey login. ```tsx import { LoginModal } from '@flowindex/auth-ui'; function Header() { const [showLogin, setShowLogin] = useState(false); return ( <> setShowLogin(false)} redirectTo="/dashboard" /> ); } ``` **Props:** | Prop | Type | Description | | ------------ | ----------------- | ---------------------------------- | | `open` | boolean | Whether the modal is visible | | `onClose` | () => void | Called when the modal should close | | `redirectTo` | string (optional) | URL to redirect to after login | | `className` | string (optional) | Additional CSS classes | The modal automatically detects available auth methods based on the `AuthProvider` configuration and renders the appropriate UI (passkey button, OAuth buttons, email input, OTP entry). Re-exported Types [#re-exported-types] For convenience, `@flowindex/auth-ui` re-exports all types from `@flowindex/auth-core`: ```ts export type { AuthUser, StoredTokens, TokenData, PasskeyAccount, PasskeyInfo, ProvisionResult, } from '@flowindex/auth-core'; ``` # @flowindex/flow-passkey @flowindex/flow-passkey [#flowindexflow-passkey] Low-level SDK for signing Flow blockchain transactions with WebAuthn passkeys. Implements [FLIP-264](https://github.com/onflow/flips/blob/main/protocol/20230609-webauthn-support.md) for passkey-based transaction authorization on Flow. ```bash bun add @flowindex/flow-passkey ``` **Optional peer dependency:** `@onflow/fcl >= 1.0.0` (required for `createPasskeyAuthz`) Features [#features] * **WebAuthn Credential Management** -- Create and get passkey credentials using the Web Authentication API * **Flow Transaction Signing** -- Sign Flow transactions with passkeys (FLIP-264 compatible) * **FCL Authorization** -- Create FCL-compatible authorization functions for passkey signing * **Transaction Encoding** -- RLP encoding of Flow transaction payloads and envelopes * **DER to Raw Conversion** -- Convert WebAuthn DER-encoded signatures to raw P-256 (r || s) format * **FLIP-264 Extension Data** -- Build the authenticator/client data extension payload required by Flow Transaction Signing [#transaction-signing] The primary use case is signing Flow transactions with a passkey: ```ts import { signFlowTransaction } from '@flowindex/flow-passkey'; const { signature, extensionData } = await signFlowTransaction({ messageHex: '...', // Hex-encoded transaction message credentialId: 'base64url-credential-id', rpId: 'yourdomain.com', }); ``` The signing flow: 1. SHA-256 hashes the message bytes (per FLIP-264 requirements) 2. Gets a WebAuthn assertion using the hash as the challenge 3. Converts the DER-encoded ECDSA signature to raw P-256 format (r || s, 64 bytes) 4. Builds FLIP-264 extension data from the authenticator and client data FCL Authorization [#fcl-authorization] Create an FCL-compatible authorization function for passkey signing: ```ts import { createPasskeyAuthz } from '@flowindex/flow-passkey'; import * as fcl from '@onflow/fcl'; const authz = createPasskeyAuthz({ address: '0x1234567890abcdef', keyIndex: 0, credentialId: 'base64url-credential-id', rpId: 'yourdomain.com', }); const txId = await fcl.mutate({ cadence: ` transaction { execute { log("Signed with passkey!") } } `, proposer: authz, payer: authz, authorizations: [authz], }); ``` The authorization function handles message encoding, passkey assertion, signature conversion, and extension data construction automatically. WebAuthn Primitives [#webauthn-primitives] Lower-level functions for direct WebAuthn operations: Create a Passkey Credential [#create-a-passkey-credential] ```ts import { createPasskeyCredential } from '@flowindex/flow-passkey'; const credential = await createPasskeyCredential({ rpId: 'yourdomain.com', rpName: 'Your App', challenge: challengeBytes, // Uint8Array userId: userIdBytes, // Uint8Array userName: 'user@example.com', excludeCredentials: [], // optional }); // credential.credentialId - base64url credential ID // credential.publicKeySec1Hex - SEC1 uncompressed public key (04 || x || y) // credential.attestationResponse - raw AuthenticatorAttestationResponse // credential.rawId - Uint8Array ``` Uses ES256 (P-256) key algorithm and requests resident key with user verification. Get a Passkey Assertion [#get-a-passkey-assertion] ```ts import { getPasskeyAssertion } from '@flowindex/flow-passkey'; const assertion = await getPasskeyAssertion({ rpId: 'yourdomain.com', challenge: challengeBytes, // Uint8Array allowCredentials: [{ // optional id: 'base64url-credential-id', type: 'public-key', }], mediation: 'conditional', // optional, for conditional UI signal: abortController.signal, // optional }); // assertion.credentialId - string // assertion.authenticatorData - Uint8Array // assertion.clientDataJSON - Uint8Array // assertion.signature - Uint8Array (DER-encoded) // assertion.rawId - Uint8Array ``` Encoding Utilities [#encoding-utilities] ```ts import { encodeTransactionPayload, encodeTransactionEnvelope, encodeMessageFromSignable, derToP256Raw, buildExtensionData, sha256, sha3_256, TRANSACTION_DOMAIN_TAG, } from '@flowindex/flow-passkey'; // Convert DER ECDSA signature to raw r||s (64 bytes) const rawSig = derToP256Raw(derSignatureBytes); // Build FLIP-264 extension data for a passkey assertion const extData = buildExtensionData(authenticatorData, clientDataJSON); // Hash with SHA-256 const digest = await sha256(messageBytes); ``` Byte Utilities [#byte-utilities] ```ts import { bytesToHex, hexToBytes, base64UrlToBytes, bytesToBase64Url } from '@flowindex/flow-passkey'; const hex = bytesToHex(new Uint8Array([0xab, 0xcd])); // "abcd" const bytes = hexToBytes("abcd"); // Uint8Array([0xab, 0xcd]) const b64 = bytesToBase64Url(bytes); // base64url string const back = base64UrlToBytes(b64); // Uint8Array ``` Types [#types] ```ts interface PasskeySignResult { signature: string; // Hex-encoded r||s (128 hex chars) extensionData: string; // Hex-encoded FLIP-264 extension data } interface PasskeyCredentialResult { credentialId: string; attestationResponse: AuthenticatorAttestationResponse; rawId: Uint8Array; type: string; publicKeySec1Hex: string; } interface PasskeyAssertionResult { credentialId: string; authenticatorData: Uint8Array; clientDataJSON: Uint8Array; signature: Uint8Array; rawId: Uint8Array; } ``` # @flowindex/auth-core @flowindex/auth-core [#flowindexauth-core] Framework-agnostic authentication primitives for applications that use GoTrue (Supabase Auth) with passkey support. Provides JWT parsing, token refresh, cookie-based session management, and a passkey auth client. ```bash bun add @flowindex/auth-core ``` Features [#features] * **JWT Helpers** -- Parse, validate, and extract user data from JWT tokens * **Token Refresh** -- Automatic token refresh via GoTrue's refresh\_token grant * **Cookie Management** -- Cross-subdomain session persistence using the `fi_auth` cookie with localStorage fallback * **GoTrue Integration** -- HTTP helpers for GoTrue endpoints (magic link, OTP, OAuth) * **Passkey Auth Client** -- WebAuthn registration, login, and Flow account provisioning Exports [#exports] JWT Helpers [#jwt-helpers] ```ts import { parseJwt, isExpired, secondsUntilExpiry, userFromToken } from '@flowindex/auth-core'; // Decode a JWT payload const payload = parseJwt(token); // Check if a token is expired (or within 5 seconds of expiry) if (isExpired(token)) { // refresh needed } // Get seconds until expiry const ttl = secondsUntilExpiry(token); // Extract an AuthUser from a JWT const user = userFromToken(token); // { id: "uuid", email: "user@example.com", roles: ["admin"], teams: ["eng"] } // Disable role/team parsing for simpler user objects const simpleUser = userFromToken(token, { enableRoles: false }); // { id: "uuid", email: "user@example.com" } ``` The `userFromToken` function extracts roles and teams from multiple JWT claim locations: root-level `role`/`roles` fields, `app_metadata`, and `user_metadata`. Cookie / Token Storage [#cookie--token-storage] ```ts import { loadStoredTokens, persistTokens, clearTokens } from '@flowindex/auth-core'; // Load tokens from cookie (primary) with localStorage fallback const tokens = loadStoredTokens(); // { accessToken: "...", refreshToken: "..." } | null // Save tokens to both cookie and localStorage persistTokens(accessToken, refreshToken, { cookieDomain: '.yourdomain.com', // optional, defaults to '.flowindex.io' storageKey: 'your_auth_key', // optional, defaults to 'flowindex_dev_auth' }); // Clear all stored tokens clearTokens(); ``` The cookie (`fi_auth`) is set as a cross-subdomain cookie with `secure` and `samesite=lax` attributes. This enables session sharing across subdomains. GoTrue Helpers [#gotrue-helpers] ```ts import { gotruePost, refreshAccessToken, buildOAuthRedirectUrl } from '@flowindex/auth-core'; // Generic GoTrue POST const data = await gotruePost(gotrueUrl, '/magiclink', { email: 'user@example.com' }); // Refresh an expired access token const tokens = await refreshAccessToken(gotrueUrl, refreshToken); // { access_token: "...", refresh_token: "..." } // Build an OAuth redirect URL const url = buildOAuthRedirectUrl(gotrueUrl, 'github', 'https://app.example.com/callback'); ``` Passkey Auth Client [#passkey-auth-client] ```ts import { createPasskeyAuthClient } from '@flowindex/auth-core'; const client = createPasskeyAuthClient({ passkeyAuthUrl: 'https://your-api.com/functions/v1/passkey-auth', rpId: 'yourdomain.com', rpName: 'Your App', }); // Register a new passkey for the authenticated user const { credentialId, publicKeySec1Hex } = await client.register(accessToken, 'My Wallet'); // Login with a passkey const { tokenHash, email } = await client.login(); // List passkeys for the authenticated user const passkeys = await client.listPasskeys(accessToken); // List wallet accounts (passkey-linked Flow addresses) const accounts = await client.listAccounts(accessToken); // Provision Flow accounts for a passkey credential const result = await client.provisionAccounts(accessToken, credentialId); // { networks: { mainnet: { txId: "..." }, testnet: { txId: "..." } }, publicKeySec1Hex: "04..." } // Poll for a provision transaction to seal const address = await client.pollProvisionTx(txId, 'mainnet'); // Save a provisioned address await client.saveProvisionedAddress(accessToken, credentialId, 'mainnet', address); ``` Types [#types] ```ts interface AuthUser { id: string; email: string; role?: string; roles?: string[]; team?: string; teams?: string[]; } interface StoredTokens { accessToken: string; refreshToken: string; } interface TokenData { access_token: string; refresh_token: string; } type OAuthProvider = 'github' | 'google'; interface PasskeyAccount { credentialId: string; flowAddress: string; flowAddressTestnet?: string; publicKeySec1Hex: string; authenticatorName?: string; } interface PasskeyInfo { id: string; authenticatorName?: string; deviceType?: string; backedUp?: boolean; createdAt?: string; lastUsedAt?: string; } interface PasskeyClientConfig { passkeyAuthUrl: string; rpId: string; rpName: string; } ``` # @flowindex/flow-ui @flowindex/flow-ui [#flowindexflow-ui] A shared UI component library built on Radix UI primitives with TailwindCSS styling, plus Flow blockchain utility functions for address formatting, token display, NFT media resolution, and transaction activity parsing. ```bash bun add @flowindex/flow-ui ``` **Peer dependencies:** `react >= 18.0.0`, `react-dom >= 18.0.0`, `lucide-react >= 0.300.0` **Optional peer dependency:** `react-day-picker >= 9.0.0` (for `Calendar` component) TailwindCSS Setup [#tailwindcss-setup] The package includes a TailwindCSS preset for consistent styling: ```js // tailwind.config.js module.exports = { presets: [require('@flowindex/flow-ui/tailwind-preset')], // your config... }; ``` UI Components [#ui-components] All UI components are built on [Radix UI](https://www.radix-ui.com/) primitives and styled with TailwindCSS using `class-variance-authority` for variant management. Base Components [#base-components] | Component | Description | | ----------- | -------------------------------------------------------------------------------------------------------------------------------- | | `Button` | Button with variants: `default`, `destructive`, `outline`, `secondary`, `ghost`, `link` and sizes: `default`, `sm`, `lg`, `icon` | | `Input` | Styled text input | | `Textarea` | Styled textarea | | `Label` | Form label | | `Badge` | Status badge with variants | | `Separator` | Visual separator | | `Switch` | Toggle switch | Layout Components [#layout-components] | Component | Description | | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | `Card`, `CardHeader`, `CardContent`, `CardFooter`, `CardTitle`, `CardDescription` | Card container with header/content/footer sections | | `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`, `TableCaption`, `TableFooter` | Data table | | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | Tab navigation | Overlay Components [#overlay-components] | Component | Description | | -------------------------------------------------------------------------------------------------------------- | ---------------------- | | `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription` | Modal dialog | | `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, ... | Dropdown menu | | `Popover`, `PopoverTrigger`, `PopoverContent` | Popover | | `Select`, `SelectTrigger`, `SelectContent`, `SelectItem`, ... | Select dropdown | | `Command`, `CommandInput`, `CommandList`, `CommandItem`, ... | Command palette (cmdk) | Specialized Components [#specialized-components] | Component | Description | | ------------------------------------------- | -------------------------------------------------- | | `Avatar`, `AvatarImage`, `AvatarFallback` | User/entity avatar | | `Calendar` | Date picker calendar (requires `react-day-picker`) | | `InputOTP`, `InputOTPGroup`, `InputOTPSlot` | OTP code input | Flow Display Components [#flow-display-components] Custom components for displaying Flow blockchain data: GlassCard [#glasscard] A card with a frosted glass visual effect. ```tsx import { GlassCard } from '@flowindex/flow-ui';

Account Balance

1,234.56 FLOW

``` TokenIcon [#tokenicon] Displays a token logo with fallback. ```tsx import { TokenIcon } from '@flowindex/flow-ui'; ``` UsdValue [#usdvalue] Formatted USD value display. ```tsx import { UsdValue } from '@flowindex/flow-ui'; ``` VerifiedBadge [#verifiedbadge] Verification indicator for tokens and contracts. ```tsx import { VerifiedBadge } from '@flowindex/flow-ui'; ``` EVMBridgeBadge [#evmbridgebadge] Indicator for EVM-bridged assets. ```tsx import { EVMBridgeBadge } from '@flowindex/flow-ui'; ``` ImageWithFallback [#imagewithfallback] Image component that gracefully handles loading failures. ```tsx import { ImageWithFallback } from '@flowindex/flow-ui'; ``` ActivityRow [#activityrow] Transaction activity row with transfer previews and badges. ```tsx import { ActivityRow } from '@flowindex/flow-ui'; ``` Utility Functions [#utility-functions] Address Utilities [#address-utilities] ```ts import { normalizeAddress, formatShort } from '@flowindex/flow-ui'; normalizeAddress('0x1234567890abcdef'); // "1234567890abcdef" formatShort('1234567890abcdef'); // "0x1234...cdef" ``` Token Utilities [#token-utilities] ```ts import { getTokenLogoURL } from '@flowindex/flow-ui'; const url = getTokenLogoURL({ symbol: 'FLOW', logo_url: null }); ``` NFT Utilities [#nft-utilities] ```ts import { resolveIPFS, getNFTThumbnail, getNFTMedia, getCollectionPreviewVideo } from '@flowindex/flow-ui'; resolveIPFS('ipfs://Qm...'); // "https://ipfs.io/ipfs/Qm..." ``` Formatting [#formatting] ```ts import { formatStorageBytes, formatNumber } from '@flowindex/flow-ui'; formatStorageBytes(1048576); // "1.00 MB" formatNumber(1234567); // "1,234,567" ``` Cadence Utilities [#cadence-utilities] ```ts import { decodeCadenceValue, getStoragePathId, storagePathStr } from '@flowindex/flow-ui'; // Decode a Cadence JSON-CDC value to a readable format const decoded = decodeCadenceValue(cadenceJsonValue); ``` Activity Utilities [#activity-utilities] ```ts import { deriveActivityType, deriveAllActivityBadges, buildSummaryLine, deriveTransferPreview, formatRelativeTime, formatAbsoluteTime, } from '@flowindex/flow-ui'; ``` Functions for parsing transaction events into human-readable activity summaries, transfer previews, and badge lists. Used by the `ActivityRow` component. Time Utilities [#time-utilities] ```ts import { formatRelativeTime, formatAbsoluteTime } from '@flowindex/flow-ui'; formatRelativeTime('2024-01-01T00:00:00Z'); // "3 months ago" formatAbsoluteTime('2024-01-01T00:00:00Z'); // "Jan 1, 2024 12:00 AM" ``` CSS Utility [#css-utility] ```ts import { cn } from '@flowindex/flow-ui'; // Merge TailwindCSS classes with conflict resolution cn('px-4 py-2', 'px-6'); // "px-6 py-2" ``` The `cn` function combines `clsx` and `tailwind-merge` for safe class name composition. # @flowindex/agent-wallet @flowindex/agent-wallet [#flowindexagent-wallet] An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that gives AI agents a full Flow blockchain wallet. Supports local key signing, cloud-custodied wallets, and passkey-based approval flows. Includes native Flow EVM support. ```bash bun add @flowindex/agent-wallet ``` **Requirements:** Node.js 20+ Features [#features] * **MCP Protocol** -- Exposes wallet operations as MCP tools that any MCP-compatible AI client (Claude, etc.) can call * **Multiple Signer Types** -- Local mnemonic, local private key, cloud wallet (JWT-authenticated), and interactive cloud login * **Flow + Flow EVM** -- Full support for both Cadence transactions and EVM operations (transfers, ERC-20, contract calls) * **Cadence Template Library** -- Built-in library of Cadence transaction and script templates (token transfers, NFT operations, EVM bridging, etc.) * **Transaction Approval** -- Optional approval queue that lets agents propose transactions for human review before execution * **Account Discovery** -- Automatically discovers Flow accounts associated with a public key Architecture [#architecture] The server registers five groups of MCP tools: | Tool Group | Tools | Description | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- | | **Wallet** | `wallet_status`, `wallet_login`, `wallet_login_status` | Wallet configuration, status, and interactive login | | **Templates** | `list_templates`, `get_template`, `execute_script`, `execute_template` | Cadence template browsing and execution | | **Approval** | `confirm_transaction`, `cancel_transaction`, `list_pending` | Transaction approval queue management | | **Flow Query** | `get_account`, `get_flow_balance`, `get_ft_balance`, `get_nft_collection`, `get_transaction` | Read-only Flow blockchain queries | | **EVM** | `evm_wallet_address`, `evm_get_balance`, `evm_get_token_balance`, `evm_transfer`, `evm_transfer_erc20`, `evm_read_contract`, `evm_write_contract`, `evm_get_transaction` | Flow EVM operations | Signer Types [#signer-types] The signer type is determined automatically from environment variables: | Priority | Signer Type | Trigger | Behavior | | -------- | ------------------- | ------------------------- | ---------------------------------------------------------------------------- | | 1 | `local-mnemonic` | `FLOW_MNEMONIC` is set | Derives Flow key via BIP-44 (`m/44'/539'/0'/0/0`), EVM key via standard path | | 2 | `local-key` | `FLOW_PRIVATE_KEY` is set | Uses the raw private key directly | | 3 | `cloud` | `FLOWINDEX_TOKEN` is set | Delegates signing to the FlowIndex custodial API | | 4 | `cloud-interactive` | None of the above | Requires user to authenticate via `wallet_login` before signing | Quick Example [#quick-example] Run the server with a local key: ```bash FLOW_PRIVATE_KEY=abc123... \ FLOW_ADDRESS=0x1234567890abcdef \ FLOW_NETWORK=mainnet \ npx @flowindex/agent-wallet ``` Or add it to your Claude Desktop MCP configuration: ```json { "mcpServers": { "flow-wallet": { "command": "npx", "args": ["@flowindex/agent-wallet"], "env": { "FLOW_PRIVATE_KEY": "your-private-key", "FLOW_ADDRESS": "0x1234567890abcdef", "FLOW_NETWORK": "mainnet" } } } } ``` # API Reference API Reference [#api-reference] The `@flowindex/agent-wallet` MCP server exposes the following tools. Each tool is callable by any MCP-compatible client. Wallet Tools [#wallet-tools] wallet_status [#wallet_status] Returns the current wallet configuration and status. **Parameters:** None **Returns:** ```json { "signer_type": "local", "flow_address": "1234567890abcdef", "evm_address": "0x...", "key_index": 0, "sig_algo": "ECDSA_secp256k1", "hash_algo": "SHA2_256", "network": "mainnet", "approval_required": true, "headless": true } ``` wallet_login [#wallet_login] Initiates an interactive login flow for the cloud wallet. Returns a login URL the user must visit. **Parameters:** None **Returns:** ```json { "status": "pending", "login_url": "https://flowindex.io/wallet/login?session=...", "session_id": "abc-123", "expires_in": 300 } ``` wallet_login_status [#wallet_login_status] Checks the status of an interactive login session. If authenticated, activates the cloud signer. **Parameters:** | Name | Type | Description | | ------------ | ------ | ----------------------------------------- | | `session_id` | string | The session ID returned by `wallet_login` | **Returns (authenticated):** ```json { "status": "authenticated", "flow_address": "1234567890abcdef", "key_index": 0 } ``` Template Tools [#template-tools] list_templates [#list_templates] Lists available Cadence transaction and script templates. **Parameters:** | Name | Type | Required | Description | | ---------- | ------ | -------- | ----------------------------------------------------------------- | | `category` | string | No | Filter by category (e.g., `token`, `collection`, `evm`, `bridge`) | **Returns:** Array of template summaries with `name`, `category`, `type`, `description`, and `arg_count`. get_template [#get_template] Retrieves the full Cadence source code and argument schema for a named template. **Parameters:** | Name | Type | Required | Description | | ------ | ------ | -------- | ------------------------------------------ | | `name` | string | Yes | Template name (e.g., `transfer_tokens_v3`) | **Returns:** Template object with `name`, `category`, `type`, `description`, `cadence` (source code), and `args` (array of `{ name, type, description }`). execute_script [#execute_script] Executes a read-only Cadence script on the Flow network. Provide either a template name or raw Cadence code. **Parameters:** | Name | Type | Required | Description | | --------------- | ------ | -------- | ---------------------------------- | | `template_name` | string | No | Name of a script template | | `code` | string | No | Raw Cadence script code | | `args` | array | No | Script arguments in FCL arg format | At least one of `template_name` or `code` must be provided. execute_template [#execute_template] Executes a Cadence transaction template. If approval is required and the signer is headless, the transaction is queued for manual approval. **Parameters:** | Name | Type | Required | Description | | --------------- | ------ | -------- | -------------------------------------------------- | | `template_name` | string | Yes | Name of the transaction template | | `args` | object | Yes | Named arguments matching the template's arg schema | **Returns (immediate execution):** ```json { "status": "sealed", "tx_id": "abc123...", "block_height": 12345678, "events": [{ "type": "A.xxx.FlowToken.TokensWithdrawn", "data": {} }] } ``` **Returns (approval required):** ```json { "status": "pending_approval", "tx_id": "uuid", "summary": "transfer_tokens_v3(recipient=0x..., amount=1.0)" } ``` Approval Tools [#approval-tools] confirm_transaction [#confirm_transaction] Approves and executes a pending transaction. **Parameters:** | Name | Type | Required | Description | | ------- | ------ | -------- | -------------------------- | | `tx_id` | string | Yes | The pending transaction ID | **Returns:** Same as `execute_template` immediate execution result. cancel_transaction [#cancel_transaction] Cancels and discards a pending transaction. **Parameters:** | Name | Type | Required | Description | | ------- | ------ | -------- | -------------------------- | | `tx_id` | string | Yes | The pending transaction ID | list_pending [#list_pending] Lists all transactions currently queued and awaiting approval. **Parameters:** None **Returns:** ```json { "count": 1, "transactions": [ { "id": "uuid", "summary": "transfer_tokens_v3(...)", "createdAt": 1700000000000 } ] } ``` Flow Query Tools [#flow-query-tools] get_account [#get_account] Returns detailed information about a Flow account including keys, contracts, and storage usage. **Parameters:** | Name | Type | Required | Description | | --------- | ------ | -------- | ----------------------------------------------- | | `address` | string | Yes | Flow address (hex, with or without `0x` prefix) | get_flow_balance [#get_flow_balance] Returns the native FLOW token balance for a Flow account. **Parameters:** | Name | Type | Required | Description | | --------- | ------ | -------- | ------------ | | `address` | string | Yes | Flow address | get_ft_balance [#get_ft_balance] Returns all fungible token vault balances for a Flow account (FLOW, USDC, stFlow, etc.). **Parameters:** | Name | Type | Required | Description | | --------- | ------ | -------- | ------------ | | `address` | string | Yes | Flow address | get_nft_collection [#get_nft_collection] Returns all NFT collections and items held by a Flow account. **Parameters:** | Name | Type | Required | Description | | --------- | ------ | -------- | ------------ | | `address` | string | Yes | Flow address | get_transaction [#get_transaction] Returns full details for a Flow transaction by its ID, including events, status, and error messages. **Parameters:** | Name | Type | Required | Description | | ------- | ------ | -------- | -------------------------------------- | | `tx_id` | string | Yes | Flow transaction ID (64-character hex) | EVM Tools [#evm-tools] evm_wallet_address [#evm_wallet_address] Returns the current EVM EOA address derived from the configured signer. **Parameters:** None evm_get_balance [#evm_get_balance] Returns the native FLOW balance of an EVM address on Flow EVM. **Parameters:** | Name | Type | Required | Description | | --------- | ------ | -------- | --------------------- | | `address` | string | Yes | EVM address (`0x...`) | evm_get_token_balance [#evm_get_token_balance] Returns the ERC-20 token balance for an address, including symbol and decimals. **Parameters:** | Name | Type | Required | Description | | --------------- | ------ | -------- | ----------------------------- | | `token_address` | string | Yes | ERC-20 token contract address | | `owner` | string | Yes | Address to check balance for | evm_transfer [#evm_transfer] Sends native FLOW on Flow EVM from the agent wallet to a recipient address. **Parameters:** | Name | Type | Required | Description | | -------- | ------ | -------- | ------------------------------ | | `to` | string | Yes | Recipient EVM address | | `amount` | string | Yes | Amount in FLOW (e.g., `"1.5"`) | evm_transfer_erc20 [#evm_transfer_erc20] Transfers an ERC-20 token on Flow EVM from the agent wallet to a recipient. **Parameters:** | Name | Type | Required | Description | | --------------- | ------ | -------- | ------------------------------------------------ | | `token_address` | string | Yes | ERC-20 token contract address | | `to` | string | Yes | Recipient EVM address | | `amount` | string | Yes | Amount in human-readable units (e.g., `"100.0"`) | | `decimals` | number | No | Token decimals (default: 18) | evm_read_contract [#evm_read_contract] Calls a read-only (view/pure) function on an EVM smart contract. **Parameters:** | Name | Type | Required | Description | | ------------------ | ------ | -------- | -------------------------------- | | `contract_address` | string | Yes | Contract address | | `abi` | string | Yes | ABI as a JSON array string | | `function_name` | string | Yes | Function name to call | | `args` | string | No | Arguments as a JSON array string | evm_write_contract [#evm_write_contract] Calls a state-changing function on an EVM smart contract. **Parameters:** | Name | Type | Required | Description | | ------------------ | ------ | -------- | ----------------------------------- | | `contract_address` | string | Yes | Contract address | | `abi` | string | Yes | ABI as a JSON array string | | `function_name` | string | Yes | Function name to call | | `args` | string | No | Arguments as a JSON array string | | `value` | string | No | Native FLOW to send (e.g., `"0.5"`) | evm_get_transaction [#evm_get_transaction] Returns details of an EVM transaction by its hash, including status, block number, gas used, and receipt. **Parameters:** | Name | Type | Required | Description | | --------- | ------ | -------- | -------------------------- | | `tx_hash` | string | Yes | Transaction hash (`0x...`) | # Getting Started Getting Started [#getting-started] This guide walks through setting up `@flowindex/agent-wallet` with different signer configurations. Installation [#installation] ```bash bun add @flowindex/agent-wallet ``` Or install globally for CLI use: ```bash npm install -g @flowindex/agent-wallet ``` Configuration [#configuration] All configuration is done through environment variables. The server auto-detects the signer type based on which variables are set. Environment Variables [#environment-variables] | Variable | Required | Default | Description | | ------------------- | -------- | ---------------------- | --------------------------------------------------------- | | `FLOW_NETWORK` | No | `mainnet` | Flow network (`mainnet` or `testnet`) | | `FLOW_MNEMONIC` | No | -- | BIP-39 mnemonic phrase (triggers `local-mnemonic` signer) | | `FLOW_PRIVATE_KEY` | No | -- | Hex-encoded private key (triggers `local-key` signer) | | `FLOW_ADDRESS` | No | -- | Explicit Flow address (auto-discovered if omitted) | | `FLOW_KEY_INDEX` | No | `0` | Key index on the Flow account | | `FLOW_SIG_ALGO` | No | `ECDSA_secp256k1` | Signature algorithm (`ECDSA_P256` or `ECDSA_secp256k1`) | | `FLOW_HASH_ALGO` | No | `SHA2_256` | Hash algorithm (`SHA2_256` or `SHA3_256`) | | `EVM_PRIVATE_KEY` | No | -- | Dedicated EVM private key (falls back to Flow key) | | `EVM_ACCOUNT_INDEX` | No | `0` | EVM derivation index when using mnemonic | | `FLOWINDEX_TOKEN` | No | -- | FlowIndex API JWT token (triggers `cloud` signer) | | `FLOWINDEX_URL` | No | `https://flowindex.io` | FlowIndex API base URL | | `APPROVAL_REQUIRED` | No | `true` | Require manual approval for transactions | Option 1: Local Private Key [#option-1-local-private-key] The simplest setup. Provide a raw private key and the associated Flow address: ```bash export FLOW_PRIVATE_KEY="your-64-char-hex-private-key" export FLOW_ADDRESS="0x1234567890abcdef" export FLOW_NETWORK="testnet" ``` If you omit `FLOW_ADDRESS`, the server will attempt to discover accounts associated with the public key using the FlowIndex key indexer. Option 2: Mnemonic Phrase [#option-2-mnemonic-phrase] Derive keys from a BIP-39 mnemonic. The Flow key uses path `m/44'/539'/0'/0/0` (secp256k1), and the EVM key uses the standard Ethereum path: ```bash export FLOW_MNEMONIC="word1 word2 word3 ... word12" export FLOW_NETWORK="mainnet" ``` Option 3: Cloud Wallet [#option-3-cloud-wallet] Use the FlowIndex custodial wallet API. Requires a JWT token from FlowIndex: ```bash export FLOWINDEX_TOKEN="eyJhbG..." export FLOW_NETWORK="mainnet" ``` Option 4: Interactive Cloud Login [#option-4-interactive-cloud-login] If no credentials are provided, the server starts in interactive mode. The AI agent must call `wallet_login` to initiate authentication, which returns a URL the user opens in a browser. ```bash export FLOW_NETWORK="mainnet" # No FLOW_MNEMONIC, FLOW_PRIVATE_KEY, or FLOWINDEX_TOKEN ``` Running the Server [#running-the-server] Standalone (stdio transport) [#standalone-stdio-transport] ```bash npx @flowindex/agent-wallet ``` The server communicates over stdio using the MCP protocol. It logs status information to stderr. With Claude Desktop [#with-claude-desktop] Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "flow-wallet": { "command": "npx", "args": ["@flowindex/agent-wallet"], "env": { "FLOW_PRIVATE_KEY": "your-private-key", "FLOW_ADDRESS": "0x1234567890abcdef", "FLOW_NETWORK": "testnet", "APPROVAL_REQUIRED": "true" } } } } ``` Inspecting Tools [#inspecting-tools] Use the MCP inspector to browse available tools interactively: ```bash npx @modelcontextprotocol/inspector node node_modules/@flowindex/agent-wallet/dist/index.js ``` Transaction Approval [#transaction-approval] By default, `APPROVAL_REQUIRED=true`. When enabled, transaction templates executed via `execute_template` are queued instead of submitted immediately. The AI agent receives a pending transaction ID and must call `confirm_transaction` to actually submit it. This provides a human-in-the-loop safety mechanism -- the agent can propose transactions, but a human (or secondary check) must approve them. To disable and allow immediate execution: ```bash export APPROVAL_REQUIRED=false ``` Cadence Templates [#cadence-templates] The server ships with a built-in library of Cadence templates organized by category: * **token** -- Fungible token transfers, vault setup * **collection** -- NFT transfers (single and batch) * **evm** -- COA creation, EVM contract calls, cross-VM transfers * **bridge** -- FlowEVMBridge token bridging operations Use `list_templates` to browse available templates and `get_template` to inspect a specific template's source code and argument schema.