> 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-waas/quick-start.md).

# Quick Start

This guide walks through the full setup: creating an organization, adding a user, granting policies, and creating a wallet — everything you need before executing transactions.

## Prerequisites

```bash
npm install tweetnacl
```

## Setup helpers

```js
const nacl = require("tweetnacl");
const { randomUUID } = require("crypto");

const WAAS_BASE_URL = "https://api.hinkal.io";

function bytesToHex(bytes) {
  return Buffer.from(bytes).toString("hex");
}

function normalizeRoutePath(path) {
  const withSlash = path.startsWith("/") ? path : `/${path}`;
  return withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
}

// Must match the server's route pattern exactly, e.g. "POST /waas/create-wallet"
function buildActionBinding(method, routePath) {
  return `${method.toUpperCase()} ${normalizeRoutePath(routePath)}`;
}

function buildXStamp(binding, params, keypair) {
  const canonical = JSON.stringify([binding, Object.entries(params)]);
  const sigBytes = nacl.sign.detached(Buffer.from(canonical, "utf8"), keypair.secretKey);
  const stamp = {
    publicKey: bytesToHex(keypair.publicKey),
    signature: bytesToHex(sigBytes),
  };
  return Buffer.from(JSON.stringify(stamp)).toString("base64url");
}

async function post(path, body, keypair) {
  const bodyWithNonce = { ...body, nonce: randomUUID() };
  const binding = buildActionBinding("POST", path);
  const res = await fetch(`${WAAS_BASE_URL}${path}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Stamp": buildXStamp(binding, bodyWithNonce, keypair),
    },
    body: JSON.stringify(bodyWithNonce),
  });
  return res.json();
}
```

{% stepper %}
{% step %}

## Step 1 — Generate keypairs

The root keypair belongs to the organization administrator. The user keypair belongs to the end user.

```js
const rootKeypair = nacl.sign.keyPair();
const userKeypair = nacl.sign.keyPair();
```

{% endstep %}

{% step %}

## Step 2 — Create an organization

The root keypair signs this request. The signing public key becomes the root user's identifier.

```js
const orgRes = await post(
  "/waas/create-organization",
  { organizationName: "My Company" },
  rootKeypair
);

const { organizationId, userId: rootUserId } = orgRes.data;
```

{% endstep %}

{% step %}

## Step 3 — Create a user

Root creates a user by registering the user's public key.

```js
const userRes = await post(
  "/waas/create-user",
  {
    organizationId,
    publicKey: bytesToHex(userKeypair.publicKey),
  },
  rootKeypair
);

const { userId } = userRes.data;
```

{% endstep %}

{% step %}

## Step 4 — Grant policies

Policies define what actions a user is allowed to perform. Grant `CREATE_WALLET` and `SIGN_TRANSACTION` before the user can create wallets or execute transactions.

```js
await post(
  "/waas/add-policy",
  {
    organizationId,
    policy: { userIds: [userId], actionType: "CREATE_WALLET" },
  },
  rootKeypair
);

await post(
  "/waas/add-policy",
  {
    organizationId,
    policy: { userIds: [userId], actionType: "SIGN_TRANSACTION" },
  },
  rootKeypair
);
```

{% endstep %}

{% step %}

## Step 5 — Create a wallet

The user signs this request with their own keypair. The response contains EVM, Tron, and Solana addresses.

```js
const walletRes = await post(
  "/waas/create-wallet",
  { organizationId, userId },
  userKeypair
);

const { addresses } = walletRes.data;
console.log("EVM:    ", addresses.evm);
console.log("Tron:   ", addresses.tron);
console.log("Solana: ", addresses.solana);
```

{% endstep %}

{% step %}

## Step 6 — Execute a transaction

Once the wallet is set up, execute transactions using the user's keypair.

`POST /waas/public-to-public` performs a deposit-and-withdraw: it deposits tokens into Hinkal and immediately schedules the withdrawal to the recipient. The response includes both a `txHash` (the deposit transaction) and a `scheduleId` for polling the withdrawal status.

```js
const txRes = await post(
  "/waas/public-to-public",
  {
    organizationId,
    userId,
    fromAddress: addresses.evm,
    to: "0xRecipientAddress",
    token: "0xTokenContractAddress",
    amount: "1.0",
    chainId: 1,
  },
  userKeypair
);

const { txHash, scheduleId } = txRes.data;
console.log("Deposit tx:", txHash);
console.log("Schedule ID:", scheduleId);
```

{% endstep %}

{% step %}

## Step 7 — Poll withdrawal status

Use the `scheduleId` to track when the withdrawal reaches the recipient on-chain. Poll until `status` is `completed` or `failed`.

```js
async function get(path, routePath, keypair) {
  const params = { nonce: randomUUID() };
  const binding = buildActionBinding("GET", routePath);
  const qs = new URLSearchParams(params).toString();
  const res = await fetch(`${WAAS_BASE_URL}${path}?${qs}`, {
    headers: { "X-Stamp": buildXStamp(binding, params, keypair) },
  });
  return res.json();
}

async function pollUntilComplete(scheduleId, keypair, { intervalMs = 3000, timeoutMs = 120000 } = {}) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    // routePath stays the literal ":scheduleId" pattern — the binding uses the route pattern,
    // not the concrete ID in the URL
    const res = await get(
      `/waas/scheduled-transaction/${scheduleId}`,
      "/waas/scheduled-transaction/:scheduleId",
      keypair,
    );
    const { transactions } = res.data;
    transactions.forEach((tx) => console.log("Status:", tx.status, "| txHash:", tx.txHash));
    const isTerminal = (tx) => tx.status === "completed" || tx.status === "failed";
    if (transactions.length > 0 && transactions.every(isTerminal)) return transactions;
    await new Promise((r) => setTimeout(r, intervalMs));
  }
  throw new Error("Timed out waiting for scheduled transaction");
}

const finished = await pollUntilComplete(scheduleId, userKeypair);
finished.forEach((tx) => console.log(tx.status === "failed" ? "Withdrawal failed:" : "Withdrawal tx:", tx.status, tx.txHash));
```

For private transactions, use `/waas/public-to-private`, `/waas/private-to-public`, or `/waas/private-to-private` with the same request shape.
{% endstep %}
{% endstepper %}
