@compx/sdk
v2.1.4
Published
TypeScript SDK for the CompX platform on Algorand: staking, lending, payments, and clAMM (coming soon)
Maintainers
Readme
CompX SDK
TypeScript SDK for the CompX platform on Algorand. One package, multiple domains: staking, lending, payments, and clAMM (coming soon).
Domain support
| Domain | Client | Status | What it covers |
|--------|--------|--------|----------------|
| Lending | sdk.lending | Supported | Markets, APY/LST pricing, oracle prices, user & debt positions |
| Staking | sdk.staking | Supported | Pool state, staker records, annualized APR estimates |
| Payments | sdk.payments | Supported | Waypoint route discovery/reads, fee tiers, and vesting/claimable calculations |
| clAMM | sdk.clamm | Coming soon | Concentrated-liquidity AMM (placeholder; not yet usable) |
The SDK is read- and calculation-focused: it reads on-chain state and computes derived values without a wallet. Transaction signing (deposit, stake, route creation, etc.) is performed in the application layer with a connected signer.
Installation
npm install @compx/sdk
# or
pnpm add @compx/sdk
# or
yarn add @compx/sdkalgosdk is a peer dependency:
npm install algosdkQuick Start
import { CompXSDK } from '@compx/sdk';
// The simplest setup: just pick a network. The SDK defaults to the public
// Nodely Algod endpoint for that network.
const sdk = new CompXSDK({ network: 'mainnet' });import { CompXSDK } from '@compx/sdk';
import algosdk from 'algosdk';
// Or supply your own Algod client (custom node, token, port)
const algodClient = new algosdk.Algodv2(
'', // API token (empty for public nodes)
'https://mainnet-api.algonode.cloud', // Algod URL
'' // Port
);
const sdk = new CompXSDK({
network: 'mainnet', // Required: 'mainnet' or 'testnet'
// algodClient, // Optional: defaults to the Nodely endpoint for the network
// indexerClient, // Optional: for historical queries
// masterRepoAppId, // Optional: override Master Repo registry app ID (defaults per network)
// payments: { registryAppId, fluxOracleAppId }, // Optional: payments config
});
// Lending (no wallet needed)
const market = await sdk.lending.getMarket(12345678);
console.log('Supply APY:', market.supplyApy);
// Staking (no wallet needed)
const pool = await sdk.staking.getPool(23456789);
console.log('Total staked:', pool.totalStaked.toString());
// Payments (pure calculations)
const fee = sdk.payments.estimateRouteFee(1_000_000n, 31566704n, 0, 31566704n);
console.log('Route fee:', fee.toString());You can also construct any domain client on its own:
import { LendingClient, StakingClient, PaymentsClient } from '@compx/sdk';
const lending = new LendingClient({ algodClient, network: 'mainnet' });
const staking = new StakingClient({ algodClient, network: 'mainnet' });
const payments = new PaymentsClient({ algodClient, network: 'mainnet' }); // registry/flux default per networkConfiguration
CompXSDK accepts a single CompXSDKConfig:
| Field | Required | Description |
|-------|----------|-------------|
| algodClient | No | Pre-configured algosdk.Algodv2 client. Defaults to the public Nodely endpoint for network |
| network | Yes | 'mainnet' or 'testnet' |
| indexerClient | No | algosdk.Indexer for historical queries |
| masterRepoAppId | No | Master Repo registry app ID for on-chain market and pool discovery (defaults to the known app ID for each network) |
| payments | No | { registryAppId?: bigint; fluxOracleAppId?: bigint } — Waypoint registry / Flux oracle app IDs (default per network; algodClient is passed through for on-chain route reads) |
Lending domain (sdk.lending)
Read-only access to CompX lending markets. All methods work without a wallet.
| Method | Description |
|--------|-------------|
| getMarket(appId) | Comprehensive MarketData (APYs, TVL, utilization, rate model) |
| getAPY(appId) | Current supply/borrow APYs (APYData) |
| getLSTPrice(appId) | LST exchange rate (LSTPrice) |
| getMarkets(appIds) | Multiple markets in parallel |
| getAllMarkets() | All markets discovered on chain via the Master Repo registry, enriched with on-chain data |
| getMarketList() | Basic market list discovered on chain via the Master Repo registry (MarketInfo[]) |
| getOraclePrice(oracleAppId, assetId) | Single oracle price (OraclePrice) |
| getOraclePrices(oracleAppId, assetIds) | Multiple oracle prices (OraclePriceMap) |
| getAssetInfo(assetId) / getAssetsInfo(assetIds) | Asset metadata from Algorand |
| getMarketLoanRecords(appId) | All active loan records in a market |
| getAllDebtPositions(marketAppIds) | Debt positions with health metrics |
| getAllDebtPositionsFromAllMarkets() | Debt positions across all markets |
| getUserPosition(appId, address) | A user's position in one market |
| getAllUserPositions(address) | Aggregated positions across all markets |
| getUserPositionsForMarkets(address, appIds) | Aggregated positions across given markets |
| getGlobalState(appId) | Raw market global state |
const market = await sdk.lending.getMarket(12345678);
console.log(`Supply APY: ${market.supplyApy.toFixed(2)}%`);
console.log(`Borrow APY: ${market.borrowApy.toFixed(2)}%`);
const positions = await sdk.lending.getAllUserPositions('USERADDRESS...');
console.log(`Active in ${positions.activeMarkets} markets`);Calculation and state-decode helpers are also exported directly: utilNormBps, aprBpsKinked, currentAprBps, calculateLSTDue, calculateAssetDue, calculateLSTPrice, microToStandard, standardToMicro, getApplicationGlobalState, getBoxValue, decodeDepositRecord, decodeLoanRecord, decodeOraclePrice, createDepositBoxName, createLoanBoxName, createOraclePriceBoxName, getRegisteredContracts, getRegisteredAppIdsByType, getRegisteredLendingAppIds, getRegisteredStakingAppIds, DEFAULT_MASTER_REPO_APP_ID.
Building lending transactions
The SDK can build ready-to-sign, correctly grouped unsigned transactions for the core lending actions against ASA-base markets. The SDK never signs or submits: each builder returns unsigned algosdk.Transaction objects with the atomic group ID already assigned, plus signing instructions and metadata. App-call resources (boxes, foreign apps/assets) and inner-transaction fees are resolved up front (via simulate, no signer required), so the group is valid as-is once signed.
| Method | Group shape |
|--------|-------------|
| buildDepositTransactions({ appId, sender, amount }) | [optInLST?] → baseTransfer → depositASA |
| buildWithdrawTransactions({ appId, sender, amount }) | [optInBase?] → lstTransfer → withdrawDeposit |
| buildBorrowTransactions({ appId, sender, borrowAmount, collateralAmount, collateralTokenId }) | [optInBase?] → gas → collateralTransfer → borrow |
| buildRepayTransactions({ appId, sender, amount }) | baseTransfer → repayLoanASA |
All amounts are in base units (micro units) as bigint | number. Each builder also accepts an optional appCallMaxFee (microAlgos, defaults to DEFAULT_APP_CALL_MAX_FEE = 250_000) and validityWindow (rounds). Needed asset opt-ins are detected and prepended automatically; metadata.optInsIncluded lists any added.
const bundle = await sdk.lending.buildDepositTransactions({
appId: 12345678,
sender: 'USERADDRESS...',
amount: 1_000_000n, // base units of the base asset
});
// bundle.transactions: unsigned algosdk.Transaction[] with group ID assigned
// bundle.signers: [{ address, transactionIndexes }] signing instructions
// bundle.metadata: { action, appId, sender, baseTokenId, lstTokenId, optInsIncluded }
// Sign every transaction with the connected wallet, then submit the group:
const signed = await wallet.signTransactions(bundle.transactions);
await algodClient.sendRawTransaction(signed).do();The standalone functions buildDepositTransactions, buildWithdrawTransactions, buildBorrowTransactions, and buildRepayTransactions (which take (algodClient, params)) are also exported. See src/examples/lending-build-transactions.ts.
Staking domain (sdk.staking)
Read-only access to CompX staking pools.
| Method | Description |
|--------|-------------|
| getPoolAppIds() | Discover all staking pool app IDs from the on-chain Master Repo registry |
| getAllPools() | Discover and read StakingPoolState for every registered pool |
| getPool(appId) | Decoded StakingPoolState (stake, rewards, timing, fees) |
| getPools(appIds) | Multiple pools in parallel |
| getStakerInfo(appId, address) | A single staker's record (StakerInfo) or null |
| getPoolApr(appId, options) | Annualized APR percentage, or null if indeterminable |
| getMasterRepoAppId() | Master Repo app ID used for on-chain pool discovery |
| createStakerBoxName(address) | Builds the staker box name |
Pool discovery uses the same on-chain Master Repo registry as lending (defaulting to the known app ID per network); pass masterRepoAppId to override it.
// Discover and read every staking pool on chain
const pools = await sdk.staking.getAllPools();const pool = await sdk.staking.getPool(23456789);
// USD-accurate APR for cross-asset pools
const apr = await sdk.staking.getPoolApr(23456789, {
stakedAssetDecimals: 6,
rewardAssetDecimals: 6,
stakedAssetPriceUsd: 0.15,
rewardAssetPriceUsd: 1.0,
});
console.log(apr === null ? 'APR n/a' : `${apr.toFixed(2)}% APR`);The pure APR helper calculateStakingApr and formatAmount / SECONDS_PER_YEAR are exported for off-chain use.
Payments domain (sdk.payments)
On-chain discovery and reads for Waypoint payment routes (send/request tokens over time), plus portable fee and vesting calculations.
Payments uses a dedicated Waypoint registry (separate from the lending/staking Master Repo). Each route is its own deployed app; the registry's routes BoxMap indexes them by route app ID. The registry app ID defaults per network (mainnet 3253603509); override via payments.registryAppId.
| Method | Description |
|--------|-------------|
| getRouteAppIds() | Discover all route app IDs from the on-chain Waypoint registry |
| getRoute(routeAppId, nowTimestamp?) | Decoded, claim-aware RouteViewModel (linear or invoice) or null |
| getAllRoutes(nowTimestamp?) | Discover and read every registered route |
| getNominatedAssetId() | Nominated platform asset ID from the registry |
| getUserFluxTier(address) | A user's Flux fee-discount tier (0 when disabled) |
| estimateRouteFeeForUser(amount, tokenId, address) | Fee using on-chain tier + nominated asset |
| feeBps(isNominatedAsset, tier) | Fee in basis points for an asset/tier |
| estimateRouteFee(amount, tokenId, tier, nominatedAssetId) | Fee for a deposit amount (pure) |
| calculateClaimable(route, nowTimestamp?) | Currently claimable amount for a route (pure) |
| parseAmount(value, decimals) | Parse a decimal string into base units (pure) |
// Discover and read every route on chain
const routes = await sdk.payments.getAllRoutes();
// 25 bps for the nominated asset at tier 0 (pure calculation)
const fee = sdk.payments.estimateRouteFee(1_000_000n, 31566704n, 0, 31566704n);On-chain methods require an algodClient (passed through from CompXSDK). The pure calculation helpers (tierToBps, calculateFee, parseDecimalToUnits, calculateClaimableAmount, RouteKind, InvoiceRouteStatus, DEFAULT_WAYPOINT_REGISTRY_APP_ID, DEFAULT_FLUX_ORACLE_APP_ID, fee-tier tables) are exported directly.
Building payments transactions
The SDK can build ready-to-sign, correctly grouped unsigned transactions for Waypoint payment routes (linear send, invoice request, accept/decline, claim, and manual registry re-index). The SDK never signs or submits: each builder returns unsigned algosdk.Transaction objects with the atomic group ID already assigned, plus signing instructions and metadata. App-call resources and inner-transaction fees are resolved up front (via simulate, no signer required).
Route creation is multi-step because the route app must confirm before its routeAppId is known. Use the typed plan helpers (buildCreateLinearRoutePlan, buildCreateInvoiceRoutePlan) or call each step builder individually.
| Method | Group shape | Signer |
|--------|-------------|--------|
| buildCreateLinearApplicationTransactions | [createApplication] (standalone) | depositor |
| buildInitLinearRouteTransactions | [mbrPayment → initApp] | depositor |
| buildCreateLinearRouteTransactions | [tokenTransfer → createRoute] | depositor |
| buildCreateInvoiceApplicationTransactions | [createApplication] (standalone) | requester |
| buildInitInvoiceRouteTransactions | [mbrPayment → initApp] | requester |
| buildCreateInvoiceRouteTransactions | [createRoute] | requester |
| buildAcceptInvoiceRouteTransactions | [tokenTransfer → acceptRoute] | payer |
| buildDeclineInvoiceRouteTransactions | [declineRoute] | payer |
| buildClaimRouteTransactions | [optIn? → claim] | beneficiary |
| buildRegisterRouteTransactions | [registerRoute] on registry | any account (not admin-gated) |
Registry indexing is automatic via inner calls in createRoute (linear) and acceptRoute (invoice). buildRegisterRouteTransactions is only needed to re-index routes missing from the registry.
Claim (single group):
const bundle = await sdk.payments.buildClaimRouteTransactions({
routeAppId: 123456789n,
beneficiary: 'BENEFICIARY...',
routeKind: 'linear', // optional; auto-detected when omitted
});
const signed = await wallet.signTransactions(bundle.transactions);
await algodClient.sendRawTransaction(signed).do();Create linear route (multi-step):
import { extractCreatedAppId } from '@compx/sdk';
const plan = await sdk.payments.buildCreateLinearRoutePlan({
sender: 'DEPOSITOR...',
beneficiary: 'BENEFICIARY...',
tokenId: 31_566_704n,
depositAmount: 1_000_000n,
payoutAmount: 100_000n,
startTimestamp: BigInt(Math.floor(Date.now() / 1000)),
periodSeconds: 86_400n,
maxPeriods: 10n,
});
// Step 1: deploy route app
const signed1 = await wallet.signTransactions(plan.createApplication.transactions);
const { txid } = await algodClient.sendRawTransaction(signed1).do();
const confirmed = await algosdk.waitForConfirmation(algodClient, txid, 4);
const routeAppId = extractCreatedAppId(confirmed);
// Step 2: fund MBR + init
const initBundle = await plan.initApp(routeAppId);
const signed2 = await wallet.signTransactions(initBundle.transactions);
await algodClient.sendRawTransaction(signed2).do();
// Step 3: deposit + create route (registers in registry via inner call)
const routeBundle = await plan.createRoute(routeAppId);
const signed3 = await wallet.signTransactions(routeBundle.transactions);
await algodClient.sendRawTransaction(signed3).do();Standalone builder functions (which take (algodClient, params)) are also exported. See src/examples/payments-build-transactions.ts.
clAMM domain (sdk.clamm)
Not yet available. The clAMM contracts exist on-chain, but a stable SDK surface for manager-routed transactions is still being finalized. CLAMM_STATUS is 'pending', and constructing ClammClient throws. Track docs/clamm-contract-reference.md for progress.
Features
- Multi-domain: staking, lending, payments (clAMM coming soon)
- Read- and calculation-focused: no wallet required
- Bring your own
algosdkclients (custom nodes, tokens, networks) - Fully typed with detailed TypeScript definitions
- Ships CJS, ESM, and type declarations
Development
pnpm install # Install dependencies
pnpm run build # Build the SDK (CJS + ESM + d.ts)
pnpm run dev # Watch mode
pnpm run test # Run unit tests (mocked, no network)
pnpm run typecheck # Type checking
pnpm run lint # Lint
pnpm run test:integration # Live tests (real funds; requires .env.test)Live integration tests for the transaction builders submit real transactions and are gated behind .env.test credentials. They are skipped by default. See TESTING.md for setup and safety notes.
License
MIT
