Overview
Fystack enables developers to create production-ready cryptocurrency payment gateways without managing private keys or blockchain infrastructure. The platform handles wallet creation, deposit address generation across multiple chains, automatic fund consolidation, and transaction monitoring through a unified SDK.
Architecture Overview
The payment flow consists of six steps:
- Applications create wallets per user via the Fystack SDK
- Fystack generates deposit addresses across Ethereum, Solana, Tron, and Bitcoin
- Users deposit funds to their assigned addresses
- Sweep tasks automatically consolidate funds into a central hot wallet when thresholds are met
- Consolidated funds reach the hot wallet for settlement
- Withdrawals and payouts distribute funds to merchants or external addresses
The system uses Hyper wallets (HD-derived for high-volume user wallets) and MPC wallets (threshold signatures for treasury storage), eliminating direct private key exposure.
Prerequisites
- Node.js 22 or later
- Fystack account with API credentials
- Workspace ID from the Fystack dashboard

Copy workspaceID from API Key section
Setup Steps
Step 1: Install and Configure SDK
npm install @fystack/sdk
Create a .env file with credentials:
FYSTACK_API_KEY=your-api-key
FYSTACK_API_SECRET=your-api-secret
FYSTACK_WORKSPACE_ID=your-workspace-uuid
HOT_WALLET_ID=your-hot-wallet-uuid
Initialize the SDK:
import {
FystackSDK,
Environment,
WalletType,
WalletPurpose,
AddressType,
} from "@fystack/sdk";
const sdk = new FystackSDK({
credentials: {
apiKey: process.env.FYSTACK_API_KEY!,
apiSecret: process.env.FYSTACK_API_SECRET!,
},
environment: Environment.Production,
workspaceId: process.env.FYSTACK_WORKSPACE_ID!
});
Step 2: Create a Hot Wallet
const hotWallet = await sdk.createWallet(
{
name: "Treasury Hot Wallet",
walletType: WalletType.MPC,
walletPurpose: WalletPurpose.General,
},
true // Wait for creation to complete
);
console.log("Hot wallet ID:", hotWallet.wallet_id);

Create a wallet through Fystack portal
MPC wallet creation is asynchronous; the distributed key generation protocol requires 10-30 seconds across multiple nodes.
Step 3: Create a Sweep Task
Visit the Fystack portal → Automation → Add sweep task. Configure:
- Task name
- Destination wallet (the MPC hot wallet)
- Minimum trigger value in USD
The sweep task ID appears as the last segment of the URL.
Step 4: Create Per-User Wallets
async function createUserWallet(userId: string) {
const wallet = await sdk.createWallet(
{
name: `user_${userId}`,
walletType: WalletType.Hyper,
walletPurpose: WalletPurpose.OneTimeUse,
sweepTaskID: '6bdc8e47-dc1a-4416-9158-b99cdc23205a'
},
true
);
return wallet;
}
OneTimeUse wallets attempt to empty completely during sweeps rather than preserving dust amounts for future gas fees, preventing stranded balances across thousands of deposit wallets.
Step 5: Get Multi-Chain Deposit Addresses
async function getDepositAddresses(walletId: string) {
const evm = await sdk.getDepositAddress(walletId, AddressType.Evm);
const sol = await sdk.getDepositAddress(walletId, AddressType.Solana);
return { address, qr_code };
}
Supported chains by address type:
| Type | Chains | Format |
|---|---|---|
evm |
Ethereum, Polygon, Arbitrum, Optimism, Base | 0x... |
sol |
Solana | Base58 public key |
tron |
Tron | T... |
btc |
Bitcoin | bc1q... |
Display endpoint example:
app.get("/api/payment/:userId/address", async (req, res) => {
const { userId } = req.params;
const { chain } = req.query;
const wallet = await getOrCreateWallet(userId);
const addressType = chain === "solana" ? AddressType.Solana : AddressType.Evm;
const deposit = await sdk.getDepositAddress(wallet.wallet_id, addressType);
res.json({
address: deposit.address,
qrCode: deposit.qr_code,
chain,
});
});
Step 6: Listen for Deposits with Webhooks
Fystack sends webhook events for transaction state changes:
| Event | Trigger | Purpose |
|---|---|---|
deposit.pending |
On-chain detection, unconfirmed | Show pending UI |
deposit.confirmed |
Blockchain confirmation | Credit account |
withdrawal.pending |
Withdrawal request created | Update records |
withdrawal.confirmed |
Transaction confirmed | Mark complete |
withdrawal.failed |
Transaction failed | Alert team |
Verify webhook signatures using Ed25519:
import crypto from "crypto";
const { public_key } = await sdk.getWebhookPublicKey(
process.env.FYSTACK_WORKSPACE_ID!
);
app.post("/webhook/fystack", async (req, res) => {
const signature = req.headers["x-webhook-signature"] as string;
const event = req.headers["x-webhook-event"] as string;
const payload = req.body;
const canonical = JSON.stringify(sortKeysDeep(payload));
const isValid = crypto.verify(
null,
Buffer.from(canonical),
{
key: Buffer.from(public_key, "base64"),
format: "der",
type: "spki",
},
Buffer.from(signature, "base64")
);
if (!isValid) {
return res.status(401).json({ error: "Invalid signature" });
}
switch (event) {
case "deposit.confirmed":
await handleDepositConfirmed(payload);
break;
case "withdrawal.confirmed":
await handleWithdrawalConfirmed(payload);
break;
case "withdrawal.failed":
await handleWithdrawalFailed(payload);
break;
}
res.status(200).json({ received: true });
});
function sortKeysDeep(obj: any): any {
if (Array.isArray(obj)) return obj.map(sortKeysDeep);
if (obj && typeof obj === "object") {
return Object.keys(obj)
.sort()
.reduce((acc: any, key) => {
acc[key] = sortKeysDeep(obj[key]);
return acc;
}, {});
}
return obj;
}
Summary
This part covers wallet infrastructure, deposit address generation, automatic consolidation via sweep tasks, and webhook-based deposit detection. Part 2 will explore withdrawals, treasury management, and production architecture patterns.
For questions about custody setup, visit the inquiry form or join the Telegram community.





Top comments (0)