Skip to main content

Headless Wallet Skill

The hathor-wallet-headless skill gives your LLM domain-specific guidance for building wallet backends, payment flows, token operations, multisig, atomic swaps, and nano contract calls through Headless Wallet.

For installation, see the LLM Integration overview.

tip

New to Headless Wallet? Start with the Headless Wallet component docs before using this skill.

Prerequisites

Activate the skill

Invoke explicitly:

/hathor-wallet-headless

Or let Claude Code activate it automatically when your request mentions Headless Wallet, /wallet/ endpoints, x-wallet-id, HTR transfers, token creation, NFTs, atomic swaps, multisig, or nano contracts.

To verify the skill is working:

/hathor-wallet-headless

Help me design a safe backend flow for sending HTR. Do not write code yet — give me the architecture and the docs I should check.

A working response will focus on the Headless Wallet workflow, warn against exposing secrets, and point to official docs.

Standard workflow

When starting any new integration:

  1. Describe the feature you want to build.
  2. Tell the LLM it uses Hathor Headless Wallet.
  3. Ask for architecture and a plan before asking for code.
  4. Ask the LLM to flag which official Hathor docs to check at each step.
  5. Ask for a review checklist before testing with real funds.

Build a payment endpoint — quick start

This walkthrough shows how to use the skill to build POST /payments/send-htr end-to-end.

Step 1 — Define the architecture

/hathor-wallet-headless

I want to create POST /payments/send-htr in my backend.

Give me the recommended architecture for using Hathor Headless Wallet safely.

Include:
- which logic belongs in my backend vs Headless Wallet
- which secrets must stay server-side
- which official Hathor article is relevant at each step
- what can go wrong during development

Expected guidance:

  • Frontend never calls Headless Wallet directly
  • Backend owns the integration — validates input, converts amounts, calls Headless Wallet
  • Seeds, API keys, and wallet config stay server-side
  • Verify endpoint-specific behavior in the API reference

Step 2 — Configure your environment

Set these backend environment variables:

HEADLESS_WALLET_URL=http://localhost:8000
HEADLESS_WALLET_ID=your-wallet-id
HEADLESS_WALLET_API_KEY=change-me
HATHOR_NETWORK=testnet

Config helper:

// src/config/headlessWalletConfig.ts

export type HeadlessWalletConfig = {
baseUrl: string;
walletId: string;
apiKey?: string;
network: 'testnet' | 'mainnet' | 'privatenet';
};

export function getHeadlessWalletConfig(): HeadlessWalletConfig {
const baseUrl = process.env.HEADLESS_WALLET_URL;
const walletId = process.env.HEADLESS_WALLET_ID;
const apiKey = process.env.HEADLESS_WALLET_API_KEY;
const network = process.env.HATHOR_NETWORK ?? 'testnet';

if (!baseUrl) throw new Error('Missing HEADLESS_WALLET_URL');
if (!walletId) throw new Error('Missing HEADLESS_WALLET_ID');
if (!['testnet', 'mainnet', 'privatenet'].includes(network)) {
throw new Error('Invalid HATHOR_NETWORK value');
}

return { baseUrl, walletId, apiKey, network: network as HeadlessWalletConfig['network'] };
}

Step 3 — Generate the implementation

/hathor-wallet-headless

Generate the backend code for POST /payments/send-htr.

Use TypeScript. Create:
- HeadlessWalletClient (status check + send HTR)
- htrToIntegerAmount helper
- POST /payments/send-htr route handler

Requirements:
- read config from environment variables
- check wallet readiness before sending
- convert HTR amounts before calling Headless Wallet
- return only the transaction hash to the client
- add comments where endpoint fields must be verified in the official API reference

HTR amount conversion helper:

// src/utils/htrAmount.ts

// Hathor amounts are integers where the last two digits are the decimal part.
// 1.25 HTR => 125. Confirm in the official docs before production use.
const HTR_DECIMALS = 2;
const HTR_FACTOR = 10 ** HTR_DECIMALS;

export function htrToIntegerAmount(amount: number | string): number {
const normalized = String(amount).trim();

if (!/^\d+(\.\d+)?$/.test(normalized)) {
throw new Error('Amount must be a positive decimal value');
}

const [wholePart, decimalPart = ''] = normalized.split('.');

if (decimalPart.length > HTR_DECIMALS) {
throw new Error(`Amount cannot have more than ${HTR_DECIMALS} decimal places`);
}

const integerAmount =
Number(wholePart) * HTR_FACTOR +
Number(decimalPart.padEnd(HTR_DECIMALS, '0'));

if (!Number.isSafeInteger(integerAmount) || integerAmount <= 0) {
throw new Error('Amount must be greater than zero and within the safe integer range');
}

return integerAmount;
}

Headless Wallet client:

// src/integrations/headless-wallet/HeadlessWalletClient.ts

export type HeadlessWalletClientOptions = {
baseUrl: string;
walletId: string;
apiKey?: string;
};

export type SendHtrInput = { address: string; amount: number };
export type SendHtrResult = { txId: string };

