Documentation
Trading
Submit buys and sells against the BondingCurveManager. Derive minOut from an on-chain quote, then apply your own slippage tolerance.
Buy
buypayablebuy(address token, uint256 minTokensOut, address calloutAnnouncer) payable returns (uint256 tokensOut)Send gross ETH as msg.value. Set minTokensOut from quoteBuy with slippage. Pass address(0) for calloutAnnouncer unless attributing a callout. ETH above the fair gross fill is refunded in the same transaction.
Sell
sellnonpayablesell(address token, uint256 tokenAmount, uint256 minEthOut, address calloutAnnouncer) returns (uint256 ethOut)Approve the Manager for tokenAmount first, or use sellWithPermit. Set minEthOut from quoteSell with slippage. Pass address(0) for calloutAnnouncer unless attributing a callout.
sellWithPermitnonpayablesellWithPermit(address token, uint256 tokenAmount, uint256 minEthOut, address calloutAnnouncer, uint256 deadline, uint8 v, bytes32 r, bytes32 s) returns (uint256 ethOut)EIP-2612 permit and sell in one transaction. Economics match sell().
Example
viemts
import {
createPublicClient,
createWalletClient,
http,
parseAbi,
parseAbiItem,
zeroAddress,
type Address,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
/** Arklis BondingCurveManager — Robinhood Chain (4663) */
export const MANAGER = "0x8c0CB9498fE0dCC9F5405e99A17C01579c7C6066" as const;
export const CHAIN_ID = 4663 as const;
export const FEE_BPS_TOTAL = 125n;
export const BPS_DENOM = 10000n;
export const TOKEN_DECIMALS = 6;
const rpcUrl = process.env.RPC_URL!;
export const publicClient = createPublicClient({
chain: {
id: CHAIN_ID,
name: "Robinhood Chain",
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
rpcUrls: { default: { http: [rpcUrl] } },
},
transport: http(rpcUrl),
});
export const managerAbi = parseAbi([
"function quoteBuy(address token, uint256 ethIn) view returns (uint256)",
"function quoteBuyFair(address token, uint256 ethInGross) view returns (uint256 tokensOut, uint256 feeEth, uint256 netEth, uint256 grossEth)",
"function quoteSell(address token, uint256 tokenIn) view returns (uint256)",
"function buy(address token, uint256 minTokensOut, address calloutAnnouncer) payable returns (uint256 tokensOut)",
"function sell(address token, uint256 tokenAmount, uint256 minEthOut, address calloutAnnouncer) returns (uint256 ethOut)",
"function sellWithPermit(address token, uint256 tokenAmount, uint256 minEthOut, address calloutAnnouncer, uint256 deadline, uint8 v, bytes32 r, bytes32 s) returns (uint256 ethOut)",
"event TokenCreated(address indexed token, address indexed creator, string name, string symbol, string uri, uint256 virtualEth, uint256 virtualToken)",
"event Trade(address indexed token, address indexed trader, bool indexed isBuy, uint256 ethAmount, uint256 tokenAmount, uint256 feeEth, uint256 reserveEth, uint256 soldTokens, uint256 spotPriceWei)",
"event CurveCompleted(address indexed token)",
]);
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const wallet = createWalletClient({
account,
chain: publicClient.chain,
transport: http(rpcUrl),
});
/** Pass zeroAddress for calloutAnnouncer unless attributing a callout. */
export async function buyToken(
token: Address,
ethInGrossWei: bigint,
slippageBps = 100n,
) {
const tokensOut = await publicClient.readContract({
address: MANAGER,
abi: managerAbi,
functionName: "quoteBuy",
args: [token, ethInGrossWei],
});
const minTokensOut = (tokensOut * (BPS_DENOM - slippageBps)) / BPS_DENOM;
return wallet.writeContract({
address: MANAGER,
abi: managerAbi,
functionName: "buy",
args: [token, minTokensOut, zeroAddress],
value: ethInGrossWei,
});
}
const erc20Abi = parseAbi([
"function approve(address spender, uint256 amount) returns (bool)",
"function allowance(address owner, address spender) view returns (uint256)",
]);
export async function sellToken(
token: Address,
tokenAmount: bigint,
slippageBps = 100n,
) {
const ethOut = await publicClient.readContract({
address: MANAGER,
abi: managerAbi,
functionName: "quoteSell",
args: [token, tokenAmount],
});
const minEthOut = (ethOut * (BPS_DENOM - slippageBps)) / BPS_DENOM;
const allowance = await publicClient.readContract({
address: token,
abi: erc20Abi,
functionName: "allowance",
args: [account.address, MANAGER],
});
if (allowance < tokenAmount) {
await wallet.writeContract({
address: token,
abi: erc20Abi,
functionName: "approve",
args: [MANAGER, tokenAmount],
});
}
return wallet.writeContract({
address: MANAGER,
abi: managerAbi,
functionName: "sell",
args: [token, tokenAmount, minEthOut, zeroAddress],
});
}Coming soon