TypeScript SDK
@alula/client-sdk is the high-level TypeScript client for the Alula protocol. It wraps the generated Soroban contract bindings (@alula/market-sdk and the Aqua/Soroswap swap providers) behind an ergonomic, service-oriented API, handling bigint conversion, result unwrapping, decimal scaling, and transaction assembly for you. It is the same integration layer the Alula web app is built on.
If you are integrating from Rust or working directly against the contract ABI, see the Smart Contract API and the Developer Quickstart instead.
Installation
pnpm add @alula/client-sdk @stellar/stellar-sdk@stellar/stellar-sdk is a peer dependency — the SDK returns and consumes its transaction and account types.
Creating a client
AlulaClient is created through an async factory. Construction reads the market's oracle decimals from the contract, so you must await it.
import { AlulaClient } from '@alula/client-sdk'
const client = await AlulaClient.create({
publicKey: 'GABC...', // connected wallet address (optional)
marketContractId: 'CBP7...', // market contract for this network
opts: {
rpc: 'public', // 'devnet' | 'testnet' | 'public'
// horizonRpcUrl / sorobanRpcUrl are optional overrides
},
})A convenience factory is also available when you only have an address:
const client = await AlulaClient.fromAddress(address, marketContractId, { rpc: 'public' })INFO
publicKey is optional — read-only calls (querying pools, market state, obligations) work without a connected wallet. A wallet is only required to sign and send transactions.
Services
Every capability hangs off a service on the client:
| Service | Access | Purpose |
|---|---|---|
| Market | client.market | Read market and pool state, oracle prices, withdrawal simulation — see Reading Market State |
| Obligation | client.obligation | Read a user's obligation (positions) — see Reading Market State |
| Lending | client.lending | Deposit, withdraw, add/remove collateral — see Supply & Borrow |
| Borrowing | client.borrowing | Borrow and repay — see Supply & Borrow |
| Multiply | client.multiply | Open and close leveraged positions — see Multiply |
| Swap | client.swap | Quote routes and execute swaps — see Swap |
| Wallet | client.wallet | Balances and trustlines — see Wallet & Trustlines |
Core patterns
These conventions apply across every service. The individual pages assume you have read this section.
Build vs. execute
Every state-changing operation comes in two forms:
buildXTx(...)returns an unsignedAssembledTransactionyou can inspect, simulate, or estimate fees on before committing.- The convenience method (
deposit,borrow,openPosition, …) builds, signs, and submits in one call.
// Inspect first
const tx = await client.lending.buildDepositTx(user, poolAddress, '100', assetDecimals)
const feeXlm = client.lending.getTransactionFee(tx, assetDecimals)
// Or do everything at once
await client.lending.deposit(user, poolAddress, 100, assetDecimals, kit)Wallet kit injection
The SDK does not depend on any specific wallet library. Signing methods accept a kit argument — any object exposing a signTransaction(xdr, opts) method that returns { signedTxXdr } satisfies the required SigningKit shape. This works with Stellar Wallets Kit, custom signers, or test doubles.
interface SigningKit {
signTransaction: (
xdr: string,
opts?: Record<string, unknown>,
) => Promise<{ signedTxXdr: string }>
}Obligations
Every user-scoped operation takes an ObligationKey, not a plain address:
import type { ObligationKey } from '@alula/market-sdk'
const user: ObligationKey = { user: 'GABC...', seed: undefined } // standard obligationThe seed distinguishes obligation types (standard, earn, multiply). See Concepts and the User Operations reference for details.
Amounts and decimals
You pass human-readable amounts (strings or numbers) plus the token's decimal precision; the SDK scales them to on-chain i128 values internally. Utility helpers are exported for the reverse direction:
import { amountToBigInt, bigintToNumber } from '@alula/client-sdk'
amountToBigInt('100', 7) // 1000000000n
bigintToNumber(1000000000n, 7) // '100'Maxing out with withBuffer
Withdraw, borrow, and remove-collateral accept a withBuffer flag. When true, the SDK submits MAX_I128 so the contract releases the maximum currently allowed (capped by health and liquidity), avoiding dust left behind by rounding between quote and execution.
// Withdraw everything available
await client.lending.withdraw(user, poolAddress, 0, assetDecimals, kit, /* withBuffer */ true)Preview objects
Multiply and swap operations return rich preview objects describing fees, slippage, minimum outputs, and price impact before you commit. Render these to the user, then pass the same parameters to the execute call. See Multiply and Swap.
Debug logging
Signing methods take an options argument that defaults to { debug: true }, which logs the transaction, signed XDR, and result to the console. Pass { debug: false } to silence this in production.
await client.lending.deposit(user, poolAddress, 100, assetDecimals, kit, { debug: false })Error handling
Contract failures surface as errors carrying a Soroban contract error code. Use getErrorMessage to turn them into human-readable strings mapped from the protocol's error codes:
import { getErrorMessage } from '@alula/client-sdk'
try {
await client.borrowing.borrow(user, poolAddress, 500, assetDecimals, kit, false)
} catch (err) {
console.error(getErrorMessage(err)) // e.g. "Health factor is below the required threshold..."
}