> For the complete documentation index, see [llms.txt](https://hinkal-team.gitbook.io/hinkal/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hinkal-team.gitbook.io/hinkal/hinkal-api/description/examples.md).

# 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](/hinkal/hinkal-api/description/authentication.md) 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

{% tabs %}
{% tab title="EVM" %}

```js
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;
}
```

{% endtab %}

{% tab title="Tron" %}

```js
const { secp256k1 } = require("@noble/curves/secp256k1");
const { createHash, randomBytes, randomUUID } = require("crypto");
const TronWeb = require("tronweb");

const BASE_URL = "https://api.hinkal.io";
const TRON_CHAIN_ID = 728126428; // Tron mainnet
const TRON_USDT = "0xECa9bC828A3005B9a3b909f2cc5c2a54794DE05F";

const tronWeb = new TronWeb({ fullHost: "https://api.trongrid.io" });

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;
}
```

{% endtab %}

{% tab title="Solana" %}

```js
import nacl from "tweetnacl";
import bs58 from "bs58";
import { secp256k1 } from "@noble/curves/secp256k1";
import { createHash, randomBytes, randomUUID } from "crypto";

const BASE_URL = "https://api.hinkal.io";
const SOLANA_CHAIN_ID = 501;
const SOLANA_USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const SOLANA_USDT = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB";

// keypair: { publicKey: Uint8Array, secretKey: Uint8Array }
const address = bs58.encode(keypair.publicKey);

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;
}
```

{% endtab %}
{% endtabs %}

## 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.

{% tabs %}
{% tab title="EVM (normal mode)" %}

```js
function buildSessionMessage(sessionId, clientPublicKey, useEIP712) {
  const lines = [
    "Authorize Hinkal session",
    `Session ID: ${sessionId}`,
    `Public Key: ${clientPublicKey}`,
  ];
  if (!useEIP712) lines.push("This signature can also be used to submit transactions.");
  return lines.join("\n");
}

async function createSession(useEIP712 = false, expiresAt) {
  // 1. Generate secp256k1 key pair
  const privateKey = randomBytes(32);
  const clientPublicKey = Buffer.from(secp256k1.getPublicKey(privateKey, true)).toString("hex");

  // 2. Sign session message with wallet
  const sessionId = randomUUID();
  const message = buildSessionMessage(sessionId, clientPublicKey, useEIP712);
  const signature = await wallet.signMessage(message); // EIP-191

  // 3. Build request body and request-signature header
  const nonce = randomUUID();
  const body = {
    signature,
    address: wallet.address,
    sessionId,
    clientPublicKey,
    nonce,
    useEIP712,
    ...(expiresAt ? { expiresAt } : {}),
  };

  await post("/create-session", body, {
    "x-hinkal-request-signature": signPayload(privateKey, JSON.stringify(body)),
  });
  // Response: { success, expiresAt }
  return { sessionId, privateKey, useEIP712 };
}
```

{% endtab %}

{% tab title="Tron (normal mode)" %}

```js
function buildSessionMessage(sessionId, clientPublicKey, useEIP712) {
  const lines = [
    "Authorize Hinkal session",
    `Session ID: ${sessionId}`,
    `Public Key: ${clientPublicKey}`,
  ];
  if (!useEIP712) lines.push("This signature can also be used to submit transactions.");
  return lines.join("\n");
}

async function createSession(tronAddress, tronPrivateKey, useEIP712 = false) {
  const privateKey = randomBytes(32);
  const clientPublicKey = Buffer.from(secp256k1.getPublicKey(privateKey, true)).toString("hex");

  const sessionId = randomUUID();
  const message = buildSessionMessage(sessionId, clientPublicKey, useEIP712);
  const signature = tronWeb.trx.signMessageV2(message, tronPrivateKey);

  const nonce = randomUUID();
  const body = {
    signature,
    address: tronAddress,
    sessionId,
    clientPublicKey,
    nonce,
    useEIP712,
  };

  await post("/create-session", body, {
    "x-hinkal-request-signature": signPayload(privateKey, JSON.stringify(body)),
  });
  return { sessionId, privateKey, useEIP712 };
}
```

{% endtab %}

{% tab title="Solana (normal mode)" %}

```js
function buildSessionMessage(sessionId, clientPublicKey, useEIP712) {
  const lines = [
    "Authorize Hinkal session",
    `Session ID: ${sessionId}`,
    `Public Key: ${clientPublicKey}`,
  ];
  if (!useEIP712) lines.push("This signature can also be used to submit transactions.");
  return lines.join("\n");
}

async function createSession(useEIP712 = false) {
  const privateKey = randomBytes(32);
  const clientPublicKey = Buffer.from(secp256k1.getPublicKey(privateKey, true)).toString("hex");

  const sessionId = randomUUID();
  const msg = buildSessionMessage(sessionId, clientPublicKey, useEIP712);
  const signature = Buffer.from(
    nacl.sign.detached(Buffer.from(msg, "utf8"), keypair.secretKey)
  ).toString("hex");

  const nonce = randomUUID();
  const body = {
    signature,
    address,
    sessionId,
    clientPublicKey,
    nonce,
    useEIP712,
  };

  await post("/create-session", body, {
    "x-hinkal-request-signature": signPayload(privateKey, JSON.stringify(body)),
  });
  return { sessionId, privateKey, useEIP712 };
}
```

{% endtab %}
{% endtabs %}

## Request signing helpers

Reused throughout the examples below.

```js
// Build query params + request-signature header for GET requests
function sessionGetParams(session, chainId) {
  return new URLSearchParams({
    sessionId: session.sessionId,
    nonce: randomUUID(),
    chainId: chainId.toString(),
    timestamp: Date.now().toString(),
  });
}

function requestSignatureGetHeader(session, routePath, queryString) {
  const binding = buildActionBinding("GET", routePath);
  return { "x-hinkal-request-signature": signPayload(session.privateKey, `${binding}\n${queryString}`) };
}

// Build body params + request-signature header for POST requests (normal mode)
function sessionBodyParams(session, chainId) {
  return {
    sessionId: session.sessionId,
    nonce: randomUUID(),
    chainId,
    timestamp: Date.now(),
  };
}

function requestSignaturePostHeader(session, routePath, body) {
  const binding = buildActionBinding("POST", routePath);
  return { "x-hinkal-request-signature": signPayload(session.privateKey, `${binding}\n${JSON.stringify(body)}`) };
}
```

`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 shielded balance

Both session modes use a secp256k1 request signature for reads. Auth fields go in the query string.

```js
const session = await createSession(); // normal mode

const params = sessionGetParams(session, CHAIN_ID);
const { balances } = await get("/balance", params, requestSignatureGetHeader(session, "/balance", params.toString()));
console.log(balances); // [{ chainId, tokenAddress, balance }, ...]
```

## Typed-data helpers (EVM / Tron — EIP-712 mode only)

The transaction examples below share these. `DOMAIN` and `TYPES` mirror the [schema](/hinkal/hinkal-api/description/authentication.md#evm--tron--eip-712-typed-data-v4); `tokenAmounts` applies the [normalization rules](/hinkal/hinkal-api/description/authentication.md#normalization-rules).

```js
const DOMAIN = (chainId) => ({ name: "Hinkal Enclave", chainId });

const TokenAmount = [
  { name: "token", type: "address" },
  { name: "amount", type: "int256" },
];

// checksum each address, sort by token ascending
function tokenAmounts(tokenAddresses, amounts) {
  return tokenAddresses
    .map((token, i) => ({ token: ethers.getAddress(token), amount: BigInt(amounts[i]) }))
    .sort((a, b) => a.token.localeCompare(b.token));
}

// Build dynamic fee fields — only include fields actually present in the request.
// The EIP-712 struct type must exactly match what the server reconstructs from the body.
function buildFeeFields(feeToken, feeAmount) {
  const typeFields = [];
  const value = {};

  if (feeToken) {
    typeFields.push({ name: "feeToken", type: "address" });
    value.feeToken = ethers.getAddress(feeToken);
  }
  if (feeAmount !== undefined) {
    typeFields.push({ name: "feeAmount", type: "uint256" });
    value.feeAmount = BigInt(feeAmount);
  }

  return { typeFields, value };
}
```

## Plain-text message helpers (Solana — EIP-712 mode only)

```js
function sortedTokenPairs(tokenAddresses, amounts) {
  return tokenAddresses
    .map((addr, i) => ({ tokenAddress: addr, amount: amounts[i] }))
    .sort((a, b) => a.tokenAddress.localeCompare(b.tokenAddress));
}

function renderTokenAmounts(pairs) {
  return pairs
    .map(({ tokenAddress, amount }, i) => `  ${i}:\n    Token: ${tokenAddress}\n    Amount: ${amount}`)
    .join("\n");
}

function buildHeader(primaryType, sessionId, nonce, chainId) {
  return `Hinkal Enclave\n\nPrimary Type: ${primaryType}\nSession ID: ${sessionId}\nNonce: ${nonce}\nChain ID: ${chainId}`;
}

function renderFeeFields(feeAmount) {
  return feeAmount !== undefined ? `\nFee Amount: ${feeAmount}` : "";
}

function buildTokenAmountsMessage(primaryType, params) {
  const pairs = sortedTokenPairs(params.tokenAddresses, params.amounts);
  return `${buildHeader(primaryType, params.sessionId, params.nonce, params.chainId)}\nToken Amounts:\n${renderTokenAmounts(pairs)}`;
}

function buildSolanaWithdrawMessage(params) {
  return `${buildTokenAmountsMessage("Withdraw", params)}\nRecipient: ${params.recipientAddress}${renderFeeFields(params.feeAmount)}`;
}

function buildSolanaTransferMessage(params) {
  return `${buildTokenAmountsMessage("Transfer", params)}\nRecipient: ${params.recipientAddress}${renderFeeFields(params.feeAmount)}`;
}

function buildSolanaSwapMessage(params) {
  return `${buildTokenAmountsMessage("Swap", params)}\nExternal Action ID: ${params.externalActionId}\nSwap Data: ${params.swapData}${renderFeeFields(params.feeAmount)}`;
}

function buildSolanaDepositMessage(params) {
  return buildTokenAmountsMessage("Deposit", params);
}

function signSolana(message) {
  const bytes = Buffer.from(message, "utf8");
  return Buffer.from(nacl.sign.detached(bytes, keypair.secretKey)).toString("hex");
}
```

## Deposit (public → shielded)

{% tabs %}
{% tab title="EVM — normal mode" %}

```js
const session = await createSession(); // useEIP712: false

const bodyParams = sessionBodyParams(session, CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [USDC],
  amounts: ["1000000"], // 1 USDC
};

const { txData } = await post("/deposit", body, requestSignaturePostHeader(session, "/deposit", body));

const sent = await wallet.sendTransaction(txData);
await sent.wait();
console.log("deposit tx:", sent.hash);
```

{% endtab %}

{% tab title="EVM — EIP-712 mode (per-tx signature)" %}

```js
const session = await createSession(true); // useEIP712: true

const Deposit = [
  { name: "nonce", type: "string" },
  { name: "sessionId", type: "string" },
  { name: "chainId", type: "uint256" },
  { name: "tokenAmounts", type: "TokenAmount[]" },
];

const nonce = randomUUID();
const signature = await wallet.signTypedData(
  DOMAIN(CHAIN_ID),
  { TokenAmount, Deposit },
  {
    nonce,
    sessionId: session.sessionId,
    chainId: BigInt(CHAIN_ID),
    tokenAmounts: tokenAmounts([USDC], ["1000000"]),
  },
);

const { txData } = await post("/deposit", {
  sessionId: session.sessionId,
  nonce,
  signature,
  chainId: CHAIN_ID,
  tokenAddresses: [USDC],
  amounts: ["1000000"],
});

const sent = await wallet.sendTransaction(txData);
await sent.wait();
```

{% endtab %}

{% tab title="Tron" %}

```js
// Tron uses EIP-712 typed-data. Use an ethers Wallet from the Tron private key for signing.
const session = await createSession(tronAddress, tronPrivateKey, true); // useEIP712: true
const ethersWallet = new ethers.Wallet(`0x${tronPrivateKey}`);

const Deposit = [
  { name: "nonce", type: "string" },
  { name: "sessionId", type: "string" },
  { name: "chainId", type: "uint256" },
  { name: "tokenAmounts", type: "TokenAmount[]" },
];

const nonce = randomUUID();
const signature = await ethersWallet.signTypedData(
  DOMAIN(TRON_CHAIN_ID),
  { TokenAmount, Deposit },
  {
    nonce,
    sessionId: session.sessionId,
    chainId: BigInt(TRON_CHAIN_ID),
    tokenAmounts: tokenAmounts([TRON_USDT], ["100000"]),
  },
);

const { txData } = await post("/deposit", {
  sessionId: session.sessionId,
  nonce,
  signature,
  chainId: TRON_CHAIN_ID,
  tokenAddresses: [TRON_USDT],
  amounts: ["100000"], // 0.1 USDT
});

// txData is a Tron transaction object — broadcast with tronWeb
await tronWeb.trx.sign(txData, tronPrivateKey);
await tronWeb.trx.sendRawTransaction(txData);
```

{% endtab %}

{% tab title="Solana" %}

```js
// Solana: only one token per deposit; returns base64 tx
const session = await createSession(); // normal mode

const bodyParams = sessionBodyParams(session, SOLANA_CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [SOLANA_USDC],
  amounts: ["1000"], // 0.001 USDC (6 decimals)
};

const { txData } = await post("/deposit", body, requestSignaturePostHeader(session, "/deposit", body));

// txData is a base64-encoded serialized Solana transaction
// Decode, fill in a recent blockhash, sign with your keypair, and broadcast
const txBytes = Buffer.from(txData, "base64");
// ... (use @solana/web3.js Transaction.from(txBytes), sign, sendRawTransaction)
```

{% endtab %}
{% endtabs %}

## Withdraw (shielded → public)

{% tabs %}
{% tab title="EVM — normal mode" %}

```js
const session = await createSession();

// 1. Fetch the fee amount
const feeParams = sessionGetParams(session, CHAIN_ID);
feeParams.set("feeToken", USDC);
feeParams.append("tokenAddresses", USDC);
feeParams.set("externalActionId", "Transact");
const { feeAmount } = await get("/get-fee", feeParams, requestSignatureGetHeader(session, "/get-fee", feeParams.toString()));

// 2. Submit withdraw
const recipient = "0xRecipientAddress";
const bodyParams = sessionBodyParams(session, CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [USDC],
  amounts: ["500000"],
  recipientAddress: recipient,
  feeToken: USDC,
  feeAmount,
};

const { txHash } = await post("/withdraw", body, requestSignaturePostHeader(session, "/withdraw", body));
console.log("withdraw tx:", txHash);
```

{% endtab %}

{% tab title="EVM — EIP-712 mode (per-tx signature)" %}

```js
const session = await createSession(true); // useEIP712

// 1. Fetch the fee amount
const feeParams = sessionGetParams(session, CHAIN_ID);
feeParams.set("feeToken", USDC);
feeParams.append("tokenAddresses", USDC);
feeParams.set("externalActionId", "Transact");
const { feeAmount } = await get("/get-fee", feeParams, requestSignatureGetHeader(session, "/get-fee", feeParams.toString()));

// 2. Build dynamic fee fields — only fields present in the request go into the struct
const { typeFields: feeTypeFields, value: feeValue } = buildFeeFields(USDC, feeAmount);

const Withdraw = [
  { name: "nonce", type: "string" },
  { name: "sessionId", type: "string" },
  { name: "chainId", type: "uint256" },
  { name: "tokenAmounts", type: "TokenAmount[]" },
  { name: "recipient", type: "string" },
  ...feeTypeFields, // appends feeToken / feeAmount fields only if present
];

const recipient = "0xRecipientAddress";
const nonce = randomUUID();
const signature = await wallet.signTypedData(
  DOMAIN(CHAIN_ID),
  { TokenAmount, Withdraw },
  {
    nonce,
    sessionId: session.sessionId,
    chainId: BigInt(CHAIN_ID),
    tokenAmounts: tokenAmounts([USDC], ["500000"]),
    recipient,
    ...feeValue,
  },
);

const { txHash } = await post("/withdraw", {
  sessionId: session.sessionId,
  nonce,
  signature,
  chainId: CHAIN_ID,
  tokenAddresses: [USDC],
  amounts: ["500000"],
  recipientAddress: recipient,
  feeToken: USDC,
  feeAmount,
});
console.log("withdraw tx:", txHash);
```

{% endtab %}

{% tab title="Tron" %}

```js
// Tron: same dynamic EIP-712 shape as EVM
const session = await createSession(tronAddress, tronPrivateKey, true);

const feeParams = sessionGetParams(session, TRON_CHAIN_ID);
feeParams.set("feeToken", TRON_USDT);
feeParams.append("tokenAddresses", TRON_USDT);
feeParams.set("externalActionId", "Transact");
const { feeAmount } = await get("/get-fee", feeParams, requestSignatureGetHeader(session, "/get-fee", feeParams.toString()));

const { typeFields: feeTypeFields, value: feeValue } = buildFeeFields(TRON_USDT, feeAmount);

const ethersWallet = new ethers.Wallet(`0x${tronPrivateKey}`);
const Withdraw = [
  { name: "nonce", type: "string" },
  { name: "sessionId", type: "string" },
  { name: "chainId", type: "uint256" },
  { name: "tokenAmounts", type: "TokenAmount[]" },
  { name: "recipient", type: "string" },
  ...feeTypeFields,
];

const recipient = "0xRecipientTronAddress";
const nonce = randomUUID();
const signature = await ethersWallet.signTypedData(
  DOMAIN(TRON_CHAIN_ID),
  { TokenAmount, Withdraw },
  {
    nonce,
    sessionId: session.sessionId,
    chainId: BigInt(TRON_CHAIN_ID),
    tokenAmounts: tokenAmounts([TRON_USDT], ["10000"]),
    recipient,
    ...feeValue,
  },
);

const { txHash } = await post("/withdraw", {
  sessionId: session.sessionId,
  nonce,
  signature,
  chainId: TRON_CHAIN_ID,
  tokenAddresses: [TRON_USDT],
  amounts: ["10000"],
  recipientAddress: recipient,
  feeToken: TRON_USDT,
  feeAmount,
});
```

{% endtab %}

{% tab title="Solana" %}

```js
// Solana: feeToken is auto-set to tokenAddresses[0]
const session = await createSession(); // normal mode

const feeParams = sessionGetParams(session, SOLANA_CHAIN_ID);
feeParams.set("feeToken", SOLANA_USDC);
feeParams.set("externalActionId", "Transact");
feeParams.append("tokenAddresses", SOLANA_USDC);
feeParams.append("amounts", "1000");
const { feeAmount } = await get("/get-fee", feeParams, requestSignatureGetHeader(session, "/get-fee", feeParams.toString()));

const bodyParams = sessionBodyParams(session, SOLANA_CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [SOLANA_USDC],
  amounts: ["1000"],
  recipientAddress: address,
  feeAmount,
  // feeToken omitted — server uses tokenAddresses[0] and pairs it with feeAmount
};

const { txHash } = await post("/withdraw", body, requestSignaturePostHeader(session, "/withdraw", body));
console.log("withdraw tx:", txHash);
```

{% endtab %}
{% endtabs %}

## Swap (shielded → shielded, different token)

{% tabs %}
{% tab title="EVM — normal mode" %}

```js
const session = await createSession();

// 1. Get swap quote
const swapParams = sessionGetParams(session, CHAIN_ID);
swapParams.set("inputTokenAddress", USDC);
swapParams.set("outputTokenAddress", USDT);
swapParams.set("amount", "0.001");
const { swapData, externalActionId, outSwapAmount } =
  await get("/get-swap-data", swapParams, requestSignatureGetHeader(session, "/get-swap-data", swapParams.toString()));

// 2. Get the fee amount
const feeParams = sessionGetParams(session, CHAIN_ID);
feeParams.set("feeToken", USDC);
feeParams.set("externalActionId", externalActionId.toString());
feeParams.append("tokenAddresses", USDC);
feeParams.append("tokenAddresses", USDT);
const { feeAmount } = await get("/get-fee", feeParams, requestSignatureGetHeader(session, "/get-fee", feeParams.toString()));

// 3. Submit swap
const inAmount = "1000";
const bodyParams = sessionBodyParams(session, CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [USDC, USDT],
  amounts: [(-BigInt(inAmount)).toString(), outSwapAmount],
  externalActionId,
  swapData,
  feeToken: USDC,
  feeAmount,
};

const { txHash } = await post("/swap", body, requestSignaturePostHeader(session, "/swap", body));
console.log("swap tx:", txHash);
```

{% endtab %}

{% tab title="Solana" %}

```js
// Solana swap: feeToken auto-set to output token (tokenAddresses[1])
const session = await createSession();

// 1. Get OKX swap quote
const swapParams = sessionGetParams(session, SOLANA_CHAIN_ID);
swapParams.set("inputTokenAddress", SOLANA_USDC);
swapParams.set("outputTokenAddress", SOLANA_USDT);
swapParams.set("amount", "1.2");
const { swapData, externalActionId, outSwapAmount } =
  await get("/get-swap-data", swapParams, requestSignatureGetHeader(session, "/get-swap-data", swapParams.toString()));

// 2. Fetch the fee amount (mintFrom = input token)
const inAmountWei = 1200000n;
const feeParams = sessionGetParams(session, SOLANA_CHAIN_ID);
feeParams.set("feeToken", SOLANA_USDT);
feeParams.set("externalActionId", externalActionId.toString());
feeParams.set("mintFrom", SOLANA_USDC);
feeParams.append("tokenAddresses", SOLANA_USDC);
feeParams.append("tokenAddresses", SOLANA_USDT);
feeParams.append("amounts", inAmountWei.toString());
feeParams.append("amounts", (-BigInt(outSwapAmount)).toString());
const { feeAmount } = await get("/get-fee", feeParams, requestSignatureGetHeader(session, "/get-fee", feeParams.toString()));

// 3. Submit swap
const bodyParams = sessionBodyParams(session, SOLANA_CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [SOLANA_USDC, SOLANA_USDT],
  amounts: [(-inAmountWei).toString(), outSwapAmount],
  externalActionId,
  swapData,
  feeAmount,
  // feeToken omitted — server uses tokenAddresses[1] = USDT and pairs it with feeAmount
};

const { txHash } = await post("/swap", body, requestSignaturePostHeader(session, "/swap", body));
console.log("swap tx:", txHash);
```

{% endtab %}
{% endtabs %}

## Transfer (shielded → shielded, 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.

{% tabs %}
{% tab title="EVM / Tron — normal mode" %}

```js
const session = await createSession();

// Fetch recipient's handle
const recipientParams = sessionGetParams(recipientSession, CHAIN_ID);
const { recipientInfo } = await get(
  "/recipient-info", recipientParams,
  requestSignatureGetHeader(recipientSession, "/recipient-info", recipientParams.toString())
);

// Fetch the fee amount
const feeParams = sessionGetParams(session, CHAIN_ID);
feeParams.set("feeToken", USDC);
feeParams.append("tokenAddresses", USDC);
feeParams.set("externalActionId", "Transact");
const { feeAmount } = await get("/get-fee", feeParams, requestSignatureGetHeader(session, "/get-fee", feeParams.toString()));

const bodyParams = sessionBodyParams(session, CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [USDC],
  amounts: ["10000"],
  recipientAddress: recipientInfo, // handle, not a public address
  feeToken: USDC,
  feeAmount,
};

const { txHash } = await post("/transfer", body, requestSignaturePostHeader(session, "/transfer", body));
```

{% endtab %}

{% tab title="Solana" %}

```js
const session = await createSession(); // normal mode

const recipientParams = sessionGetParams(recipientSession, SOLANA_CHAIN_ID);
const { recipientInfo } = await get(
  "/recipient-info", recipientParams,
  requestSignatureGetHeader(recipientSession, "/recipient-info", recipientParams.toString())
);

const feeParams = sessionGetParams(session, SOLANA_CHAIN_ID);
feeParams.set("feeToken", SOLANA_USDC);
feeParams.set("externalActionId", "Transact");
feeParams.append("tokenAddresses", SOLANA_USDC);
feeParams.append("amounts", "10000");
const { feeAmount } = await get("/get-fee", feeParams, requestSignatureGetHeader(session, "/get-fee", feeParams.toString()));

const bodyParams = sessionBodyParams(session, SOLANA_CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [SOLANA_USDC],
  amounts: ["10000"],
  recipientAddress: recipientInfo,
  feeAmount,
  // feeToken omitted — server uses tokenAddresses[0] and pairs it with feeAmount
};

const { txHash } = await post("/transfer", body, requestSignaturePostHeader(session, "/transfer", body));
```

{% endtab %}
{% endtabs %}

## Deposit for other

Deposit into a different user's shielded balance using their `recipientInfo` handle.

{% tabs %}
{% tab title="EVM / Tron" %}
Use `POST /deposit-for-other` with the same body as `/deposit` plus `recipientInfo`.

```js
const bodyParams = sessionBodyParams(session, CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [USDC],
  amounts: ["1000000"],
  recipientInfo,
};

const { txData } = await post("/deposit-for-other", body, requestSignaturePostHeader(session, "/deposit-for-other", body));
await wallet.sendTransaction(txData);
```

{% endtab %}

{% tab title="Solana" %}
Use `POST /deposit-solana-for-other`. Pass `tokenAddresses[]` and `amounts[]` arrays (only the first element of each is used).

```js
const bodyParams = sessionBodyParams(session, SOLANA_CHAIN_ID);
const body = {
  ...bodyParams,
  tokenAddresses: [SOLANA_USDC],
  amounts: ["1000"],
  recipientInfo,
};

const { txData } = await post("/deposit-solana-for-other", body, requestSignaturePostHeader(session, "/deposit-solana-for-other", body));
// txData is base64 — decode, sign with keypair, and broadcast
```

{% endtab %}
{% endtabs %}

## 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](/hinkal/hinkal-api/description/private-send.md).
