OverviewGetting StartedContracts & ABIIntegration GuideHTTP APIERC-8056 Stock StandardLiquidity & ReservesKeeper Node GuideError Codes & DebuggingFAQ & Risks
Integration Guide
Place a limit order or DCA plan from your own dApp in minutes. Approve, calculate slippage, submit order, and watch execution events.
1 · Clients Setup (Viem vs Ethers.js)
Use the client library that best fits your stack. Below is client initialization code for both Viem and Ethers.js:
setup-viem.ts
import { createPublicClient, createWalletClient, custom, http, parseAbi, parseUnits } from "viem";
export const robinhoodChain = {
id: 4663,
name: "Robinhood Chain",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: { default: { http: ["https://rpc.mainnet.chain.robinhood.com"] } },
};
const publicClient = createPublicClient({ chain: robinhoodChain, transport: http() });
const wallet = createWalletClient({ chain: robinhoodChain, transport: custom(window.ethereum) });
const EXECUTOR = "0x…"; // your deployment
const USDG = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168"; // 6 decimals!
const TSLA = "0x322F0929c4625eD5bAd873c95208D54E1c003b2d";
const TSLA_FEED = "0x…"; // from docs.chain.linksetup-ethers.js
import { ethers } from "ethers";
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const EXECUTOR_ADDRESS = "0x...";
const USDG_ADDRESS = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168";
const EXECUTOR_ABI = [
"function placeOrder(address tokenIn, address tokenOut, address priceFeed, uint256 amountIn, uint256 minAmountOut, int256 triggerPrice, uint8 triggerDir, uint8 orderType, uint64 interval, uint32 runs, uint64 expiry) external returns (uint256 id)",
"function cancelOrder(uint256 id) external",
];
const ERC20_ABI = ["function approve(address spender, uint256 value) returns (bool)"];
const executorContract = new ethers.Contract(EXECUTOR_ADDRESS, EXECUTOR_ABI, signer);
const usdgContract = new ethers.Contract(USDG_ADDRESS, ERC20_ABI, signer);2 · Calculating Slippage Out Off-chain
Instead of passing 0 as the slippage protection (which exposes user swaps to frontrunning or high pool price impact), query the Uniswap V2 Router and deduct a toll (e.g. 0.5% or 1% slippage):
calculate-slippage.ts
// Query Uniswap V2 Router to estimate stock output and apply 0.5% slippage tolerance
const routerAbi = parseAbi([
"function getAmountsOut(uint256 amountIn, address[] calldata path) view returns (uint256[] memory amounts)",
]);
const UNISWAP_V2_ROUTER = "0x89e5db8b5aa49aa85ac63f691524311aeb649eba";
const expectedAmounts = await publicClient.readContract({
address: UNISWAP_V2_ROUTER,
abi: routerAbi,
functionName: "getAmountsOut",
args: [parseUnits("1500", 6), [USDG, TSLA]],
});
const expectedTslaOut = expectedAmounts[1];
// Compute minAmountOut (expectedTslaOut * 99.5% = expectedTslaOut * 995 / 1000)
const minAmountOut = (expectedTslaOut * 995n) / 1000n;3 · Approve + Place Order
Call approve first, then submit the order parameter struct to the contract executor:
place-order-viem.ts
const [account] = await wallet.getAddresses();
// 1 — Approve spender (allowance of 1,500 USDG)
await wallet.writeContract({
account, address: USDG, abi: erc20Abi,
functionName: "approve",
args: [EXECUTOR, parseUnits("1500", 6)],
});
// 2 — Place limit buy with dynamic slippage
await wallet.writeContract({
account, address: EXECUTOR, abi: executorAbi,
functionName: "placeOrder",
args: [
USDG, TSLA, TSLA_FEED,
parseUnits("1500", 6), // amountIn
minAmountOut, // slippage guard
262_00000000n, // trigger: ≤ $262 (Chainlink decimals 8)
0, // triggerDir: 0 = BELOW
0, // orderType: 0 = LIMIT
0n, 1, 0n, // interval, runs, expiry
],
});place-order-ethers.js
// 1 — Approve USDG spend (1,500 USDG)
const approveTx = await usdgContract.approve(EXECUTOR_ADDRESS, ethers.utils.parseUnits("1500", 6));
await approveTx.wait();
// 2 — Place limit order
const placeTx = await executorContract.placeOrder(
USDG_ADDRESS, TSLA, TSLA_FEED,
ethers.utils.parseUnits("1500", 6), // amountIn (USDG decimals 6)
minAmountOutEthers, // minAmountOut (slippage-adjusted stock token)
26200000000, // triggerPrice: $262.00 (8 decimals)
0, // triggerDir: BELOW
0, // orderType: LIMIT
0, 1, 0 // interval, runs, expiry
);
await placeTx.wait();USDG is 6 decimals while stock tokens are 18. Ensure you use
parseUnits(val, 6) when setting amountIn for USDG tokens and 18 decimals for stock outputs.4 · Cancelling Orders
Only the order's owner can cancel it. Cancelling releases no funds (as funds stay in the owner's wallet) but immediately invalidates the order's active execution eligibility:
cancel-order.ts
// Cancel order #42 using viem
await wallet.writeContract({
account,
address: EXECUTOR,
abi: executorAbi,
functionName: "cancelOrder",
args: [42n],
});5 · Listen for Execution
watch-fills.ts
// Listen to execution logs
publicClient.watchContractEvent({
address: EXECUTOR,
abi: executorAbi,
eventName: "OrderExecuted",
onLogs: (logs) => {
for (const log of logs) {
console.log(`order #${log.args.id} filled: ${log.args.amountOut} out`);
}
},
});Reading Prices & Balances Correctly
reads.ts
const feedAbi = parseAbi([
"function latestRoundData() view returns (uint80, int256, uint256, uint256, uint80)",
]);
const stockAbi = parseAbi([
"function balanceOf(address) view returns (uint256)",
"function uiMultiplier() view returns (uint256)",
]);
// Chainlink price — 8 decimals, ALREADY multiplier-adjusted (total return)
const [, answer] = await publicClient.readContract({
address: TSLA_FEED, abi: feedAbi, functionName: "latestRoundData",
});
const priceUsd = Number(answer) / 1e8;
// share-equivalent balance (ERC-8056)
const [raw, multiplier] = await Promise.all([
publicClient.readContract({ address: TSLA, abi: stockAbi, functionName: "balanceOf", args: [account] }),
publicClient.readContract({ address: TSLA, abi: stockAbi, functionName: "uiMultiplier" }),
]);
const shares = (raw * multiplier) / 10n ** 18n;The multiplier rule: Chainlink stock feeds on Robinhood Chain are total-return (dividends reinvested via
uiMultiplier). Apply the multiplier to balances (balanceOf × uiMultiplier / 1e18), never to the feed price. Applying it twice overstates values.