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

Signing Requests

Every request to the WaaS API requires an X-Stamp header — an Ed25519 signature over the full request payload. This page shows how to build it.

How it works

1

Build an action binding

"<METHOD> <routePath>", e.g. "POST /waas/create-wallet" or "GET /waas/public-balance".

routePath is the server's route pattern — for the one parameterized route, GET /waas/scheduled-transaction/{scheduleId}, the binding is the literal "GET /waas/scheduled-transaction/:scheduleId", not the concrete scheduleId from the URL. Binding the route into the signed message stops a stamp captured for one endpoint from being replayed against another.

2

Take all request parameters

Body for POST, query params for GET.

3

Add a unique nonce

Add a unique nonce to the parameters.

4

Serialize

JSON.stringify([binding, Object.entries(params)])

5

Sign the bytes

Sign the UTF-8 bytes with your Ed25519 private key.

6

Encode and send

Base64URL-encode the resulting JSON stamp and send it as the X-Stamp header.

Building the stamp

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

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: Buffer.from(keypair.publicKey).toString("hex"),
    signature: Buffer.from(sigBytes).toString("hex"),
  };
  return Buffer.from(JSON.stringify(stamp)).toString("base64url");
}

Making a signed POST request

For a static route like /waas/create-wallet, path doubles as the route pattern, so no extra argument is needed. Every WaaS POST route is static — only the scheduled-transaction GET route is parameterized (see below).

Making a signed GET request

For GET requests, the nonce and all parameters go in the query string — the stamp is built over those same query parameters, bound to the route.

routePath is usually the same as path — e.g. get(baseUrl, "/waas/private-balance", "/waas/private-balance", params, keypair).

The exception is GET /waas/scheduled-transaction/{scheduleId}: path includes the concrete ID (`/waas/scheduled-transaction/${scheduleId}`), but routePath must stay the literal pattern:

Nonce

Every request must include a unique nonce. A UUID works well:

Once a nonce has been used, any request reusing it will be rejected with 401 Nonce already used.