A TypeScript/JavaScript SDK for interacting with Raindex orderbook contracts, providing comprehensive functionality for order management, configuration parsing, and blockchain interactions.
Raindex is an onchain orderbook contract that enables users to deploy complex, perpetual trading algorithms using Rainlang, a domain-specific language interpreted onchain. Learn more about Rainlang in the official documentation.
This SDK provides Rust-powered WebAssembly bindings for orderbook functionality, enabling developers to:
Before using this SDK, ensure you have:
npm install @rainlanguage/orderbook
All of the code snippets below reuse the same fixed-limit dotrain/settings source. The portion before --- represents the shared orderbook and dotrain YAML, and Rainlang lives after the separator.
Heads-up: These values are purely illustrative. Before deploying anything, pull the canonical strategies and settings from rainlanguage/rain.strategies to mirror what our web apps run in production.
const FIXED_LIMIT_SOURCE = `
version: 4
networks:
base:
rpcs:
- https://base-rpc.publicnode.com
chain-id: 8453
network-id: 8453
currency: ETH
metaboards:
base: https://api.goldsky.com/api/public/project_clv14x04y9kzi01saerx7bxpg/subgraphs/metadata-base/2025-07-06-594f/gn
subgraphs:
base: https://example.com/subgraph
local-db-remotes:
raindex: https://example.com/subgraph
orderbooks:
base:
network: base
address: 0x52CEB8eBEf648744fFDDE89F7Bc9C3aC35944775
deployment-block: 36667253
subgraph: base
local-db-remote: raindex
tokens:
usdc:
network: base
address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
decimals: 6
label: USD Coin
symbol: USDC
weth:
network: base
address: 0x4200000000000000000000000000000000000006
decimals: 18
label: Wrapped Ether
symbol: WETH
deployers:
base:
network: base
address: 0x6557778Db274f04B9E9f39F8Ff2D621c2036e978
orders:
fixed-limit:
orderbook: base
inputs:
- token: usdc
vault-id: 1
outputs:
- token: weth
vault-id: 1
deployer: base
scenarios:
base:
orderbook: base
runs: 1
bindings:
raindex-subparser: 0x22839F16281E67E5Fd395fAFd1571e820CbD46cB
fixed-io-output-token: 0x4200000000000000000000000000000000000006
deployments:
base:
order: fixed-limit
scenario: base
using-tokens-from:
- https://tokens.coingecko.com/base/all.json
gui:
name: Fixed limit
description: Deploy a USDC -> WETH limit order on Base.
short-description: Deploy a USDC -> WETH limit order on Base.
deployments:
base:
name: Base
description: Deploy a limit order on Base.
deposits:
- token: usdc
presets:
- "0"
- "100"
- "1000"
fields:
- binding: fixed-io
name: USDC per WETH
description: Fixed exchange rate (USDC received per 1 WETH sold)
presets:
- value: "1800"
- value: "1850"
- value: "1900"
- binding: amount-per-trade
name: Amount per trade
description: USDC spent per fill
presets:
- value: "100"
- value: "250"
- value: "500"
select-tokens:
- key: input-token
name: Token to Buy
description: Select the token you want to purchase
- key: output-token
name: Token to Sell
description: Select the token you want to sell
---
#raindex-subparser !The subparser to use.
#fixed-io !The io ratio for the limit order.
#fixed-io-output-token !The output token that the fixed io is for. If this doesn't match the runtime output then the fixed-io will be inverted.
#calculate-io
using-words-from raindex-subparser
max-output: max-positive-value(),
io: if(
equal-to(
output-token()
fixed-io-output-token
)
fixed-io
inv(fixed-io)
);
#handle-io
:;
#handle-add-order
:;
`;
const ORDERBOOK_SETTINGS = FIXED_LIMIT_SOURCE.split('---')[0];
This first snippet does three things: (1) load one or more settings YAML strings (these describe networks, accounts, and subgraph URLs), (2) feed those sources into RaindexClient.new so the WASM layer can parse and validate them, and (3) unwrap the resulting WasmEncodedResult so downstream samples can call the client with standard JS error handling expectations.
import { RaindexClient } from '@rainlanguage/orderbook';
const clientResult = RaindexClient.new([ORDERBOOK_SETTINGS]);
if (clientResult.error) throw new Error(clientResult.error.readableMsg);
const client = clientResult.value;
Pass true as the second argument to RaindexClient.new when you want strict schema validation.
Here we scope the query by chain IDs and typical filters (owner, token, activity flag), ask the client to hydrate matching orders, and then walk the richer helpers on a single RaindexOrder—vault listings, trades, quotes, and detail lookups—to show how pagination + follow-up queries hang together.
import type { ChainIds, GetOrdersFilters } from '@rainlanguage/orderbook';
const chainIds: ChainIds = [8453];
const filters: GetOrdersFilters = {
owners: ['0x1234...'],
active: true,
tokens: ['0xTokenAddress']
};
const ordersResult = await client.getOrders(chainIds, filters, 1);
if (ordersResult.error) throw new Error(ordersResult.error.readableMsg);
const orders = ordersResult.value; // RaindexOrder[]
const first = orders[0];
const vaultList = first.vaultsList; // RaindexVaultsList helper
const rawVaults = vaultList.items; // RaindexVault[]
const tradesResult = await first.getTradesList();
if (tradesResult.error) throw new Error(tradesResult.error.readableMsg);
const tradeDetailResult = await first.getTradeDetail('0xTradeId');
if (tradeDetailResult.error) throw new Error(tradeDetailResult.error.readableMsg);
const quotesResult = await first.getQuotes();
if (quotesResult.error) throw new Error(quotesResult.error.readableMsg);
Additional helpers worth wiring up:
client.getOrderByHash(chainId, orderbookAddress, orderHash) – fetch a single order with full vault metadata.client.getAddOrdersForTransaction(...) / client.getRemoveOrdersForTransaction(...) – diff deployments and removals by transaction hash.client.getTransaction(orderbookAddress, txHash) – inspect who sent a transaction, the block number, and timestamp.After you submit an execute/addOrders transaction you immediately have the transaction hash, but the subgraph still needs a few blocks to index the resulting order. Rather than re-querying every order and diffing manually, poll client.getAddOrdersForTransaction with that hash until it returns at least one RaindexOrder.
import type { RaindexClient } from '@rainlanguage/orderbook';
const POLL_INTERVAL_MS = 5_000;
const MAX_ATTEMPTS = 12;
async function waitForOrderFromTx(
client: RaindexClient,
{
chainId,
orderbookAddress,
txHash
}: {
chainId: number;
orderbookAddress: string;
txHash: string;
}
) {
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
const result = await client.getAddOrdersForTransaction(chainId, orderbookAddress, txHash);
if (result.error) throw new Error(result.error.readableMsg);
if (result.value.length) {
return result.value[0]; // RaindexOrder; multiple orders are possible for batched deployments
}
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
}
throw new Error('Order not indexed yet; increase MAX_ATTEMPTS or interval if needed');
}
const txReceipt = await executeOrder(...);
const raindexOrder = await waitForOrderFromTx(client, {
chainId: 8453,
orderbookAddress: '0x52CEB8eBEf648744fFDDE89F7Bc9C3aC35944775',
txHash: txReceipt.transactionHash
});
const orderResult = await client.getOrderByHash(
8453, // Base
'0x52CEB8eBEf648744fFDDE89F7Bc9C3aC35944775',
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12'
);
if (orderResult.error) throw new Error(orderResult.error.readableMsg);
const order = orderResult.value; // RaindexOrder
const vaultsList = order.vaultsList;
const removeCalldataResult = await order.getRemoveCalldata();
if (removeCalldataResult.error) throw new Error(removeCalldataResult.error.readableMsg);
Vault workflows usually require combining filters, inspecting the returned RaindexVaultsList, and then producing calldata or math-heavy amounts. This example chains those steps: fetch vaults, narrow the list to withdrawable entries, pull history, parse human inputs with Float, and finally build deposit/withdraw/approval payloads while checking allowances.
import { Float, type GetVaultsFilters } from '@rainlanguage/orderbook';
const vaultFilters: GetVaultsFilters = {
owners: ['0x1234...'],
hideZeroBalance: true
};
const vaultsResult = await client.getVaults([8453], vaultFilters, 1);
if (vaultsResult.error) throw new Error(vaultsResult.error.readableMsg);
const vaultsList = vaultsResult.value; // RaindexVaultsList
const withdrawableResult = vaultsList.getWithdrawableVaults();
if (withdrawableResult.error) throw new Error(withdrawableResult.error.readableMsg);
const withdrawableVaults = withdrawableResult.value;
const vault = withdrawableVaults[0];
const historyResult = await vault.getBalanceChanges();
if (historyResult.error) throw new Error(historyResult.error.readableMsg);
const depositAmount = Float.parse('10.5');
if (depositAmount.error) throw new Error(depositAmount.error.readableMsg);
const depositCalldataResult = await vault.getDepositCalldata(depositAmount.value);
if (depositCalldataResult.error) throw new Error(depositCalldataResult.error.readableMsg);
const withdrawAmount = Float.parse('2');
if (withdrawAmount.error) throw new Error(withdrawAmount.error.readableMsg);
const withdrawCalldataResult = await vault.getWithdrawCalldata(withdrawAmount.value);
if (withdrawCalldataResult.error) throw new Error(withdrawCalldataResult.error.readableMsg);
const approvalResult = await vault.getApprovalCalldata(depositAmount.value);
if (approvalResult.error) throw new Error(approvalResult.error.readableMsg);
const allowanceResult = await vault.getAllowance();
if (allowanceResult.error) throw new Error(allowanceResult.error.readableMsg);
const allowance = allowanceResult.value;
RaindexVaultsList also exposes getWithdrawCalldata() (builds a multicall to empty every vault with a balance), pickByIds([...]), and concat(otherList) if you need to restructure vault groups before submitting a transaction.
const vaultResult = await client.getVault(
8453,
'0x52CEB8eBEf648744fFDDE89F7Bc9C3aC35944775',
'0x01'
);
if (vaultResult.error) throw new Error(vaultResult.error.readableMsg);
const vault = vaultResult.value; // RaindexVault
const balanceChangesResult = await vault.getBalanceChanges();
if (balanceChangesResult.error) throw new Error(balanceChangesResult.error.readableMsg);
Once you have hydrated orders, you typically need deterministic hashes plus calldata builders. The snippet below hashes an order struct, generates take-orders calldata, asks an order for its removal calldata, and fetches quotes—mirroring the usual “inspect -> prepare transaction -> submit” flow.
import { getOrderHash, getTakeOrders3Calldata } from '@rainlanguage/orderbook';
const orderHashResult = getOrderHash(orderV4Struct);
if (orderHashResult.error) throw new Error(orderHashResult.error.readableMsg);
const takeOrdersResult = getTakeOrders3Calldata(takeOrdersConfig);
if (takeOrdersResult.error) throw new Error(takeOrdersResult.error.readableMsg);
const takeOrdersCalldata = takeOrdersResult.value; // hex string ready for the contract
const removeCalldataResult = await first.getRemoveCalldata();
if (removeCalldataResult.error) throw new Error(removeCalldataResult.error.readableMsg);
const quotesResult = await first.getQuotes();
if (quotesResult.error) throw new Error(quotesResult.error.readableMsg);
Every RaindexOrder exposes vaultsList, inputsList, outputsList, and inputsOutputsList, so you can quickly scope which vault IDs map to which IO leg before building calldata.
DotrainRegistryIf you maintain a hosted registry, instantiate the helper, inspect what it exposes, and pull down any dotrain/GUI definitions you need:
import { DotrainRegistry } from '@rainlanguage/orderbook';
const registryResult = await DotrainRegistry.new('https://example.com/registry.txt');
if (registryResult.error) throw new Error(registryResult.error.readableMsg);
const registry = registryResult.value;
const orderMenuResult = registry.getAllOrderDetails();
if (orderMenuResult.error) throw new Error(orderMenuResult.error.readableMsg);
const orderMenu = orderMenuResult.value; // Map<orderKey, { name, description, short_description }>
const deploymentsResult = registry.getDeploymentDetails('fixed-limit');
if (deploymentsResult.error) throw new Error(deploymentsResult.error.readableMsg);
const deployments = deploymentsResult.value;
Registry manifests follow the format:
https://example.com/shared-settings.yaml
fixed-limit https://example.com/orders/fixed-limit.rain
dca https://example.com/orders/dca.rain
The SDK merges the shared settings YAML with each order’s .rain content before you ever build a GUI.
Any dotrain file that includes a gui: block plus the usual settings YAML is enough to drive DotrainOrderGui. The FIXED_LIMIT_SOURCE constant declared earlier already includes the required networks/tokens/deployers plus a full gui definition, so you can reference it directly (or trim it to your own bindings) instead of copying pieces of settings.yaml inline in this guide. Always cross-check the source you feed in with the latest definitions in rainlanguage/rain.strategies; that repository tracks the real configurations our UI ships with.
With that single source string (read from disk or built dynamically) you can drive the full GUI workflow:
import { DotrainOrderGui } from '@rainlanguage/orderbook';
const dotrainWithGui = FIXED_LIMIT_SOURCE;
const deploymentsResult = await DotrainOrderGui.getDeploymentKeys(dotrainWithGui);
if (deploymentsResult.error) throw new Error(deploymentsResult.error.readableMsg);
const [firstDeployment] = deploymentsResult.value;
const guiResult = await DotrainOrderGui.newWithDeployment(dotrainWithGui, firstDeployment);
if (guiResult.error) throw new Error(guiResult.error.readableMsg);
const gui = guiResult.value;
const configResult = gui.getAllGuiConfig();
if (configResult.error) throw new Error(configResult.error.readableMsg);
const config = configResult.value;
const selectTokensResult = gui.getSelectTokens();
if (selectTokensResult.error) throw new Error(selectTokensResult.error.readableMsg);
const selectTokens = selectTokensResult.value;
const depositsResult = gui.getDeposits();
if (depositsResult.error) throw new Error(depositsResult.error.readableMsg);
const deposits = depositsResult.value;
await gui.setSelectToken('input-token', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'); // USDC
await gui.setSelectToken('output-token', '0x4200000000000000000000000000000000000006'); // WETH
const fieldResult = gui.setFieldValue('fixed-io', '1850');
if (fieldResult.error) throw new Error(fieldResult.error.readableMsg);
const amountResult = gui.setFieldValue('amount-per-trade', '250');
if (amountResult.error) throw new Error(amountResult.error.readableMsg);
await gui.setDeposit('usdc', '5000');
const vaultIdResult = gui.setVaultId('input', 'usdc', '42');
if (vaultIdResult.error) throw new Error(vaultIdResult.error.readableMsg);
const allowancesResult = await gui.checkAllowances('0xOwner');
if (allowancesResult.error) throw new Error(allowancesResult.error.readableMsg);
const allowances = allowancesResult.value;
const approvalCalldatasResult = await gui.generateApprovalCalldatas('0xOwner');
if (approvalCalldatasResult.error) throw new Error(approvalCalldatasResult.error.readableMsg);
const depositCalldatasResult = await gui.generateDepositCalldatas();
if (depositCalldatasResult.error) throw new Error(depositCalldatasResult.error.readableMsg);
const deploymentArgsResult = await gui.getDeploymentTransactionArgs('0xOwner');
if (deploymentArgsResult.error) throw new Error(deploymentArgsResult.error.readableMsg);
const { approvals, deploymentCalldata, orderbookAddress, chainId } = deploymentArgsResult.value;
const rainlangResult = await gui.getComposedRainlang();
if (rainlangResult.error) throw new Error(rainlangResult.error.readableMsg);
const composedRainlang = rainlangResult.value;
const serializedStateResult = gui.serializeState();
if (!serializedStateResult.error) {
localStorage.setItem('fixed-limit-state', serializedStateResult.value);
}
Serialize the GUI state and later revive it with DotrainOrderGui.newFromState(dotrainText, serializedState, callback) if you want to skip re-entering form choices.
gui.getDeploymentTransactionArgs(owner) returns a DeploymentTransactionArgs struct with:
approvals: ExtendedApprovalCalldata[] (each item contains token, calldata, and the token symbol for UX)deploymentCalldata: Hex – a multicall that performs deposits (if required) and adds the order in one transactionorderbookAddress: string – destination for the multicallchainId: number – network you must connect your wallet toA typical deployment flow is:
getDeploymentTransactionArgsapprovals is empty)client.getAddOrdersForTransaction (the helper shown earlier) with the deployment hash until the subgraph surfaces your RaindexOrderimport type { RaindexClient } from '@rainlanguage/orderbook';
const deploymentArgsResult = await gui.getDeploymentTransactionArgs(owner);
if (deploymentArgsResult.error) throw new Error(deploymentArgsResult.error.readableMsg);
const { approvals, deploymentCalldata, orderbookAddress, chainId } = deploymentArgsResult.value;
// Assume sendTransaction({ to, data }) and waitForReceipt(hash) come from your wallet stack.
for (const approval of approvals) {
const approvalHash = await sendTransaction({ to: approval.token, data: approval.calldata });
await waitForReceipt(approvalHash);
}
const deploymentHash = await sendTransaction({ to: orderbookAddress, data: deploymentCalldata });
await waitForReceipt(deploymentHash);
const raindexOrder = await waitForOrderFromTx(client as RaindexClient, {
chainId,
orderbookAddress,
txHash: deploymentHash
});
After you have a local GUI-aware dotrain source, you can also fetch equivalent sources from a registry and run the same flow:
import { DotrainRegistry } from '@rainlanguage/orderbook';
const registryResult = await DotrainRegistry.new('https://example.com/registry.txt');
if (registryResult.error) throw new Error(registryResult.error.readableMsg);
const registry = registryResult.value;
const guiSourceResult = await registry.getGui('fixed-limit', 'base');
if (guiSourceResult.error) throw new Error(guiSourceResult.error.readableMsg);
const guiFromRegistry = guiSourceResult.value;
// guiFromRegistry is already a DotrainOrderGui instance, so you can reuse
// the same GUI helper steps shown above (select tokens, deposits, calldata, etc.).
If you just need Rainlang composition (no GUI state), read the dotrain text plus shared settings yourself, instantiate a DotrainOrder, and then ask it to compose scenario/deployment/post-task Rainlang. The example below reuses FIXED_LIMIT_SOURCE, but you can replace it with the contents of any .rain file.
import { DotrainOrder } from '@rainlanguage/orderbook';
const dotrainResult = await DotrainOrder.create(FIXED_LIMIT_SOURCE, [ORDERBOOK_SETTINGS]);
if (dotrainResult.error) throw new Error(dotrainResult.error.readableMsg);
const dotrain = dotrainResult.value;
const scenarioResult = await dotrain.composeScenarioToRainlang('backtest');
if (!scenarioResult.error) console.log(scenarioResult.value);
const deploymentResult = await dotrain.composeDeploymentToRainlang('flare-prod');
if (!deploymentResult.error) console.log(deploymentResult.value);
const postTaskResult = await dotrain.composeScenarioToPostTaskRainlang('flare-prod');
if (!postTaskResult.error) console.log(postTaskResult.value);
getOrderHash, keccak256, keccak256HexString – deterministic hashing helpers for Rain orders or arbitrary payloads.Float – arbitrary-precision arithmetic with parsing, formatting, comparisons, math ops, fixed-decimal conversions, and helpers like Float.zero() or .formatWithRange(...).RaindexClient.getAllAccounts() / getAllVaultTokens() – introspect accounts and ERC20 metadata defined in your YAML or discovered via subgraphs.clearTables, getSyncStatus, RaindexClient.syncLocalDatabase, RaindexClient.setDbCallback – plug in a persistent cache for offline apps.RaindexVaultsList.getWithdrawCalldata() – multicall builder that withdraws every vault with a balance.RaindexOrder.convertToSgOrder() – convert WASM order representations back into the raw subgraph schema when you need to interop with other tooling.Every exported function returns a WasmEncodedResult<T>:
type WasmEncodedResult<T> =
| { value: T; error: undefined }
| { value: undefined; error: { msg: string; readableMsg: string } };
const result = await client.getVault(14, '0xOrderbook', '0x01');
if (result.error) {
console.error('Vault lookup failed:', result.error.readableMsg);
return;
}
console.log(result.value);
This SDK is part of the Rain Language ecosystem. For contributions and issues, please visit the GitHub repository.