The sdk.balance resource provides methods to manage the dual ledger system of OFFER-HUB: fast off-chain balances (with available and reserved splits) synchronized with on-chain Stellar trustline or payment provider assets.
Controller: apps/api/src/modules/balance/balance.controller.ts
DTOs:
CreditBalanceDto (apps/api/src/modules/balance/dto/credit-balance.dto.ts)
DebitBalanceDto (apps/api/src/modules/balance/dto/debit-balance.dto.ts)
ReserveBalanceDto (apps/api/src/modules/balance/dto/reserve-balance.dto.ts)
ReleaseBalanceDto (apps/api/src/modules/balance/dto/release-balance.dto.ts)
CancelReservationDto (apps/api/src/modules/balance/dto/cancel-reservation.dto.ts)
DeductReservedDto (apps/api/src/modules/balance/dto/deduct-reserved.dto.ts)
Service: apps/api/src/modules/balance/balance.service.ts
Database Models: Balance, BalanceTransaction, Reservation in packages/db/prisma/schema.prisma
Balance Ledger Lifecycle
Resource Overview
Method HTTP Route Description sdk.balance.get()GET /api/v1/users/:userId/balanceRetrieves current available and reserved balance sdk.balance.credit()POST /api/v1/users/:userId/balance/creditCredits funds to available balance sdk.balance.debit()POST /api/v1/users/:userId/balance/debitDebits funds from available balance sdk.balance.reserve()POST /api/v1/users/:userId/balance/reserveMoves funds from available to reserved sdk.balance.release()POST /api/v1/users/:userId/balance/releaseMoves funds from reserved back to available sdk.balance.cancelReservation()POST /api/v1/users/:userId/balance/cancel-reservationCancels all reservations for a specific order sdk.balance.deductReserved()POST /api/v1/users/:userId/balance/deduct-reservedPermanently consumes reserved funds for escrow sdk.balance.sync()POST /api/v1/users/:userId/balance/syncReconciles off-chain ledger with provider / Stellar chain sdk.balance.verify()GET /api/v1/users/:userId/balance/verifyAudits off-chain ledger against on-chain balance without mutating
Common Types
interface Balance {
userId: string;
available: string; // Decimal string formatted, e.g. "100.00"
reserved: string; // Decimal string formatted, e.g. "25.00"
total: string; // Sum of available + reserved, e.g. "125.00"
currency: string; // Currency ISO code, e.g. "USD" or "USDC"
updatedAt: string; // ISO-8601 timestamp
}
Methods
Retrieves the current available and reserved balance for a specific user.
Signature
async get(userId: string): Promise<Balance>
Parameters
Parameter Type Required Description userIdstringTarget OFFER-HUB user ID (usr_...)
Return Type
Promise<Balance>
Errors
Error Class HTTP Status Code Cause NotFoundError404USER_NOT_FOUNDUser does not exist
Example
import { OfferHubSDK } from '@offerhub/sdk';
const sdk = new OfferHubSDK({
apiUrl: process.env.OFFERHUB_API_URL!,
apiKey: process.env.OFFERHUB_API_KEY!,
});
const balance = await sdk.balance.get('usr_abc123');
console.log(`Available: ${balance.available} ${balance.currency}`);
console.log(`Reserved: ${balance.reserved} ${balance.currency}`);
Credits an amount directly to the user's available balance (used for manual top-ups, referral bonuses, or platform adjustments).
Signature
async credit(userId: string, params: CreditBalanceParams): Promise<Balance>
Parameters
Parameter Type Required Description userIdstringTarget user ID params.amountstringPositive numeric string representing the amount to credit (e.g. '50.00') params.currencystringNo Currency code (defaults to platform base currency) params.referencestringNo External reference ID (e.g. promotional ID or external transaction ID) params.descriptionstringNo Human-readable audit log description
Return Type
Promise<Balance>
Errors
Error Class HTTP Status Code Cause ValidationError400INVALID_AMOUNTAmount is zero, negative, or not a valid decimal string NotFoundError404USER_NOT_FOUNDUser does not exist
Example
const updatedBalance = await sdk.balance.credit('usr_abc123', {
amount: '50.00',
currency: 'USD',
reference: 'bonus_signup_2026',
description: 'Welcome promotion bonus',
});
console.log('New available balance:', updatedBalance.available);
Debits an amount directly from the user's available balance (used for platform fees or manual balance adjustments).
Signature
async debit(userId: string, params: DebitBalanceParams): Promise<Balance>
Parameters
Parameter Type Required Description userIdstringTarget user ID params.amountstringAmount to deduct from available balance params.currencystringNo Currency code params.referencestringNo External audit reference params.descriptionstringNo Description for transaction log
Return Type
Promise<Balance>
Errors
Error Class HTTP Status Code Cause InsufficientFundsError422INSUFFICIENT_FUNDSRequested debit amount exceeds available balance ValidationError400INVALID_AMOUNTAmount is negative or formatted incorrectly NotFoundError404USER_NOT_FOUNDUser does not exist
Example
import { OfferHubSDK, InsufficientFundsError } from '@offerhub/sdk';
try {
const updatedBalance = await sdk.balance.debit('usr_abc123', {
amount: '15.00',
currency: 'USD',
description: 'Monthly platform fee',
});
console.log('Debited successfully. Remaining:', updatedBalance.available);
} catch (error) {
if (error instanceof InsufficientFundsError) {
console.error(`Debit failed: need ${error.required}, available ${error.available}`);
}
}
Transfers funds from available to reserved balance. This is executed during order checkout to lock buyer funds before the on-chain escrow contract is deployed.
Signature
async reserve(userId: string, params: ReserveBalanceParams): Promise<Balance>
Parameters
Parameter Type Required Description userIdstringTarget user ID params.amountstringAmount to lock into reservations params.orderIdstringNo Associated order ID (ord_...) params.currencystringNo Currency code params.reasonstringNo Reason description (e.g. 'Order checkout lock')
Return Type
Promise<Balance>
Errors
Error Class HTTP Status Code Cause InsufficientFundsError422INSUFFICIENT_FUNDSUser available balance is less than amount ValidationError400VALIDATION_ERRORInvalid amount format NotFoundError404USER_NOT_FOUNDUser does not exist
Example
const balanceAfterReserve = await sdk.balance.reserve('usr_buyer123', {
amount: '100.00',
orderId: 'ord_xyz789',
reason: 'Escrow funding reservation',
});
console.log('Available:', balanceAfterReserve.available); // Decreased by 100.00
console.log('Reserved:', balanceAfterReserve.reserved); // Increased by 100.00
Releases funds from reserved back into available balance (e.g., when an order is cancelled or times out before escrow is funded).
Signature
async release(userId: string, params: ReleaseBalanceParams): Promise<Balance>
Parameters
Parameter Type Required Description userIdstringTarget user ID params.amountstringAmount to move from reserved to available params.orderIdstringNo Associated order ID params.currencystringNo Currency code params.reasonstringNo Reason description
Return Type
Promise<Balance>
Errors
Error Class HTTP Status Code Cause ValidationError400RESERVATION_EXCEEDEDAmount to release exceeds currently reserved balance NotFoundError404USER_NOT_FOUNDUser does not exist
Example
const balanceAfterRelease = await sdk.balance.release('usr_buyer123', {
amount: '100.00',
orderId: 'ord_xyz789',
reason: 'Order cancelled by buyer prior to escrow funding',
});
console.log('Restored available funds:', balanceAfterRelease.available);
Cancels all active reservations tied to a specific orderId, restoring the full reserved sum back to available.
Signature
async cancelReservation(userId: string, params: CancelReservationParams): Promise<Balance>
Parameters
Parameter Type Required Description userIdstringTarget user ID params.orderIdstringOrder ID whose reservations should be cancelled params.reasonstringNo Optional cancellation reason
Return Type
Promise<Balance>
Errors
Error Class HTTP Status Code Cause NotFoundError404RESERVATION_NOT_FOUNDNo active reservations found for the specified orderId ValidationError400VALIDATION_ERRORMissing orderId
Example
const restoredBalance = await sdk.balance.cancelReservation('usr_buyer123', {
orderId: 'ord_xyz789',
reason: 'Deployment timeout',
});
console.log('All reservations cancelled. Available:', restoredBalance.available);
Permanently deducts funds from the reserved balance once they have been locked into an on-chain smart contract escrow.
Signature
async deductReserved(userId: string, params: DeductReservedParams): Promise<Balance>
Parameters
Parameter Type Required Description userIdstringTarget user ID params.amountstringAmount to deduct from reserved balance params.orderIdstringNo Associated order ID params.currencystringNo Currency code params.reasonstringNo Reason description (e.g. 'Escrow funded on Stellar')
Return Type
Promise<Balance>
Errors
Error Class HTTP Status Code Cause ValidationError400INVALID_RESERVED_DEDUCTIONAmount to deduct exceeds reserved balance NotFoundError404USER_NOT_FOUNDUser does not exist
Example
const balance = await sdk.balance.deductReserved('usr_buyer123', {
amount: '100.00',
orderId: 'ord_xyz789',
reason: 'Soroban escrow contract funded on-chain',
});
console.log('Reserved balance decremented to:', balance.reserved);
Triggers a force reconciliation between the local database ledger and the payment provider (Stellar Horizon stream or AirTM ledger). If a discrepancy is detected, the database balance is updated and an audit event is emitted.
Signature
async sync(userId: string): Promise<BalanceSyncResult>
Parameters
Parameter Type Required Description userIdstringTarget user ID to synchronize
Return Type
interface BalanceSyncResult {
synced: boolean;
previousBalance: Balance;
currentBalance: Balance;
discrepancyDetected: boolean;
adjustment?: string | null;
}
Errors
Error Class HTTP Status Code Cause NotFoundError404USER_NOT_FOUNDUser does not exist ProviderTimeoutError504PROVIDER_TIMEOUTExternal blockchain node or payment provider timed out
Example
const syncResult = await sdk.balance.sync('usr_abc123');
if (syncResult.discrepancyDetected) {
console.warn(`Discrepancy reconciled. Adjusted by: ${syncResult.adjustment}`);
} else {
console.log('Balance in sync with provider.');
}
Performs a read-only audit comparing off-chain ledger balances with the user's on-chain Stellar trustline or external provider balance without performing mutations.
Signature
async verify(userId: string): Promise<BalanceVerification>
Parameters
Parameter Type Required Description userIdstringTarget user ID to verify
Return Type
interface BalanceVerification {
isValid: boolean;
offChainBalance: Balance;
onChainBalance: {
address: string;
balance: string;
asset: string;
};
difference: string;
}
Errors
Error Class HTTP Status Code Cause NotFoundError404USER_NOT_FOUNDUser or wallet does not exist OfferHubError500VERIFICATION_FAILEDHorizon connection error or RPC failure
Example
const verification = await sdk.balance.verify('usr_abc123');
if (!verification.isValid) {
console.error(`Balance mismatch detected! Difference: ${verification.difference}`);
// Trigger automated sync or alert admin
await sdk.balance.sync('usr_abc123');
} else {
console.log('Off-chain and on-chain balances match.');
}
Users Reference (sdk.users) — User identities
Wallet Reference (sdk.wallet) — Deposit addresses and transaction records
Deposits Guide — Deep dive into payment rails and funding
SDK Quick Start — Getting started