For the complete documentation index, see llms.txt. This page is also available as Markdown.

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 txData you broadcast yourself; withdraw / transfer / swap are relayed and return a txHash directly.

  • Solana txData is 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 /withdraw and /transfer the server auto-sets the fee token to tokenAddresses[0]. For /swap it uses tokenAddresses[1] (the output token).

  • Fees: /withdraw, /transfer, and /swap compute relay fees automatically. feeAmount is optional. Call GET /get-fee to display a fee or lock one in.

  • Request signature payload: `${binding}\n${payload}`, where binding is "<METHOD> <routePath>" and payload is JSON.stringify(body) for POST or the raw query string (everything after ?) for GET. The nonce field in the body/query ensures each signature is unique. POST /create-session is the only route that signs the raw body with no binding.

  • For the multi-send flow, see Private Send.

Last updated