flowvault-sdk
v0.1.1
Published
TypeScript SDK for FlowVault — Programmable USDCx Routing on Stacks
Maintainers
Readme
FlowVault SDK
TypeScript SDK for FlowVault — Programmable USDCx Routing on Stacks.
FlowVault SDK wraps every FlowVault smart-contract function into a clean, typed TypeScript API so developers can integrate programmable stablecoin flows in minutes.
Table of Contents
- Features
- Installation
- Quick Start
- Configuration
- API Reference
- Routing Model
- Utilities
- Error Handling
- Network Configuration
- Development
- Versioning
Features
- Minimal — zero opinions, pure interface layer.
- Typed — full TypeScript types for every input & output.
- Deterministic — inputs are validated before any network call.
- Network configurable —
"testnet"or"mainnet"with one string. - Frontend-friendly — works in Node.js, bundlers, and browser runtimes.
Installation
npm install flowvault-sdk
# or
yarn add flowvault-sdkThe SDK depends on @stacks/transactions and @stacks/network (peer-level — installed automatically).
Quick Start
import { FlowVault, tokenToMicro } from "flowvault-sdk";
// 1. Initialise
const vault = new FlowVault({
network: "testnet",
contractAddress: "STD7QG84VQQ0C35SZM2EYTHZV4M8FQ0R7YNSQWPD",
contractName: "flowvault",
tokenContractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",
tokenContractName: "usdcx",
senderKey: process.env.STACKS_PRIVATE_KEY!,
});
// 2. Set routing rules
await vault.setRoutingRules({
lockAmount: tokenToMicro("100"), // lock 100 USDCx
lockUntilBlock: 210000, // until block 210 000
splitAddress: "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG",
splitAmount: tokenToMicro("50"), // send 50 USDCx to split address
});
// 3. Deposit USDCx (routing rules execute automatically)
await vault.deposit(tokenToMicro("500"));
// 4. Read vault state
const state = await vault.getVaultState(
"ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG"
);
console.log("Locked:", state.lockedBalance);
console.log("Available:", state.unlockedBalance);
// 5. Withdraw unlocked funds
await vault.withdraw(tokenToMicro("200"));Browser Wallet Mode Example
import { request } from "@stacks/connect";
import { FlowVault, tokenToMicro } from "flowvault-sdk";
const senderAddress = "ST...connected-wallet-address";
const vault = new FlowVault({
network: "testnet",
contractAddress: "STD7QG84VQQ0C35SZM2EYTHZV4M8FQ0R7YNSQWPD",
contractName: "flowvault",
tokenContractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",
tokenContractName: "usdcx",
senderAddress,
contractCallExecutor: async (call) => {
return request("stx_callContract", {
contract: `${call.contractAddress}.${call.contractName}`,
functionName: call.functionName,
functionArgs: call.functionArgs,
network: call.network,
postConditionMode:
String(call.postConditionMode ?? "allow").toLowerCase().includes("deny")
? "deny"
: "allow",
postConditions: call.postConditions,
});
},
});
await vault.deposit(tokenToMicro("10"));Configuration
| Parameter | Type | Required | Description |
| ----------------------- | -------- | -------- | ---------------------------------------- |
| network | string | Yes | "testnet" or "mainnet" |
| contractAddress | string | Yes | FlowVault deployer address |
| contractName | string | Yes | FlowVault contract name |
| tokenContractAddress | string | Yes | USDCx token deployer address |
| tokenContractName | string | Yes | USDCx token contract name |
| senderKey | string | No* | Private key for signing transactions |
| senderAddress | string | No | Sender address for wallet-executor mode |
| contractCallExecutor | func | No | Custom write executor (wallet signing) |
| postConditions | array | No | Default post-conditions for write calls |
| postConditionMode | string | No | "allow" or "deny" post-condition mode |
* Required for state-changing methods (deposit, withdraw, setRoutingRules, clearRoutingRules). Read-only methods work without it.
If you provide contractCallExecutor, write methods use that executor and do not require senderKey.
Default Testnet Addresses
import { DEFAULT_CONTRACTS } from "flowvault-sdk";
console.log(DEFAULT_CONTRACTS.testnet);
// {
// contractAddress: "STD7QG84VQQ0C35SZM2EYTHZV4M8FQ0R7YNSQWPD",
// contractName: "flowvault",
// tokenContractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",
// tokenContractName: "usdcx",
// }API Reference
Constructor
const vault = new FlowVault(config: FlowVaultConfig);Validates configuration and stores network / contract metadata. Throws InvalidConfigurationError if required fields are missing.
Signer Modes
The SDK supports two write-signing modes:
senderKeymode (backend/services):
- SDK builds, signs, and broadcasts using
makeContractCall+broadcastTransaction.
contractCallExecutormode (browser wallets):
- SDK builds validated Clarity args and delegates transaction execution to your wallet adapter.
- Useful with
@stacks/connectwhere the private key is not exposed.
When using contractCallExecutor, pass senderAddress from the connected wallet account so SDK validations and read helpers can use the right principal context.
setRoutingRules
await vault.setRoutingRules(
rules: RoutingRules,
options?: TransactionOptions
): Promise<TransactionResult>Configure routing rules that execute automatically on the next deposit.
Parameters (all in micro-units):
| Field | Type | Description |
| ---------------- | ---------------- | ----------------------------------------------- |
| lockAmount | MicroAmount | Amount to lock (0 = no lock) |
| lockUntilBlock | number | Block height for lock expiry |
| splitAddress | string \| null | Recipient of split amount |
| splitAmount | MicroAmount | Amount forwarded to split address (0 = no split)|
Validation: Rejects negative values, invalid addresses, zero split-address with positive split-amount, and self-splits.
Important: when lockAmount > 0, lockUntilBlock must be strictly greater than the current chain block.
Recommended pattern:
const current = await vault.getCurrentBlockHeight("ST...sender");
const lockDuration = 144;
await vault.setRoutingRules({
lockAmount: tokenToMicro("100"),
lockUntilBlock: current + lockDuration,
splitAddress: null,
splitAmount: tokenToMicro("0"),
});deposit
await vault.deposit(
amount: MicroAmount,
options?: TransactionOptions
): Promise<TransactionResult>Deposit USDCx into the vault. Routing rules (lock / split / hold) are applied automatically.
amount— micro-units asbigint, integernumber, or numericstring.
withdraw
await vault.withdraw(
amount: MicroAmount,
options?: TransactionOptions
): Promise<TransactionResult>Withdraw unlocked USDCx. Fails on-chain if amount exceeds unlocked balance.
amount— micro-units asbigint, integernumber, or numericstring.
clearRoutingRules
await vault.clearRoutingRules(options?: TransactionOptions): Promise<TransactionResult>Delete all routing rules for the caller. Future deposits go straight to unlocked balance.
getVaultState
await vault.getVaultState(userAddress: string): Promise<VaultState>Read-only. Returns the complete vault state including balances and active routing rules.
interface VaultState {
totalBalance: number;
lockedBalance: number;
unlockedBalance: number;
lockUntilBlock: number;
currentBlock: number;
routingRules: RoutingRules;
}getRoutingRules
await vault.getRoutingRules(userAddress: string): Promise<RoutingRules | null>Read-only. Returns the user's routing rules, or null if none are configured.
hasLockedFunds
await vault.hasLockedFunds(userAddress: string): Promise<boolean>Read-only. Returns true if the user has locked funds that haven't expired.
getCurrentBlockHeight
await vault.getCurrentBlockHeight(senderAddress: string): Promise<number>Read-only. Returns the current Stacks block height. Useful for calculating lockUntilBlock values.
Routing Model
FlowVault executes three routing operations at deposit time:
- LOCK — A fixed amount is locked until a specific block height. Locked funds cannot be withdrawn until the lock expires.
- SPLIT — A fixed amount is immediately forwarded to another Stacks principal.
- HOLD — The remainder stays in the vault as unlocked balance, available for withdrawal at any time.
Deposit 1000 USDCx
├─ LOCK 200 (until block 200000)
├─ SPLIT 300 → ST2CY5V39NHDP...
└─ HOLD 500 (available immediately)Utilities
import {
tokenToMicro, // "1.5" -> 1500000n
microToToken, // 1500000 -> "1.5"
microToNumber, // 1500000 -> 1500000 (throws if unsafe)
isValidAddress, // "ST..." → true/false
isBlockInFuture, // (target, current) → boolean
} from "flowvault-sdk";Error Handling
All SDK errors extend FlowVaultError:
| Error Class | When |
| --------------------------- | --------------------------------------------- |
| InvalidAmountError | Negative, NaN, or non-integer amount |
| InvalidAddressError | Bad Stacks address format |
| InvalidConfigurationError | Missing config fields or bad network |
| InvalidRoutingRuleError | Routing rules are invalid or unsafe |
| NetworkConfigurationError | Unsupported network selection |
| ContractCallError | On-chain rejection (includes error code) |
| NetworkError | RPC unreachable, timeouts |
| ParsingError | Contract response could not be parsed safely |
ContractCallError includes a code property mapping to FlowVault contract errors:
| Code | Meaning | | ---- | ------------------------------------------ | | 1001 | Invalid amount | | 1002 | Insufficient balance | | 1003 | Funds are locked | | 1004 | Routing amounts exceed deposit | | 1007 | Split address required | | 1008 | Lock block must be in the future | | 1010 | Lock exceeds hold | | 1011 | Cannot split to yourself |
Network Configuration
// Testnet (default for development)
const vault = new FlowVault({ network: "testnet", ... });
// Mainnet
const vault = new FlowVault({ network: "mainnet", ... });The SDK passes the network string directly to @stacks/transactions which handles RPC endpoint resolution internally.
Post-Conditions (Optional)
You can attach post-conditions to protect token movement during deposit and withdraw.
import { PostConditionMode } from "@stacks/transactions";
const vault = new FlowVault({
network: "testnet",
contractAddress: "...",
contractName: "flowvault",
tokenContractAddress: "...",
tokenContractName: "usdcx",
senderKey: process.env.STACKS_PRIVATE_KEY!,
postConditionMode: PostConditionMode.Deny,
postConditions: [/* your post-conditions */],
});
await vault.deposit(tokenToMicro("10"), {
postConditionMode: "deny",
postConditions: [/* per-call post-conditions */],
});Common Integration Notes
- Use
tokenToMicro("...")for all user-entered token amounts. - Read methods can run without
senderKey; write methods need eithersenderKeyorcontractCallExecutor. - In wallet mode, return wallet response objects as-is; SDK extracts
txid/txId/idautomatically. - For lock rules, prefer duration-based UX (
currentBlock + duration) to avoid stale absolute inputs.
Development
# Install dependencies
npm install
# Run tests
npm test
# Watch mode
npx vitest
# Build
npm run buildProject Structure
flowvault-sdk/
├── src/
│ ├── index.ts — Public re-exports
│ ├── FlowVault.ts — Main SDK class
│ ├── types.ts — TypeScript interfaces
│ ├── errors.ts — Error classes
│ ├── constants.ts — Defaults & error map
│ ├── network.ts — Network helpers
│ └── utils.ts — Validation, conversion, parsing
├── tests/ — Vitest test suites (100 tests)
├── docs/ — Extended documentation
├── dist/ — Compiled output (generated)
├── package.json
├── tsconfig.json
└── vitest.config.tsVersioning
Follows Semantic Versioning:
| Version | Meaning |
| ------- | ------------------------------ |
| 0.1.0 | Initial public SDK |
| 0.2.0 | Feature additions |
| 1.0.0 | Stable, production-ready API |
Breaking changes will be documented in release notes.
License
MIT
