@flop-labs/sdk
FaucetClient.healthCheck() reports the faucet's HTTP readiness as a boolean.
It returns false for HTTP 503 or a request failure, including a live faucet
whose caller IP classification has not been checked. This is not a process-liveness
or grant-eligibility test. The health GET does not request funds or classify the IP.
An explicit requestFaucetFunding() attempt performs admission and returns a
refusal reason and retryAfterSeconds when supplied. It never automatically
repeats a funding POST. An unknown outcome can mean a grant occurred: preserve
the result and reconcile before requesting again. The health check alone must not
block an explicit funding request.
Unreleased faucet migration: fundFromFaucet retains its boolean signature
but currently always returns false; false means unconfirmed, not unpaid. The
faucet does not yet expose an authenticated durable binding from a unique request
to its transaction, so the SDK cannot establish request-specific confirmation.
Use fundFromFaucetDetailed to distinguish refusal, unavailable preflight, and
unknown outcomes and retain the returned hash when available. Its accepted HTTP
response is still unknown as a grant. QuickStart throws FaucetUnconfirmedError
with the outcome when funding cannot be confirmed; reconcile the original request
before retrying. Faucet-enabled QuickStart requires a caller-owned signer or keypair
so an unconfirmed payment cannot be stranded in an inaccessible generated wallet. For the separate question of whether an account has enough funds,
use waitForAvailableBalance; the old waitForFaucetFunding name is a deprecated
balance-check alias. No source-only test confirms a live grant.
Ordinary HTLC creation requires a registered EconomicCritical appraisal profile.
Supply tee_requirements / teeRequirements as {profile: {profile_id, version}, model_hash, measurement_hash} (32-byte hex hashes), or its canonical 100-byte SCALE hex.
The runtime checks that the registered profile and immutable commitments match its policy; there is no simulator default.
For the examples, set FLOP_HTLC_TEE_REQUIREMENTS to that canonical hex from your network's registered profile.
TypeScript SDK for the FLOP Network — HTLC workflows, PoI proofs, and account management.
SUPPORTED_SPEC_VERSIONS in this source admits exactly 1013000, 1014000, 1014001, 1014002, 1014003, 1014004, 1014005, 1014007, 1014008, 1014009, 1014010, 1014011, 1014012, 1014013, 1014014, 1014015, 1014016, 1014017, 1014018, 1014019, 1014020.
SUPPORTED_SPEC_VERSION_RANGE retains its exported shape as the envelope
1013000..1014020; admission requires exact membership (including no 1014006). The published 0.4.0 executable stops at 1014011 and also excludes 1014006;
its bundled README has stale connection and HTLC examples. These source corrections
are unreleased: installing @latest does not deliver them until a new version is
published. The examples below describe this source candidate. Qualification covers
runtime 1013000 at 44a5fedc88614b9a6e8c97605c2ec943faf47d9d, the
registrar runtime 1014000 at e4f01cc849fa6c768d5c0f9c111dffb450ea4fa9
(PR #2726), runtime 1014001 at de4e2779dbf6f8d25348953ce0992ef8e89756bd
(PR #2803), runtime 1014002, the #2637 devnet hybrid-verifier fast path, and runtime
1014003 at 9035450361681a69c6c02c9c53e47351999ef6f8 (PR #3096). Candidate runtime
1014004 (1.14.4, PR #3006) has source compatibility coverage for uploader-funded
qualification. Candidate runtime 1014005 (1.14.5) has source compatibility coverage for the
preactivation FCU name replacement; this does not qualify physical latency or deployment.
Candidate specs beyond 1014003 have source compatibility coverage; acceptance by
the SDK does not establish deployment or end-to-end inference qualification.
Other versions require separate qualification.
Every FlopClient connection path checks it. Unsupported runtime upgrades detected
by polkadot.js metadata events disconnect the API and make subsequent SDK operations
raise FlopError with code RUNTIME_INCOMPATIBLE, the qualified versions, and upgrade guidance.
Generated clients apply the same exact-version guard; explicit profile constraints can
narrow compatibility and require a spec name. Upgrade with pnpm add @flop-labs/sdk@latest to a release
supporting the chain's runtime; allowlist changes require compatibility validation.
Installation
npm install @flop-labs/sdk
Peer Dependencies
React is optional (only needed if using React hooks):
npm install @flop-labs/sdk react # Optional: for React integration
Quick Start
The live devnet was checked at finalized block 2,764,673 on 3 November 2026:
spec 1014003, with an empty attest.profiles registry. The HTLC example below
cannot run there until authorized governance registers its exact EconomicCritical
appraisal policy, verifier set and version. Neither simulator mode nor an arbitrary
profile ID supplies that prerequisite. Use assertHtlcAppraisalProfile(api, hex)
before signing; the runtime independently enforces the policy and commitments.
The three exported ghost/TOPLOC/DA profile references are not HTLC profiles.
For a direct compute-channel purchase, prepare and persist the original intent:
import { FlopClient } from '@flop-labs/sdk';
import { readFile, writeFile } from 'node:fs/promises';
const client = await FlopClient.createWithKeypair('wss://rpc.devnet.flop.network', keypair);
try {
// Run preparation once, in a private directory. The intent contains a session seed.
const intent = await client.prepareInferenceSession({ minerUrl, escrowFlop: 0.01, maxTurns: 1 });
await writeFile('purchase.json', JSON.stringify(intent), { flag: 'wx', mode: 0o600 });
const saved = JSON.parse(await readFile('purchase.json', 'utf8'));
const result = await client.buyInferenceSession({ intent: saved, prompt: 'Hello' });
console.log(result.channelId, result.status);
} finally { await client.disconnect(); }
On restart, read the existing file and pass that intent; never run preparation
again for the same purchase. Hold exclusive ownership of the file while using it.
Alternatively, buyInferenceSession accepts an awaited onPrepared persistence
callback. Calls without an intent or callback stop before signing. This tightens
the former convenience API and needs a breaking-change release.
The SDK reads finalized state before submitting. A missing record is still an
unconfirmed outcome; resubmission retains the original channel nonce and key.
An existing channel stops with INFERENCE_PURCHASE_UNCONFIRMED and its public ID.
Even Open may have streamed off-chain turns: do not replay them with fresh counters.
Inspect settlement or recover the retained transcript; if an undelivered Open
channel exceeds its SLA, use the permissionless timeout path after confirming
eligibility. Retain the intent until recovery and settlement/refund are finalized.
Repeated extrinsics may still pay fees. settlement_posted opens a challenge
window and does not establish a finalized miner payment.
import { FlopClient, createAgentWallet } from '@flop-labs/sdk';
// Connect to FLOP node
const client = await FlopClient.create({ endpoint: 'ws://127.0.0.1:9944', signer: '//Alice' });
// Check node health
const health = await client.health();
console.log('Node healthy:', health);
// Create an agent wallet
const wallet = await createAgentWallet(client.api, client.signer, {
agent: client.address,
dailyCap: 100n * 10n ** 18n,
perTxLimit: 5n * 10n ** 18n,
});
// Create HTLC for inference purchase
const htlc = await client.createHtlc({
agent: 'my-agent',
task: {
model: 'llama-3-8b',
prompt: 'Hello, world!',
tee_requirements: process.env.FLOP_HTLC_TEE_REQUIREMENTS
},
rewardFlop: 5.0,
timeoutBlocks: 60
});
console.log('HTLC created:', htlc.htlcId);
// Wait for settlement
const status = await client.waitForSettlement(htlc.hashLock);
console.log('Settlement:', status);
API Reference
FlopClient
Main client for interacting with the FLOP Network.
Connection factories
await FlopClient.create(options?: FlopClientOptions)
Options:
endpoint(string, optional): RPC endpoint URL. Default:ws://127.0.0.1:9944signer(string, optional): local signer URI; the//Alicedefault is for disposable local development only.createWithKeypair(endpoint, keypair): connect using an existing decoded keypair.
Methods
health()
Check node health status.
await client.health();
// Returns: { isSyncing: boolean, peers: number, shouldHavePeers: boolean }
createHtlc(params)
Create a Hash Time Lock Contract for atomic inference purchase.
await client.createHtlc({
agent: string,
task: ComputeTask,
rewardFlop: number,
timeoutBlocks: number,
hashLock?: string // Auto-generated if not provided
});
Returns:
{
htlcId: string;
hashLock: string;
preImage: string;
txHash: string;
}
createCrossChainHtlc(params)
Create a cross-chain HTLC for a FLOP ↔ BTC atomic swap (bilateral HTLC path). The
FLOP leg must outlive the counter-chain leg (timelock symmetry, enforced on-chain),
so timeoutBlocks is the longer leg and btcTimelockHours the shorter. USDT on
TON/TRON settles via NEAR Intents instead — see docs/bridge/settlement.md.
await client.createCrossChainHtlc({
agent: string,
task: ComputeTask,
rewardFlop: number,
btcAddress: string,
btcAmountSats: number | bigint,
timeoutBlocks?: number, // FLOP side, default 7200 (~2h)
btcTimelockHours?: number, // counter-chain side, default 1
hashLock?: string // auto-generated if omitted
});
checkHtlcStatus(hashLock)
Query HTLC status by hash lock.
await client.checkHtlcStatus('0x...');
// Returns: HtlcInfo | null (RPC fields include reward_amount and timeout_block)
waitForSettlement(hashLock, timeoutMs?, pollIntervalMs?)
Poll HTLC until settled or timeout.
await client.waitForSettlement('0x...', 30_000, 1_000);
// Returns: HtlcInfo with status SETTLED or REFUNDED, or null on polling timeout.
// null does not refund funds; reconcile the original HTLC before retrying.
getBalance(address?)
Get account balance in FLOP tokens.
await client.getBalance(); // Defaults to the connected account's SS58 address.
// Returns: string (human-readable FLOP, preserving decimal precision)
submitPoUI(proofs, gnWeight, taskHash)
Submit TEE-verified PoI proof batch.
await client.submitPoUI(proofs, 1450, '0x...');
getAccountInfo(address)
Get account information including nonce and metadata.
await client.getAccountInfo('flop1qyq...');
getComputeTask(taskId)
Get compute task details.
await client.getComputeTask('task_0x...');
getTeeQuote(task)
Get TEE quote for compute task.
await client.getTeeQuote({ model: 'llama-3-8b', prompt: '...' });
Agent Wallet
Create and manage agent wallets with session keys.
createAgentWallet(api, signer, options)
Create a new agent wallet.
import { createAgentWallet } from '@flop-labs/sdk';
const walletTxHash = await createAgentWallet(client.api, client.signer, {
agent: agentAddress, // Existing agent SS58 account, distinct from the owner.
dailyCap: 100n * 10n ** 18n,
perTxLimit: 5n * 10n ** 18n,
});
// Returns the transaction hash, not a wallet object.
fundAgentWallet(api, signer, agent, amountRaw)
Fund an agent wallet from owner account.
import { fundAgentWallet } from '@flop-labs/sdk';
await fundAgentWallet(client.api, client.signer, agentAddress, 50n * 10n ** 18n);
agentTransfer(client, fromWallet, toAddress, amount)
Transfer funds between agent wallets.
import { agentTransfer } from '@flop-labs/sdk';
await agentTransfer(client, 'flop1agent1...', 'flop1agent2...', 10.0);
ownerWithdraw(client, walletAddress, amount)
Withdraw funds from agent wallet to owner account.
import { ownerWithdraw } from '@flop-labs/sdk';
await ownerWithdraw(client, 'flop1agent...', 25.0);
createSession(client, walletAddress, options)
Create a session key with spending limits.
import { createSession } from '@flop-labs/sdk';
const session = await createSession(client, 'flop1agent...', {
spendingLimit: 10.0,
expiresAt: Date.now() + 3600000 // 1 hour
});
executeViaSession(client, sessionKey, call)
Execute a call using session key.
import { executeViaSession } from '@flop-labs/sdk';
await executeViaSession(client, sessionKey, {
pallet: 'htlc',
method: 'create',
args: { ... }
});
x402 Payment Protocol
Utilities for x402 payment headers.
x402Fetch(url, options, paymentParams)
Fetch with x402 payment headers.
import { x402Fetch } from '@flop-labs/sdk';
const response = await x402Fetch('https://api.example.com/inference', {
method: 'POST',
body: JSON.stringify({ prompt: '...' })
}, {
amount: 5.0,
recipient: 'flop1qyq...'
});
parsePaymentHeaders(headers)
Parse x402 payment headers from response.
import { parsePaymentHeaders } from '@flop-labs/sdk';
const payment = parsePaymentHeaders(response.headers);
Named-miner sessions
runDirectSession({ api, signer, miner, model, endpoint, prompt, maxFlop, maxTokens })
opens a channel on the named miner, verifies its identity and signed response, and
settles or recovers escrow. maxFlop is raw chain units; maxTokens is a positive
safe integer and defaults to 256 completion tokens. The request carries that limit
in the chat envelope bound by the verified receipt. Inspect receipts[0].finishReason and
completionTokens for truncation. The 60-second turn deadline remains in force;
larger token budgets do not extend it. Settlement separately waits for the channel's
finalized calibration allowance before submitting payment. Cooperative delivery and
finalized-event discovery each have a bounded 60-second window. Expired channels use
timeout recovery. If the result is unknown, retain the channel ID and inspect finalized
channel state and terminal events before retrying; a closed channel alone does not prove
successful settlement. A zero-turn force-open can be expired after its acknowledgement
deadline, and an open channel can be timed out after its lifetime.
Session Estimation (compute channel)
Size a compute-channel session before opening it, entirely off chain, using the chain's own meter:
computeChannel.estimateSessionturns a turn profile intoG_n, minimum blocks, channel count and escrow.computeChannel.sessionPriorreturns P50/P90/P99 profiles for a task you have not run yet.computeChannel.calibrateSessionPriorfits that prior to your model and agent configuration from about 10 probe runs.computeChannel.reforecastSessionupdates the quantiles mid-run.
See the session estimation guide.
React Hooks (Optional)
import { useFlopClient, useWalletBalance, useHtlcStatus } from '@flop-labs/sdk/react';
function MyComponent() {
const client = useFlopClient('http://127.0.0.1:16111/rpc');
const { balance } = useWalletBalance(client, 'flop1qyq...');
const { status } = useHtlcStatus(client, '0x...');
return <div>Balance: {balance} FLOP</div>;
}
Types
interface ComputeTask {
model: string;
prompt: string;
maxTokens?: number;
temperature?: number;
}
type HtlcInfo = import('@flop-labs/sdk').HtlcInfo;
interface PoUISubmission {
proofHash: string;
gnMeasured: number;
teeQuote: string;
timestamp: number;
}
interface WalletInfo {
address: string;
balance: number;
dailyLimit: number;
dailySpent: number;
allowedPallets: string[];
}
Error Handling
import { FlopClient, FlopError } from '@flop-labs/sdk';
let client: FlopClient | undefined;
try {
client = await FlopClient.create({ endpoint: 'ws://127.0.0.1:9944', signer: '//Alice' });
console.log(await client.health());
} catch (error: unknown) {
if (error instanceof FlopError) {
// Includes RUNTIME_INCOMPATIBLE raised while awaiting the connection factory.
console.error(error.code, error.message);
} else if (error instanceof Error) {
console.error(error.message);
} else {
console.error('Unexpected error:', error);
}
} finally {
await client?.disconnect();
}
Complete Example
This source example uses the public, well-known //Alice key only on a disposable
local chain. Set FLOP_AGENT_ADDRESS to the agent's SS58 account and
FLOP_HTLC_TEE_REQUIREMENTS to the registered appraisal requirements matching
the model used below. Use a private working directory. The HTLC recovery file is created exclusively before
transactions; retain it on errors and reconcile its original hash lock before
resuming, rather than rerunning this one-time setup. Never log its pre-image.
import { writeFile } from 'node:fs/promises';
import { FlopClient, assertHtlcAppraisalProfile, createAgentWallet, fundAgentWallet } from '@flop-labs/sdk';
async function main() {
const agentAddress = process.env.FLOP_AGENT_ADDRESS;
const teeRequirements = process.env.FLOP_HTLC_TEE_REQUIREMENTS;
if (!agentAddress || !teeRequirements) throw new Error('Set the agent address and registered HTLC appraisal requirements');
const client = await FlopClient.create({ endpoint: 'ws://127.0.0.1:9944', signer: '//Alice' });
try {
await assertHtlcAppraisalProfile(client.api, teeRequirements);
const { preImage, hashLock } = FlopClient.generateSecrets();
await writeFile('htlc-purchase.json', JSON.stringify({ preImage, hashLock }) + '\n', { flag: 'wx', mode: 0o600 });
const FLOP = 10n ** 18n;
const walletTxHash = await createAgentWallet(client.api, client.signer, {
agent: agentAddress,
dailyCap: 100n * FLOP,
perTxLimit: 5n * FLOP,
});
console.log('Wallet creation transaction:', walletTxHash);
await fundAgentWallet(client.api, client.signer, agentAddress, 50n * FLOP);
// This direct HTLC uses the owner's balance, separately from the agent wallet.
const htlc = await client.createHtlc({
agent: agentAddress,
task: { model: 'llama-3-8b', prompt: 'Analyze BTC price trend', tee_requirements: teeRequirements },
rewardFlop: 5.0,
timeoutBlocks: 60,
hashLock,
});
console.log('HTLC created:', htlc.htlcId);
const settlement = await client.waitForSettlement(hashLock, 30_000, 1_000);
if (settlement?.status === 'SETTLED') {
console.log('Node reports settlement:', settlement.miner, settlement.gn_used);
} else if (settlement?.status === 'REFUNDED') {
console.log('Node reports a refund; confirm finality before treating it as complete');
} else {
console.log('Unconfirmed: polling timed out without a refund. Keep the original intent and reconcile chain state');
}
} finally {
await client.disconnect();
}
}
main().catch(console.error);
Development
# Install dependencies
npm install
# Build the SDK
npm run build
# Watch mode
npm run dev
# Clean build artifacts
npm clean
License
Apache-2.0 © FLOP Network
NVIDIA GPU Operators
For miners operating NVIDIA GPUs to earn rewards, see the complete guide:
- Run a FLOP miner on a GPU host - Mining setup and workflows
- Put your GPU to work on FLOP - Plain-language start for GPU owners new to crypto
Quick example for GPU operators:
import { FlopClient, type TeeQuote } from '@flop-labs/sdk';
// Connect as miner
const client = await FlopClient.create({
endpoint: 'ws://127.0.0.1:9944',
signer: '//Miner'
});
// Monitor for HTLCs
const htlc = await client.checkHtlcStatus(hashLock);
if (htlc && htlc.status === 'CREATED') {
// Execute inference on NVIDIA GPU
const result = await executeOnGpu(htlc.task); // Your GPU execution logic
// Generate TEE quote
const teeQuote: TeeQuote = {
version: '1.0',
miner_address: client.address,
task_reference: {
htlc_hash_lock: htlc.hashLock,
model_hash: `sha256:${htlc.task.model}`,
task_timestamp: Math.floor(Date.now() / 1000),
},
compute_output: {
gn_weight: result.gnWeight, // G_n weight (BFLOPs)
execution_time_ms: result.executionTime,
encrypted_result: result.encryptedOutput,
},
attestation: {
tee_type: 'NVIDIA_HOPPER_CC', // or 'NVIDIA_RUBIN_CC', 'CPU_WRAPPED_TEE'
hardware_pubkey: hardwarePubkey,
quote_hex: teeQuoteHex,
signature: minerSignature,
},
};
// Redeem HTLC with TEE quote
const txHash = await client.redeemHtlc(
htlc.htlcId,
preImage,
teeQuote,
);
console.log('✓ HTLC redeemed:', txHash);
// Submit PoUI proof batch (optional, for additional rewards)
const proofs = generateStarkProofs(result);
const taskHash = generateTaskHash(htlc);
const proofTxHash = await client.submitPoUI(proofs, result.gnWeight, taskHash);
console.log('✓ PoUI proof submitted:', proofTxHash);
}
TEE Quote Format
interface TeeQuote {
version: string; // "1.0"
miner_address: string; // flop1... address
task_reference: {
htlc_hash_lock: string; // HTLC identifier
model_hash: string; // sha256:model...
task_timestamp: number; // Unix timestamp (seconds)
};
compute_output: {
gn_weight: number; // G_n weight (BFLOPs)
execution_time_ms: number;
encrypted_result: string; // 0x... encrypted output
};
attestation: {
tee_type: string; // "NVIDIA_HOPPER_CC" | "NVIDIA_RUBIN_CC" | "CPU_WRAPPED_TEE"
hardware_pubkey: string; // 0x... hardware public key
quote_hex: string; // 0x... raw TEE quote
signature: string; // 0x... miner signature
};
}
Supported TEE Types
| TEE Type | Hardware | Production Ready |
|---|---|---|
NVIDIA_HOPPER_CC | H100, H200 | ✅ Yes |
NVIDIA_RUBIN_CC | B100, B200 | ✅ Yes |
CPU_WRAPPED_TEE | RTX 4090+ + AMD SEV/Intel TDX | ⚠️ Testnet only |
TEE Verification
The TEE bridge validates quotes with:
- Timestamp freshness: Quotes must be < 60 seconds old
- Signature verification: Validates miner signature
- Replay protection: Tracks task hashes to prevent double-spending
- Hardware verification: Checks hardware keys against registry (production only)
import { verifyTeeQuote, type PouiPayload } from '@flop-labs/sdk/tee';
const bridge = new TeeBridge({ simulatorMode: false });
// Register known hardware keys (production)
bridge.registerHardwareKey('0x...');
// Verify TEE quote
const payload: PouiPayload = parseTeeQuote(quoteJson);
const result = await bridge.verify(payload);
if (result.valid) {
console.log(`✓ TEE verified: ${result.gnWeight} G_n`);
} else {
console.error(`✗ Verification failed: ${result.error}`);
}
For complete setup instructions, hardware requirements, and production deployment guide:
- Run a FLOP miner on a GPU host - Production setup and workflows
- Put your GPU to work on FLOP - Plain-language start for GPU owners: hardware, account, deposit, access