Integration

SDK Reference

@fairblock/stellar-stabletrust a TypeScript SDK that abstracts all cryptography, Soroban invocations, and ZK proof generation so you can integrate confidential payments in a few lines of code.


Installation

Requires Node.js 18+ or a modern browser environment.

bash
npm install @fairblock/stellar-stabletrust
# or
yarn add @fairblock/stellar-stabletrust
# or
pnpm add @fairblock/stellar-stabletrust

Prerequisites

Node.js 18 or later (or browser with WebAssembly support)
A Stellar Keypair with XLM for transaction fees
Access to a Soroban RPC endpoint (testnet or mainnet)
Recipient must also have a finalized Stabletrust account before receiving transfers

Quick start

Full lifecycle: account creation, deposit, transfer, apply, balance query, and withdrawal.

example.ts
import { StellarStabletrustClient } from "@fairblock/stellar-stabletrust";
import { SorobanRpc, Keypair } from "@stellar/stellar-sdk";

// 1. Initialise the client
const client = new StellarStabletrustClient({
  sorobanRpcUrl: "https://soroban-rpc.testnet.stellar.org",
  contractAddress: "CAAAA...",          // Soroban confidential-mirror contract
  networkPassphrase: "Test SDF Network ; September 2015",
});

// 2. One-time account creation (generates ElGamal keypair from wallet signature)
const keypair = Keypair.fromSecret("S...");
await client.createAccount(keypair);
// Waits for FairyPort relay and Fairyring confirmation (~10–30s)

// 3. Deposit Stellar tokens into the confidential layer
await client.deposit({
  keypair,
  tokenAddress: "CBBBB...",  // Stellar token contract address
  amount: 100n,              // plaintext amount (BigInt, in token's base unit)
});

// 4. Send a private confidential transfer
await client.transfer({
  keypair,
  tokenAddress: "CBBBB...",
  recipientAddress: "GABC...",
  amount: 50,                // Number; generates ZK proofs client-side
  useOffchainVerify: false,  // false = onchain ZKP via Fairyring (trustless)
});

// 5. Apply incoming pending credits (must be called to make received funds spendable)
await client.applyPending({ keypair });

// 6. Query encrypted balance (decrypted client-side with your private key)
const balance = await client.getBalance({
  keypair,
  tokenAddress: "CBBBB...",
});
// { available: "50", pending: "0" }

// 7. Withdraw back to standard Stellar tokens
await client.withdraw({
  keypair,
  tokenAddress: "CBBBB...",
  amount: 25,
  useOffchainVerify: false,
});

useOffchainVerify: set to false (default) for trustless onchain ZKP verification via Fairyring. Set to true for faster settlement where FairyPort pre-verifies proofs off-chain.

Key management

The SDK derives your ElGamal keypair deterministically from your Stellar wallet. The private key never leaves your device.

typescript
import { StellarStabletrustClient } from "@fairblock/stellar-stabletrust";

// The SDK derives your ElGamal keypair deterministically from a signature
// over a well-known message, using your Stellar keypair.
// The private key is NEVER transmitted to any server.

const client = new StellarStabletrustClient({ ... });

// Option A: derive and cache the keypair in-memory
const elgamalKeypair = await client.deriveElGamalKeypair(keypair);
// { publicKey: Uint8Array, secretKey: Uint8Array }

// Option B: supply a pre-derived keypair directly (for hardware wallets)
const balance = await client.getBalance({
  keypair,
  tokenAddress: "CBBBB...",
  elgamalSecretKey: storedSecretKey, // Uint8Array (32 bytes)
});

Key loss is permanent. If you lose your Stellar private key, your confidential balances cannot be decrypted. Store your Stellar keypair securely. If using a hardware wallet, the ElGamal key is re-derived on each session.

Proof generation

All ZK proofs are generated client-side. Nothing sensitive leaves the user's device.

typescript
// Proofs are generated entirely client-side inside the SDK.
// No proof material leaves the user's device.

// Transfer proof bundle contains three proofs:
// 1. Equality proof      same plaintext encrypted under both pubkeys
// 2. Range proof         sender balance after deduction is >= 0
// 3. Ciphertext validity transfer ciphertexts are well-formed

// Withdraw proof bundle contains two proofs:
// 1. Equality proof correctly links ciphertext to amount
// 2. Range proof    balance after deduction is >= 0 (bound to tx_id nonce)

// The SDK handles all proof generation transparently:
await client.transfer({
  keypair,
  tokenAddress: "...",
  recipientAddress: "...",
  amount: 10,
  // useOffchainVerify defaults to false (trustless, onchain ZKP)
});

Method reference

createAccount(keypair)Promise<void>

Registers a new confidential account. Derives an ElGamal keypair from the Stellar keypair, submits a create request to the Soroban contract, and waits for FairyPort to relay the confirmation from Fairyring. Must be called exactly once per address.

Waits for full cross-chain finalization. Allow 10–60 seconds on testnet.

deposit({ keypair, tokenAddress, amount })Promise<void>

Transfers plain tokens from the user's Stellar account into the Soroban contract escrow and updates their encrypted available balance on Fairyring. amount is a BigInt in the token's base unit.

transfer({ keypair, tokenAddress, recipientAddress, amount, useOffchainVerify? })Promise<void>

Generates ZK proofs client-side and submits a confidential transfer request. The sender's available balance decreases immediately; the recipient's pending balance increases and must be applied via applyPending(). amount is a Number.

applyPending({ keypair })Promise<void>

Merges all pending credits into the spendable available balance. Must be called by the recipient after receiving a transfer before those funds can be spent or withdrawn.

withdraw({ keypair, tokenAddress, amount, useOffchainVerify? })Promise<void>

Generates a ZK range proof and submits a withdrawal. On Fairyring confirmation, the Soroban contract releases the escrowed tokens back to the user's Stellar account.

getBalance({ keypair, tokenAddress })Promise<{ available: string; pending: string }>

Queries the encrypted ciphertexts from Soroban storage and decrypts them client-side using the derived ElGamal private key. Returns string representations of the plaintext amounts in the token's base unit.

deriveElGamalKeypair(keypair)Promise<{ publicKey: Uint8Array; secretKey: Uint8Array }>

Deterministically derives the ElGamal keypair from a Stellar Keypair by signing a well-known domain-separated message. The same wallet always yields the same ElGamal key.

Error reference

ErrorCauseResolution
AccountDoesNotExistNo account registered for this addressCall createAccount() before any other operation
PendingActionExistsA previous operation has not been finalizedWait for FairyPort to relay the prior action and retry
RecipientNotFinalizedTransfer recipient has not completed account creationRecipient must call createAccount() and wait for finalization
InsufficientFundsAvailable balance too low for the requested withdrawCheck balance with getBalance() before withdrawing
ProofVerificationFailedZK proof rejected by Fairyring or FairyPortEnsure the correct ElGamal keypair is used; retry once
SenderHasNoBalanceNo encrypted balance slot for this tokenDeposit the token first before transferring

Choosing a verification mode

onchain (default)

useOffchainVerify: false

Proofs are verified by the Fairyring zkp module. Fully trustless no assumption on the relayer. Slightly higher latency due to the cross-chain round trip.

Recommended for production

Off-chain

useOffchainVerify: true

FairyPort verifies proofs via its Rust FFI library before forwarding to Fairyring. Lower latency but requires trusting the relayer operator to run the verifier correctly.

Suitable for controlled/internal deployments