export class HeadlessWalletClient {
private readonly baseUrl: string;
private readonly walletId: string;
private readonly apiKey?: string;

constructor(options: HeadlessWalletClientOptions) {
this.baseUrl = options.baseUrl.replace(/\/$/, '');
this.walletId = options.walletId;
this.apiKey = options.apiKey;
}

private buildHeaders(): HeadersInit {
const headers: HeadersInit = {
'content-type': 'application/json',
// Confirm the exact wallet identifier header in the official API reference.
'x-wallet-id': this.walletId,
};
if (this.apiKey) {
// Headless Wallet uses x-api-key for API key authentication, not Bearer.
// Confirm the exact header name in your deployment's configuration.
headers['x-api-key'] = this.apiKey;
}
return headers;
}

async getWalletStatus(): Promise<unknown> {
// Confirm the exact status endpoint in the official API reference.
const response = await fetch(`${this.baseUrl}/wallet/status`, {
method: 'GET',
headers: this.buildHeaders(),
});
if (!response.ok) throw await this.toError(response);
return response.json();
}

async sendHtr(input: SendHtrInput): Promise<SendHtrResult> {
// Confirm /wallet/simple-send-tx request fields and response shape in the API reference.
const response = await fetch(`${this.baseUrl}/wallet/simple-send-tx`, {
method: 'POST',
headers: this.buildHeaders(),
body: JSON.stringify({ address: input.address, value: input.amount }),
});
if (!response.ok) throw await this.toError(response);
const data = await response.json();
if (typeof data.hash !== 'string') {
throw new Error('Unexpected Headless Wallet response: missing transaction hash');
}
return { txId: data.hash };
}

private async toError(response: Response): Promise<Error> {
let details: unknown;
try { details = await response.json(); } catch { details = await response.text(); }
return new Error(`Headless Wallet error ${response.status}: ${JSON.stringify(details)}`);
}
}

Route handler:

// src/routes/payments.ts

import { Router } from 'express';
import { getHeadlessWalletConfig } from '../config/headlessWalletConfig';
import { HeadlessWalletClient } from '../integrations/headless-wallet/HeadlessWalletClient';
import { htrToIntegerAmount } from '../utils/htrAmount';

const router = Router();

router.post('/payments/send-htr', async (req, res) => {
try {
const { address, amount } = req.body;

if (typeof address !== 'string' || !address.trim()) {
return res.status(400).json({ message: 'Destination address is required' });
}
if (typeof amount !== 'number') {
return res.status(400).json({ message: 'Amount must be a number' });
}

const config = getHeadlessWalletConfig();
const integerAmount = htrToIntegerAmount(amount);
const client = new HeadlessWalletClient(config);

await client.getWalletStatus();

const result = await client.sendHtr({ address, amount: integerAmount });

return res.status(200).json({ txId: result.txId });
} catch (error) {
console.error('Failed to send HTR', {
message: error instanceof Error ? error.message : 'Unknown error',
});
return res.status(500).json({ message: 'Failed to send HTR' });
}
});

export default router;

Step 4 — Review the output

Before testing, ask the LLM to check its own output:

/hathor-wallet-headless

Review the generated code against this checklist:
- No seed is hardcoded
- No API key is logged
- Headless Wallet is called only from the backend
- Wallet is checked before sending
- HTR amount is converted before calling Headless Wallet
- Response exposes only the transaction hash
- Endpoint assumptions are marked for verification in the official API reference

Step 5 — Test locally

curl -X POST http://localhost:3000/payments/send-htr \
-H "Content-Type: application/json" \
-d '{"address": "<destination-hathor-address>", "amount": 1.25}'

Expected success response:

{ "txId": "transaction-hash" }
note

Confirm the exact transaction hash field name from the Headless Wallet API reference before shipping.

For testnet setup, follow Connect Headless Wallet to testnet.

Prompt patterns

Design a new integration

/hathor-wallet-headless

I need to implement [feature].

Before writing code:
1. Recommended architecture
2. Headless Wallet operations involved
3. Security concerns
4. Official Hathor docs to check at each step
5. Step-by-step development plan

Add token or NFT functionality

/hathor-wallet-headless

Help me design a [token/NFT] flow using Headless Wallet.

For each step: what my backend validates, which Headless Wallet operation is involved,
which endpoint details I must verify in the API reference, and what security checks are needed.

Debug an integration

/hathor-wallet-headless

I am getting an error from Headless Wallet. Help me debug it step by step.

Endpoint: [endpoint]
Method: [method]
Headers (no secrets): [headers]
Request body (no seeds): [body]
Response: [response]
Wallet status: [started/not started]

Review existing code

/hathor-wallet-headless

Review this Headless Wallet integration. Check:
- wallet operations are server-side only
- sensitive values are not logged
- requests include the required wallet identifier
- HTR amounts are converted before API calls
- wallet readiness is checked before sending
- errors are handled defensively

[paste code]

LLM guardrails

When using this skill, the LLM should:

  • Never invent endpoint schemas — point to the official API reference instead
  • Never ask the user to share seeds, private keys, or API keys
  • Never suggest logging sensitive wallet data
  • Prefer server-side wallet operations for privileged actions
  • Clearly mark assumptions that require verification in official docs