Examples
End-to-end examples for the API. Examples are organized by chain family where the request shape differs. The schema and normalization rules are on the Authentication page. This page focuses on runnable code and shows both session modes: normal mode (secp256k1 request signature, no per-transaction wallet signature) and EIP-712 mode (per-transaction typed-data signatures).
Setup
const { ethers } = require("ethers");
const { secp256k1 } = require("@noble/curves/secp256k1");
const { createHash, randomBytes, randomUUID } = require("crypto");
const BASE_URL = "https://api.hinkal.io";
const CHAIN_ID = 10; // Optimism
const USDC = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85";
const USDT = "0x94b008aA00579c1307b0EF2c499aD98a8ce58e58";
const provider = new ethers.JsonRpcProvider("https://mainnet.optimism.io");
const wallet = new ethers.Wallet("0x<private-key>", provider);
function normalizeRoutePath(path) {
const withSlash = path.startsWith("/") ? path : `/${path}`;
return withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
}
function buildActionBinding(method, routePath) {
return `${method.toUpperCase()} ${normalizeRoutePath(routePath)}`;
}
function signPayload(privateKey, payload) {
const msgHash = new Uint8Array(createHash("sha256").update(payload).digest());
return Buffer.from(secp256k1.sign(msgHash, privateKey).toBytes("compact")).toString("hex");
}
async function post(path, body, headers = {}) {
const res = await fetch(`${BASE_URL}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json", ...headers },
body: JSON.stringify(body),
});
const json = await res.json();
if (json.success === false) throw new Error(json.error);
return json;
}
async function get(path, params, headers = {}) {
const res = await fetch(`${BASE_URL}${path}?${params}`, { headers });
const json = await res.json();
if (json.success === false) throw new Error(json.error);
return json;
}Open a session
Every flow starts with creating a session. Generate a secp256k1 key pair, sign the session message with your wallet, and POST to /create-session. All subsequent requests are authenticated with a secp256k1 signature from the same key pair.
useEIP712: false (default) opens a normal mode session - a secp256k1 request signature authenticates all reads and writes.
useEIP712: true opens an EIP-712 mode session - each transaction carries its own typed-data wallet signature; other routes still use the secp256k1 request signature.
Request signing helpers
Reused throughout the examples below.
routePath must match the server's route pattern exactly — e.g. "/withdraw" for POST /withdraw. For the private-send status endpoint it's the literal "/private-send/:orderId", not the concrete order ID.
Read the private balance
Both session modes use a secp256k1 request signature for reads. Auth fields go in the query string.
Typed-data helpers (EVM / TRON - EIP-712 mode only)
The transaction examples below share these. DOMAIN and TYPES mirror the schema; tokenAmounts applies the normalization rules.
Plain-text message helpers (Solana - EIP-712 mode only)
Deposit (public → private)
Withdraw (private → public)
Swap (private → private, different token)
Transfer (private → private, same token)
/transfer works like /withdraw - same struct shape (tokenAmounts + recipient), but the recipient field carries the recipient's recipientInfo handle (from GET /recipient-info) instead of a plain address.
Deposit for other
Deposit into a different user's private balance using their recipientInfo handle.
Use POST /deposit-for-other with the same body as /deposit plus recipientInfo.
Use POST /deposit-solana-for-other. Pass tokenAddresses[] and amounts[] arrays (only the first element of each is used).
Notes
Amounts are always strings in the token's smallest unit (USDC has 6 decimals: 1 USDC =
"1000000").Deposit returns an unsigned
txDatayou broadcast yourself; withdraw / transfer / swap are relayed and return atxHashdirectly.Solana
txDatais a base64-encoded serialized transaction; decode it, add a recent blockhash, sign with your ed25519 keypair, and broadcast via the Solana JSON-RPC.Solana feeToken: for
/withdrawand/transferthe server auto-sets the fee token totokenAddresses[0]. For/swapit usestokenAddresses[1](the output token).Fees:
/withdraw,/transfer, and/swapcompute relay fees automatically.feeAmountis optional. CallGET /get-feeto display a fee or lock one in.Request signature payload:
`${binding}\n${payload}`, wherebindingis"<METHOD> <routePath>"andpayloadisJSON.stringify(body)for POST or the raw query string (everything after?) for GET. Thenoncefield in the body/query ensures each signature is unique.POST /create-sessionis the only route that signs the raw body with no binding.For the multi-send flow, see Private Send.
Last updated