@nodeflow-network/nodeflow-api
v1.0.1
Published
API client for NodeFlow SDK
Readme
NodeFlow API
NodeFlow API is the API client for interacting with NodeFlow backend services, enabling asset bridging between EVM chains and the Lightning Network. This documentation will help you build cross-chain transfer functionality from scratch.
Installation
yarn add @nodeflow-network/nodeflow-api
# or
npm install @nodeflow-network/nodeflow-apiCore Concepts
Before starting development, you need to understand these core concepts:
| Concept | Description |
| :--- | :--- |
| Chain ID | Unique identifier for an EVM chain. e.g., 84532 (Base Sepolia), 8453 (Base Mainnet) |
| Pair | Asset trading pair that defines the mapping between Lightning Network asset and EVM Token |
| Pair ID | Unique identifier for a trading pair, returned by getPairs API |
| Asset | Lightning Network side asset (e.g., Taproot Asset USDT) |
| Token | EVM chain side token (e.g., ERC20 USDT) |
| LSP | Liquidity Service Provider, responsible for executing cross-chain transfers |
| Hashlock | Hash lock in HTLC (Hash Time-Locked Contract), ensures atomic swap security |
API Environments
| Environment | Base URL |
| :--- | :--- |
| Development | <API_BASE_URL> |
| Production | (Contact support) |
Quick Start
Step 1: Initialize Client
import { NFApi } from '@nodeflow-network/nodeflow-api';
const client = new NFApi({
baseURL: '<API_BASE_URL>',
edgeBaseURL: '<EDGE_API_BASE_URL>', // Optional: for Edge API
onAuthError: (error) => {
// Called on 401 responses (token expired/invalid/revoked)
// Clear local auth state and prompt re-login
console.log('Auth error, please re-login');
}
});
// Available APIs:
// - client.chain.* (Bridge operations)
// - client.edge.* (Edge node operations - coming soon)Step 2: Authenticate (SIWE)
Write operations (signature, toLightning, toEVM, deposit) require JWT authentication via Sign In With Ethereum (SIWE).
import { SiweMessage } from 'siwe';
// 1. Get a one-time nonce from the server
const nonceRes = await client.chain.getNonce();
const nonce = nonceRes.data.nonce; // Server-generated, single-use, expires in 5 minutes
// 2. Construct SIWE message with server nonce
const message = new SiweMessage({
domain: window.location.host,
address: walletAddress,
statement: 'Sign in with Ethereum to Astra Bridge',
uri: window.location.origin,
version: '1',
chainId: chainId,
nonce: nonce,
issuedAt: new Date().toISOString(),
expirationTime: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
});
const messageStr = message.prepareMessage();
// 3. Sign with wallet (e.g., wagmi signMessageAsync)
const signature = await signMessageAsync({ message: messageStr });
// 4. Login and set token
const res = await client.chain.login({ message: messageStr, signature });
if (res.code === 200) {
client.setToken(res.data.token);
// Token is now auto-injected into all subsequent requests
// Store res.data.token and res.data.exp for session persistence
}
// 5. Logout (revokes token server-side)
await client.chain.logout();
client.clearToken();Token Management:
| Method | Description |
| :--- | :--- |
| client.setToken(token) | Set JWT token for all requests (both main and edge) |
| client.clearToken() | Clear JWT token from all request clients |
Public vs Protected Endpoints:
| Public (no token needed) | Protected (token required) |
| :--- | :--- |
| getContractConfig, getPairs, getPairInfo | getSignature, evmToLightning |
| getTransaction, getAssetPayReq | lightningToEVM, toDeposit |
| bridgeLimitStatus, getNonce, login | logout |
Step 3: Get Contract Configuration
Before any operation, get the contract configuration. This returns all supported chains and their contract addresses.
const configRes = await client.chain.getContractConfig();
const config = configRes.data;
// Returns supported chains, contract addresses, and ABIsStep 4: Get Trading Pairs
Select a chainId and get the supported trading pairs:
const configRes = await client.chain.getContractConfig();
const chainId = Object.keys(configRes.data)[0]; // Use the first supported chain
const pairsResult = await client.chain.getPairs({ chainId });
const pair = pairsResult.data.pairs[0];
const pairId = pair.pairId;Step 5: Get Pair Details
Get fees, limits, and other configuration:
const pairInfoRes = await client.chain.getPairInfo({ chainId, pairId });
const pairInfo = pairInfoRes.data;
// Returns fees, limits, LSP address, time locksBridge Flow Overview
How Lightning → EVM Works (Withdraw)
User sends BTC via Lightning to receive tokens on an EVM chain.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Lightning → EVM (Withdraw) Flow │
└─────────────────────────────────────────────────────────────────────────────┘
User SDK / App LSP Node
│ │ │
│ 1. Enter amount │ │
├─────────────────────────────►│ │
│ │ │
│ │ 2. Request Quote & Invoice │
│ ├───────────────────────────────►│
│ │ ◄── Return LN Invoice │
│ │ │
│ 3. Display Invoice │ │
◄──────────────────────────────│ │
│ │ │
│ 4. User pays via LN Wallet │ │
├──────────────────────────────┼───────────────────────────────►│
│ │ │
│ │ 5. Trigger Smart Contract │
│ │ ◄──────────────────────────────│
│ │ │
│ 6. Tokens received! │ │
◄──────────────────────────────│ │How EVM → Lightning Works (Deposit)
User pays with EVM tokens to pay a Lightning Invoice.
┌─────────────────────────────────────────────────────────────────────────────┐
│ EVM → Lightning (Deposit) Flow │
└─────────────────────────────────────────────────────────────────────────────┘
User SDK / App LSP Node
│ │ │
│ 1. Paste LN Invoice │ │
├─────────────────────────────►│ │
│ │ │
│ 2. Show detailed quote │ │
◄──────────────────────────────│ │
│ │ │
│ 3. Approve & Deposit Tokens │ │
├─────────────────────────────►│ │
│ │ │
│ │ 4. Lock assets in Contract │
│ ├───────────────────────────────►│
│ │ │
│ │ 5. Pay User's LN Invoice │
│ │ ◄──────────────────────────────│
│ │ │
│ 6. Invoice paid! │ │
◄──────────────────────────────│ │Complete API Flow Guide
Flow 1: EVM → Lightning (Pay LN Invoice with EVM Tokens)
Use case: User has EVM tokens (e.g., USDT) and wants to pay a Lightning invoice.
┌─────────────────────────────────────────────────────────────────────────────┐
│ EVM → Lightning Complete API Flow │
└─────────────────────────────────────────────────────────────────────────────┘
User Your App NodeFlow API
│ │ │
│ 1. Paste LN Invoice │ │
├──────────────────────────────►│ │
│ │ │
│ │ 2. getAssetPayReq({ │
│ │ payReq: "lnbc...", │
│ │ assetId: "f7ac99..." │
│ │ }) │
│ ├──────────────────────────────────►│
│ │ ◄── { amount, paymentHash } │
│ │ │
│ │ 3. getPairInfo({ │
│ │ chainId: chainId, │
│ │ pairId: "0x7966..." │
│ │ }) │
│ ├──────────────────────────────────►│
│ │ ◄── { lspAddress, fee, timeLock }│
│ │ │
│ 4. Approve Token │ │
│ ERC20.approve( │ │
│ spender: lspAddress, │ │
│ amount: tokenAmount │ │
│ ) │ │
├──────────────────────────────►│ │
│ │ │
│ 5. Deposit to Bridge │ │
│ Bridge.deposit({ │ │
│ hashlock: paymentHash, │ │
│ toAddr: lspAddress, │ │
│ pairId: "0x7966...", │ │
│ amount: tokenAmount, │ │
│ timeLock: 3600, │ │
│ fee: toAssetBaseFee │ │
│ }) │ │
├──────────────────────────────►│ │
│ │ │
│ │ 6. evmToLightning({ │
│ │ pairId: "0x7966...", │
│ │ chainId: chainId, │
│ │ amount: tokenAmount, │
│ │ lnInvoice: "lnbc...", │
│ │ depositTx: "0xabc..." │
│ │ }) │
│ ├──────────────────────────────────►│
│ │ ◄── { status: "success" } │
│ │ │
│ 7. ✅ Invoice Paid! │ │
◄───────────────────────────────│ │Code Implementation
Prerequisite based on viem/wagmi:
import { createWalletClient, custom, parseUnits } from 'viem';
import { baseSepolia } from 'viem/chains';
import { NFApi } from '@nodeflow-network/nodeflow-api';
const client = new NFApi({ baseURL: '...' });
const walletClient = createWalletClient({ chain: baseSepolia, transport: custom(window.ethereum) });
const [address] = await walletClient.requestAddresses();Step-by-Step Logic:
// 1. Get Config & Pair
const { data: config } = await client.chain.getContractConfig();
const chainId = Object.keys(config)[0]; // Use the first available chain
const pair = (await client.chain.getPairs({ chainId })).data.pairs[0];
const pairInfo = await client.chain.getPairInfo({ chainId, pairId: pair.pairId });
// 2. Check Rate Limit
const limitStatus = await client.chain.bridgeLimitStatus({ address });
if (!limitStatus.data.isAllowed) {
const minutes = Math.ceil(limitStatus.data.remainingSeconds / 60);
throw new Error(`Rate limited. Wait ${minutes} minute(s).`);
}
// 3. Parse Invoice & Validate Amount
const lnInvoice = "lnbc..."; // User input
const invoiceData = await client.chain.getAssetPayReq({ payReq: lnInvoice, assetId: pair.asset.assetId });
const assetAmount = Number(invoiceData.data.amount) / (10 ** pair.asset.decimal);
// Validate against limits from pairInfo
if (assetAmount < pairInfo.data.min) {
throw new Error(`Amount below minimum: ${pairInfo.data.min}`);
}
if (assetAmount > pairInfo.data.max) {
throw new Error(`Amount exceeds maximum: ${pairInfo.data.max}`);
}
// Check channel liquidity
if (pairInfo.data.effectiveSatsBalance < pairInfo.data.satsCapacity * 0.1) {
throw new Error("Channel liquidity too low");
}
// 4. Calculate Token Amount (Asset Decimal 8 -> Token Decimal 18)
const tokenAmount = BigInt(invoiceData.data.amount) * BigInt(10 ** pair.token.decimal) / BigInt(10 ** pair.asset.decimal);
// 5. Approve Token (ERC20)
const tokenAbi = config[chainId].USDT.abi;
await walletClient.writeContract({
address: pair.token.address,
abi: tokenAbi,
functionName: 'approve',
args: [pairInfo.data.lspAddress, tokenAmount],
account: address
});
// 6. Deposit to Bridge
const bridgeAbi = config[chainId].Bridge.abi;
const depositTx = await walletClient.writeContract({
address: config[chainId].Bridge.address,
abi: bridgeAbi,
functionName: 'deposit',
args: [
invoiceData.data.paymentHash, // hashlock (from invoice)
pairInfo.data.lspAddress, // toAddr
pair.pairId, // pairId
tokenAmount, // amount
pairInfo.data.evm2LightningDeltaSecond, // timeLock
pairInfo.data.toAssetBaseFee, // fee
'0x', // signature (not needed for EVM -> LN)
'0x00' // nodePubkey (placeholder)
],
account: address
});
// 7. Notify Backend to Pay Invoice
await client.chain.evmToLightning({
pairId: pair.pairId,
chainId: chainId,
amount: tokenAmount.toString(),
lnInvoice: lnInvoice,
depositTx: depositTx
});Flow 2: Lightning → EVM (Receive EVM Tokens by Paying LN)
Use case: User wants to receive EVM tokens by paying a Lightning invoice.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Lightning → EVM Complete API Flow │
└─────────────────────────────────────────────────────────────────────────────┘
User Your App NodeFlow API
│ │ │
│ 1. Enter desired amount │ │
├──────────────────────────────►│ │
│ │ │
│ │ 2. getSignature({ │
│ │ chainId: chainId, │
│ │ amount: 1000, │
│ │ pairId: "0x7966..." │
│ │ }) │
│ ├──────────────────────────────────►│
│ │ ◄── { hashlock, signature, │
│ │ nodePubKey } │
│ │ │
│ 3. Deposit to Bridge │ │
│ Bridge.deposit({ │ │
│ hashlock: hashlock, │ │
│ toAddr: lspAddress, │ │
│ pairId: "0x7966...", │ │
│ amount: tokenAmount, │ │
│ timeLock: 1800, │ │
│ fee: toTokenBaseFee, │ │
│ signature: signature │ │
│ }) │ │
├──────────────────────────────►│ │
│ │ │
│ │ 4. lightningToEVM({ │
│ │ pairId: "0x7966...", │
│ │ chainId: chainId, │
│ │ hashlock: hashlock, │
│ │ amount: 1000, │
│ │ depositTx: "0xabc..." │
│ │ }) │
│ ├──────────────────────────────────►│
│ │ ◄── { invoice: "lnbc..." } │
│ │ │
│ 5. Display Invoice QR │ │
◄───────────────────────────────│ │
│ │ │
│ 6. User pays via LN Wallet │ │
├───────────────────────────────┼──────────────────────────────────►│
│ │ │
│ │ (Backend detects payment, │
│ │ unlocks tokens on-chain) │
│ │ │
│ 7. ✅ Tokens Received! │ │
◄───────────────────────────────│ │Code Implementation
Step-by-Step Logic:
// Assume config, chainId, pair, pairInfo are already fetched (see Flow 1)
// 1. User inputs desired asset amount (e.g., 100 in asset units)
const assetAmount = 100; // In asset decimal units (e.g., sats for BTC)
// 2. Check Rate Limit
const limitStatus = await client.chain.bridgeLimitStatus({ address });
if (!limitStatus.data.isAllowed) {
const minutes = Math.ceil(limitStatus.data.remainingSeconds / 60);
throw new Error(`Rate limited. Wait ${minutes} minute(s).`);
}
// 3. Validate Amount against limits
if (assetAmount < pairInfo.data.min) {
throw new Error(`Amount below minimum: ${pairInfo.data.min}`);
}
if (assetAmount > pairInfo.data.max) {
throw new Error(`Amount exceeds maximum: ${pairInfo.data.max}`);
}
// 4. Get Signature from LSP
const sigRes = await client.chain.getSignature({
chainId: chainId,
amount: assetAmount,
pairId: pair.pairId
});
const sigData = sigRes.data;
// Response: { hashlock, signature, lspNodePubKey, amtInContract, deltaSecond, fee, lspAddress }
// 5. Deposit to Bridge (On-Chain)
// IMPORTANT: Use amtInContract from signature response, NOT calculated tokenAmount
const depositAmount = BigInt(sigData.amtInContract);
const bridgeAbi = config[chainId].Bridge.abi;
const depositTx = await walletClient.writeContract({
address: config[chainId].Bridge.address,
abi: bridgeAbi,
functionName: 'deposit',
args: [
sigData.hashlock, // hashlock from LSP
account.address, // toAddr (user's address - receives tokens)
sigData.pairId, // pairId
depositAmount, // amount (from signature response)
BigInt(sigData.deltaSecond), // timeLock
sigData.fee, // fee
sigData.signature, // signature (REQUIRED for LN -> EVM)
sigData.lspNodePubKey // nodePubkey (REQUIRED)
],
account: address
});
// 6. Get Lightning Invoice to Pay
const result = await client.chain.lightningToEVM({
pairId: pair.pairId,
chainId: chainId,
hashlock: sigData.hashlock,
amount: sigData.amtInContract, // Use amtInContract, not assetAmount
depositTx: depositTx
});
console.log("Pay this Invoice:", result.data.invoice);
// User pays the invoice with their Lightning wallet
// After payment, LSP unlocks tokens on-chain automaticallyAPI Reference
Chain API - Authentication Methods
| Method | Parameters | Returns | Description |
| :--- | :--- | :--- | :--- |
| chain.getNonce() | - | { nonce } | Get a one-time server nonce for SIWE login (expires in 5 min) |
| chain.login({ message, signature }) | SIWE message string, wallet signature | { token, user, exp, jti } | Login with SIWE signature, returns JWT |
| chain.logout() | - | { message } | Revoke current JWT token server-side |
Usage Example:
// Get nonce, then login
const { data: { nonce } } = await client.chain.getNonce();
// ... construct SIWE message with nonce, sign it ...
const res = await client.chain.login({ message: siweMessage, signature: walletSig });
client.setToken(res.data.token);
// Logout
await client.chain.logout();
client.clearToken();Chain API - Configuration Methods
| Method | Parameters | Returns | Description |
| :--- | :--- | :--- | :--- |
| chain.getContractConfig() | - | { [chainId]: { Bridge, Token... } } | Get all supported chains and contract configs |
| chain.getPairs({ chainId }) | chainId: number | { pairs: [...] } | Get trading pairs for a chain |
| chain.getPairInfo({ chainId, pairId }) | chainId, pairId | { lspAddress, min, max, fee... } | Get pair details |
Usage Example:
const config = await client.chain.getContractConfig();
const pairs = await client.chain.getPairs({ chainId: 84532 });
const pairInfo = await client.chain.getPairInfo({ chainId: 84532, pairId: '0x7966...' });Chain API - Transaction Methods
| Method | Parameters | Returns | Description |
| :--- | :--- | :--- | :--- |
| chain.getSignature({ chainId, amount, pairId }) | chainId, amount, pairId | { hashlock, signature } | Get signature for LN→EVM |
| chain.evmToLightning({ pairId, chainId, amount, lnInvoice, depositTx }) | see details | { status } | Submit EVM→LN deposit |
| chain.lightningToEVM({ pairId, chainId, hashlock, amount, depositTx }) | see details | { invoice } | Submit LN→EVM deposit |
| chain.toDeposit({ chainId, pairId, depositArgs, signature, signerAddress }) | see details | { status } | Submit signed deposit |
Usage Example:
const signature = await client.chain.getSignature({ chainId: 84532, amount: 1000, pairId: '0x7966...' });
await client.chain.evmToLightning({ pairId, chainId, amount, lnInvoice, depositTx });
const result = await client.chain.lightningToEVM({ pairId, chainId, hashlock, amount, depositTx });Chain API - Query Methods
| Method | Parameters | Returns | Description |
| :--- | :--- | :--- | :--- |
| chain.getTransaction({ hashlock }) | hashlock | { status, ... } | Query transaction status |
| chain.getAssetPayReq({ payReq, assetId }) | payReq, assetId | { amount, paymentHash } | Parse and validate invoice |
| chain.bridgeLimitStatus({ address }) | address | { isAllowed, remainingSeconds } | Check rate limit status |
Usage Example:
const tx = await client.chain.getTransaction({ hashlock: '0xabc...' });
const invoice = await client.chain.getAssetPayReq({ payReq: 'lnbc...', assetId: 'f7ac99...' });
const status = await client.chain.bridgeLimitStatus({ address: '0x123...' });Edge API
The Edge API provides advanced Lightning Network edge node functionality.
| Method | Parameters | Returns | Description |
| :--- | :--- | :--- | :--- |
| edge.connectNode(params) | node_key_url, node_type | { code, msg } | Connect to a peer node |
| edge.buyLiquidity(params) | assetType, assetId, amount, nodeKeyUrl, payType | { data: { invoice... } } | Purchase inbound liquidity |
| edge.liquidityOut(params) | assetType, assetId, amount, nodeKey, payType | { data: { invoice... } } | Purchase outbound liquidity |
| edge.getLiquidityRecords(params) | current, size, nodeKey | { data: { records... } } | Get liquidity order history |
| edge.getCoinList() | - | { data: [...] } | Get supported coin list |
| edge.getAssetList() | - | { data: [...] } | Get asset list |
| edge.getPublicInfo() | - | { data: [...] } | Get edge node public info |
Usage Example:
// 1. Connect Node
await client.edge.connectNode({
node_key_url: "[email protected]:9735",
node_type: "LND"
});
// 2. Get Public Info
const publicInfo = await client.edge.getPublicInfo();
console.log(publicInfo.data);
// 2. Buy Liquidity (Inbound)
const inResult = await client.edge.buyLiquidity({
assetType: "TAPROOT",
assetId: "f7ac99...",
amount: "100000",
nodeKeyUrl: "[email protected]:9735",
payType: "TAPROOT"
});
// 3. Buy Liquidity (Outbound)
const outResult = await client.edge.liquidityOut({
assetType: "TAPROOT",
assetId: "f7ac99...",
amount: "100000",
nodeKey: "03b24a...",
payType: "TAPROOT"
});
// 4. Get Records
const records = await client.edge.getLiquidityRecords({
current: 1,
size: 10,
nodeKey: "03b24a..."
});
// 5. Get Coin List
const coinList = await client.edge.getCoinList();
console.log(coinList.data);
// 6. Get Asset List
const assetList = await client.edge.getAssetList();
console.log(assetList.data);FAQ
Q: How do I know which chainIds are supported?
Call getContractConfig(). The response object keys are the supported chainIds.
Q: Where do I get the pairId?
Call getPairs({ chainId }). Each pair object contains a pairId.
Q: How to handle failed transactions?
Use getTransaction({ hashlock }) to query transaction status and decide if refund is needed.
License
MIT
