Fluid enables gasless Stellar transactions by abstracting network fees. The core purpose is to allow applications to let users pay with the token they're spending (e.g., USDC) without the application needing to worry about gas abstraction or requiring users to hold XLM for fees.
Users sign their transactions locally, and Fluid wraps them in fee-bump transactions to pay network fees in XLM on their behalf.
Fluid solves the gas abstraction problem for Stellar applications. Instead of requiring users to:
- Hold XLM for transaction fees
- Understand fee mechanics
- Manage multiple assets (payment token + fee token)
Applications can integrate into any Fluid server that supports the fee token to let users pay with their preferred token (USDC etc ) while the Fluid server handles all XLM fee payments in the background. This removes friction and improves user experience.
This project uses Node.js and TypeScript for easy setup and deployment.
- Node.js 18+ and npm
- A Stellar account with XLM for fee payments (testnet or mainnet)
- Clone the repository:
git clone <repository-url>
cd fluid- Install server dependencies:
cd server
npm install- Install client dependencies (optional):
cd ../client
npm install- Configure the server:
cd ../server
cp .env.example .envEdit .env and set your FLUID_FEE_PAYER_SECRET.
- Build and run the server:
npm run build
npm startThe server will start on http://localhost:3000
fluid/
├── server/ Fluid server (TypeScript/Node.js)
│ ├── src/
│ │ ├── index.ts
│ │ ├── config.ts
│ │ └── handlers/
│ │ └── feeBump.ts
│ ├── package.json
│ └── README.md
├── client/ Fluid client library (TypeScript)
│ ├── src/
│ │ └── index.ts
│ └── package.json
└── README.md
Create a .env file in the server/ directory:
FLUID_FEE_PAYER_SECRET=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
FLUID_BASE_FEE=100
FLUID_FEE_MULTIPLIER=2.0
STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
PORT=3000
FLUID_RATE_LIMIT_WINDOW_MS=60000
FLUID_RATE_LIMIT_MAX=5Wraps a signed inner transaction in a fee-bump transaction.
Request:
{
"xdr": "<base64_encoded_signed_transaction_xdr>",
"submit": false
}Response:
{
"xdr": "<base64_encoded_fee_bump_transaction_xdr>",
"status": "ready",
"hash": null
}import { FluidClient } from "./client/src";
import StellarSdk from "@stellar/stellar-sdk";
const client = new FluidClient({
serverUrl: "http://localhost:3000",
networkPassphrase: StellarSdk.Networks.TESTNET,
horizonUrl: "https://horizon-testnet.stellar.org",
});
const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(/* your operation */)
.build();
transaction.sign(keypair);
const result = await client.requestFeeBump(transaction.toXDR(), false);
const submitResult = await client.submitFeeBumpTransaction(result.xdr);const StellarSdk = require("@stellar/stellar-sdk");
const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(/* your operation */)
.build();
transaction.sign(keypair);
const response = await fetch("http://localhost:3000/fee-bump", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
xdr: transaction.toXDR(),
submit: false,
}),
});
const { xdr: feeBumpXdr } = await response.json();
const feeBumpTx = StellarSdk.TransactionBuilder.fromXDR(
feeBumpXdr,
StellarSdk.Networks.TESTNET
);
const result = await server.submitTransaction(feeBumpTx);- Stateless: Each request is independent
- Open Hosting: Anyone can run their own Fluid instance
- TypeScript: Type-safe code for both server and client
- Node.js: Easy to deploy and maintain
- Keep your
FLUID_FEE_PAYER_SECRETsecure - Use HTTPS in production
- Consider rate limiting for public endpoints
- Monitor your fee payer account balance
cd server
npm run dev
npm run build
npm startcd client
npm run dev
npm run buildTest scripts are in the initial-test/ directory:
cd initial-test
node test-fluid-server.js- User builds and signs a transaction locally
- User sends the signed XDR to Fluid server
- Fluid wraps it in a fee-bump transaction
- Fluid signs the fee-bump with its own keypair
- Fluid returns the fee-bump XDR (or submits it if
submit: true) - Client submits the fee-bump transaction to Stellar network
- By default: The client (your application) submits the final transaction
- With
submit: true: The server can submit it directly (requiresSTELLAR_HORIZON_URL)
- Fee is paid by Fluid's fee payer account
Who submits the final transaction:
- Default behavior: The client submits the fee-bump transaction after receiving it from the server
- Optional: Set
submit: truein the request to have the server submit it directly
- DApps allowing users to pay with USDC/EURT/etc. without holding XLM
- Payment processors covering fees for better UX
- Removing fee barriers for end users
- Batch transaction processing
ISC
Contributions welcome. This is an MVP with room for improvement:
- Dynamic fee calculation
- Multi-signer support
- Rate limiting
- Monitoring dashboard
- Multiple fee assets