A Solana wallet with built-in payment guardrails. Use it like a regular wallet — transactions are automatically checked against spending policies. Small/routine payments go through instantly. Larger or unusual payments are held for human approval before executing.
bun add github:cosmicsymmetry/hil-sdkAfter installing, run the setup script to register the wallet skill with OpenClaw:
bunx hil-setupThis adds the wallet skill to OpenClaw and configures your wallet URL and API key. Restart OpenClaw to activate.
import { HilWallet } from "@hil/sdk";
const wallet = new HilWallet({
rpcUrl: "http://localhost:3000",
apiKey: "your-api-key",
});
const txSignature = await wallet.transfer("7xKp...recipient", 0.5);Create a wallet instance.
const wallet = new HilWallet({
rpcUrl: "http://localhost:3000", // HIL server URL
apiKey: "your-api-key", // Agent API key
pollingIntervalMs: 2000, // Optional: poll interval (default: 2000)
timeoutMs: 300000, // Optional: max wait for approval (default: 5 min)
});Send SOL or SPL tokens. Returns a transaction signature.
// Send SOL
const sig = await wallet.transfer("7xKp...recipient", 0.5);
// Send SOL with memo
const sig = await wallet.transfer("7xKp...recipient", 1.0, {
memo: "Payment for services",
});
// Send SPL token
const sig = await wallet.transfer("7xKp...recipient", 100, {
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
});
// With provenance tag (helps the approver understand context)
const sig = await wallet.transfer("7xKp...recipient", 0.5, {
memo: "Invoice #1234",
source: "email", // "user-command" | "email" | "web-scrape" | "tool-output" | "scheduled"
});Parameters:
| Param | Type | Description |
|---|---|---|
recipient |
string |
Base58 public key of the recipient |
amount |
number |
Amount in SOL (e.g. 0.5 for half a SOL) |
opts.mint |
string? |
SPL token mint address. Omit for native SOL |
opts.memo |
string? |
Human-readable reason for the payment |
opts.source |
string? |
Where the instruction came from. Default: "tool-output" |
Returns: Promise<string> — transaction signature
Throws: Error if the transfer is denied by policy, rejected by the approver, or times out.
Get the SOL balance of the wallet.
const balance = await wallet.getBalance(); // e.g. 4.5Returns: Promise<number> — balance in SOL
Get the wallet's public key.
const pubkey = await wallet.getPublicKey(); // e.g. "7xKp..."Returns: Promise<string> — base58-encoded public key
Get recent transactions made through this wallet.
const history = await wallet.getTransactionHistory(10);
for (const tx of history) {
console.log(`${tx.amount} SOL → ${tx.recipient} [${tx.status}]`);
}Returns: Promise<TransactionRecord[]>
Each record contains:
| Field | Type | Description |
|---|---|---|
id |
string |
Payment ID |
recipient |
string |
Recipient public key |
amount |
number |
Amount in SOL |
mint |
string | null |
Token mint (null for SOL) |
memo |
string | null |
Memo |
status |
string |
"confirmed", "rejected", "expired", "failed" |
txSignature |
string | null |
On-chain transaction signature |
createdAt |
string |
ISO timestamp |
resolvedAt |
string | null |
ISO timestamp when resolved |
All errors are standard Error instances with descriptive messages:
try {
await wallet.transfer("7xKp...", 5.0);
} catch (err) {
// "Transfer denied: Amount exceeds per-tx limit"
// "Transfer rejected by approver"
// "Transfer expired — no approval received in time"
// "Transfer timed out waiting for approval"
console.error(err.message);
}Under the hood, the wallet routes every transfer through a guardrail server that:
- Checks the transfer against spending policies (per-transaction limits, volume caps, allowlists)
- Auto-approves small routine payments instantly
- Sends larger or unusual payments to a human approver via Telegram
- Signs and submits the transaction only after authorization