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.
New to Headless Wallet? Start with the Headless Wallet component docs before using this skill.
Prerequisites
- The
hathor-wallet-headlessskill installed (install guide) - A running Headless Wallet instance (Docker setup · source setup · connect testnet)
- Node.js and TypeScript if you plan to follow the code examples
- Headless Wallet API reference open for exact endpoint schemas
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:
- Describe the feature you want to build.
- Tell the LLM it uses Hathor Headless Wallet.
- Ask for architecture and a plan before asking for code.
- Ask the LLM to flag which official Hathor docs to check at each step.
- 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" }
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