
Validation
- 153 installs
- 9 repo stars
- Updated July 16, 2026
- aibtcdev/skills
Validate Stacks transactions, signatures, addresses, and contract inputs before submit or persist—reducing bad on-chain state in wallets, APIs, and agent-driven Bitcoin apps.
About
Provides validation patterns for Stacks and Bitcoin payloads: signatures, addresses, amounts, and contract arguments before execution. Critical aibtcdev/skills guidance at ship time so agents and APIs reject invalid crypto operations early instead of broadcasting failing or unsafe transactions.
- Pre-submit transaction validation
- Signature and address verification
- Contract input sanity checks
- Agent-safe rejection messaging
- Reduces costly on-chain failures
Validation by the numbers
- 153 all-time installs (skills.sh)
- Ranked #122 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aibtcdev/skills --skill validationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 153 |
|---|---|
| repo stars | ★ 9 |
| Last updated | July 16, 2026 |
| Repository | aibtcdev/skills ↗ |
What it does
Validate Stacks transactions, signatures, addresses, and contract inputs before submit or persist—reducing bad on-chain state in wallets, APIs, and agent-driven Bitcoin apps.
Files
Validation Skill
Provides ERC-8004 on-chain agent validation operations using the validation-registry contract. Read operations (get-status, get-summary, get-agent-validations, get-validator-requests) work without a wallet. Write operations (request, respond) require an unlocked wallet.
Usage
bun run validation/validation.ts <subcommand> [options]Subcommands
request
Request validation from a validator for an agent in the ERC-8004 validation registry. The request hash must be a 32-byte SHA-256 hash of the request data. Requires an unlocked wallet.
bun run validation/validation.ts request --validator <address> --agent-id <id> --request-uri <uri> --request-hash <hex> [--fee <fee>] [--sponsored]Options:
--validator(required) — Stacks address of the validator to request validation from--agent-id(required) — Agent ID to request validation for (non-negative integer)--request-uri(required) — URI pointing to the validation request data--request-hash(required) — 32-byte SHA-256 hash of the request data as a hex string--fee(optional) — Fee preset (low,medium,high) or micro-STX amount--sponsored(flag) — Submit as a sponsored transaction
Output:
{
"success": true,
"txid": "0xabc...",
"message": "Validation requested from SP2... for agent 42.",
"validator": "SP2...",
"agentId": 42,
"requestUri": "ipfs://request...",
"network": "mainnet",
"explorerUrl": "https://explorer.hiro.so/txid/0xabc..."
}respond
Submit a validation response for a pending validation request. Only the validator specified in the original request can call this. Response must be an integer between 0 and 100. Can be called multiple times for progressive updates. Requires an unlocked wallet.
bun run validation/validation.ts respond --request-hash <hex> --response <value> --response-uri <uri> --response-hash <hex> [--tag <tag>] [--fee <fee>] [--sponsored]Options:
--request-hash(required) — 32-byte SHA-256 hash of the original request as a hex string--response(required) — Validation response score (integer between 0 and 100)--response-uri(required) — URI pointing to the validation response data--response-hash(required) — 32-byte SHA-256 hash of the response data as a hex string--tag(optional) — Classification tag for the validation response--fee(optional) — Fee preset (low,medium,high) or micro-STX amount--sponsored(flag) — Submit as a sponsored transaction
Output:
{
"success": true,
"txid": "0xdef...",
"message": "Validation response 85 submitted for request hash a3f2b1....",
"response": 85,
"responseUri": "ipfs://response...",
"tag": "security",
"network": "mainnet",
"explorerUrl": "https://explorer.hiro.so/txid/0xdef..."
}get-status
Get the status of a validation request by its 32-byte request hash. Returns validator, agent ID, response score, response hash, tag, last update block, and whether a response has been submitted. Does not require a wallet.
bun run validation/validation.ts get-status --request-hash <hex>Options:
--request-hash(required) — 32-byte SHA-256 hash of the validation request as a hex string
Output:
{
"success": true,
"requestHash": "a3f2b1...64hex",
"validator": "SP2...",
"agentId": 42,
"response": 85,
"responseHash": "b4e9c2...64hex",
"tag": "security",
"lastUpdate": 123456,
"hasResponse": true,
"network": "mainnet"
}get-summary
Get the aggregated validation summary for an agent. Returns the total validation count and average response score. Does not require a wallet.
bun run validation/validation.ts get-summary --agent-id <id>Options:
--agent-id(required) — Agent ID to query (non-negative integer)
Output:
{
"success": true,
"agentId": 42,
"count": 3,
"avgResponse": 88,
"network": "mainnet"
}get-agent-validations
Get a paginated list of validation request hashes for an agent. Returns request hashes as hex strings. Cursor-based pagination with page size 14. Does not require a wallet.
bun run validation/validation.ts get-agent-validations --agent-id <id> [--cursor <cursor>]Options:
--agent-id(required) — Agent ID to query (non-negative integer)--cursor(optional) — Pagination cursor (non-negative integer, from previous response)
Output:
{
"success": true,
"agentId": 42,
"validations": ["a3f2b1...64hex", "c5d8e4...64hex"],
"cursor": null,
"network": "mainnet"
}get-validator-requests
Get a paginated list of validation request hashes submitted to a validator. Returns request hashes as hex strings. Cursor-based pagination with page size 14. Does not require a wallet.
bun run validation/validation.ts get-validator-requests --validator <address> [--cursor <cursor>]Options:
--validator(required) — Stacks address of the validator to query--cursor(optional) — Pagination cursor (non-negative integer, from previous response)
Output:
{
"success": true,
"validator": "SP2...",
"requests": ["a3f2b1...64hex", "c5d8e4...64hex"],
"cursor": null,
"network": "mainnet"
}Notes
- Read operations (get-status, get-summary, get-agent-validations, get-validator-requests) work without a wallet
- Write operations require an unlocked wallet (
bun run wallet/wallet.ts unlock) --request-hashand--response-hashmust be exactly 32 bytes (64 hex characters); use SHA-256--responsescore must be an integer between 0 and 100 (inclusive)respondcan only be called by the validator specified in the original validation requestrespondcan be called multiple times on the same request for progressive updates- Pagination uses cursor-based navigation; pass the
cursorfrom one response into the next call - Validation is a Stacks L2 operation — check transaction status with
stx get-transaction-statusafter write calls
Validation Agent
This agent manages ERC-8004 on-chain agent validation using the validation-registry contract. It handles requesting validations from validators, submitting validation responses, and all read-only queries for validation data. Read operations work without a wallet. Write operations require an unlocked wallet.
Prerequisites
- For write operations (request, respond): wallet must be unlocked — run
bun run wallet/wallet.ts unlockfirst - For read operations (get-status, get-summary, get-agent-validations, get-validator-requests): no wallet required
- The target agent ID must exist in the identity registry before requesting validation
respond: the active wallet must match the validator address specified in the original request--request-hashand--response-hashmust be computed with SHA-256 before passing (exactly 32 bytes = 64 hex chars)
Decision Logic
| Goal | Subcommand |
|---|---|
| Request validation from a specific validator for an agent | request --validator <addr> --agent-id <id> --request-uri <uri> --request-hash <hex> |
| Submit a validation response score as a validator | respond --request-hash <hex> --response <0-100> --response-uri <uri> --response-hash <hex> |
| Check the status and score of a specific validation request | get-status --request-hash <hex> — read-only |
| Get the aggregated validation count and average score for an agent | get-summary --agent-id <id> — read-only |
| List all validation request hashes for an agent | get-agent-validations --agent-id <id> — paginated |
| List all validation request hashes submitted to a validator | get-validator-requests --validator <addr> — paginated |
Safety Checks
--request-hashand--response-hashmust each be exactly 32 bytes (64 hex characters); compute with SHA-256 before passing — do not use truncated or non-SHA-256 hashesrespondcan only be called by the validator specified in the original request — the active wallet address must matchvalidatorin the on-chain record; verify withget-statusfirstrespondcan be called multiple times on the same request hash for progressive score updates — each call overwrites the previous response--responsemust be an integer between 0 and 100 inclusive; values outside this range are rejected by the contract- Both
requestandrespondsubmit Stacks L2 transactions — check status withbun run stx/stx.ts get-transaction-status --txid <txid>after submission
Error Handling
| Error message | Cause | Fix |
|---|---|---|
| "No active wallet. Please unlock your wallet first." | Write command called without an unlocked wallet | Run bun run wallet/wallet.ts unlock --password <password> |
| "--agent-id must be a non-negative integer" | Invalid agent ID | Pass a non-negative integer (e.g., --agent-id 42) |
| "--response must be an integer between 0 and 100" | Response score out of range | Pass an integer from 0 to 100 inclusive |
| "--request-hash must be exactly 32 bytes (64 hex characters)" | Hash is wrong length or not valid hex | Compute SHA-256 of the request data and pass the 64-char hex result |
| "--response-hash must be exactly 32 bytes (64 hex characters)" | Response hash is wrong length or not valid hex | Compute SHA-256 of the response data |
| "--cursor must be a non-negative integer" | Invalid pagination cursor | Use the cursor value from a previous paginated response |
| "Validation request not found" | get-status found no record for the given hash | The request may not be confirmed yet; wait for tx confirmation or verify the hash |
Output Handling
request: extracttxidto track the on-chain submission;explorerUrllinks to the transactionrespond: extracttxidto confirm the response was submitted;responseconfirms the score recordedget-status: extracthasResponse(true if a score has been submitted),response(0–100 score),validator, andagentId;lastUpdateis the block height of the last responseget-summary: extractcount(total validations) andavgResponse(average score 0–100) to assess overall agent validation qualityget-agent-validations: extractvalidations(array of request hash hex strings) andcursor(null if last page); use each hash withget-statusfor detailsget-validator-requests: extractrequests(array of request hash hex strings) andcursorfor pagination; useful for validators processing their queue
Example Invocations
# Request validation from a validator for agent 42
bun run validation/validation.ts request --validator SP2... --agent-id 42 --request-uri ipfs://request-data --request-hash a3f2b1...64hex
# Submit a validation response score of 85
bun run validation/validation.ts respond --request-hash a3f2b1...64hex --response 85 --response-uri ipfs://response-data --response-hash b4e9c2...64hex --tag security
# Get the aggregated validation summary for agent 42
bun run validation/validation.ts get-summary --agent-id 42#!/usr/bin/env bun
/**
* Validation skill CLI
* ERC-8004 on-chain agent validation management
*
* Usage: bun run validation/validation.ts <subcommand> [options]
*/
import { Command } from "commander";
import { NETWORK, getExplorerTxUrl } from "../src/lib/config/networks.js";
import { getWalletManager } from "../src/lib/services/wallet-manager.js";
import { Erc8004Service } from "../src/lib/services/erc8004.service.js";
import { resolveFee } from "../src/lib/utils/fee.js";
import { printJson, handleError } from "../src/lib/utils/cli.js";
// ---------------------------------------------------------------------------
// Validation helpers
// ---------------------------------------------------------------------------
/** Default read-only caller address per network (boot addresses) */
const DEFAULT_CALLER: Record<string, string> = {
mainnet: "SP000000000000000000002Q6VF78",
testnet: "ST000000000000000000002AMW42H",
};
/**
* Get the caller address for read-only calls.
* Prefers the active wallet address if available.
*/
function getCallerAddress(): string {
const walletManager = getWalletManager();
const sessionInfo = walletManager.getSessionInfo();
return sessionInfo?.address || DEFAULT_CALLER[NETWORK] || DEFAULT_CALLER.testnet;
}
/**
* Strip optional 0x prefix and validate a hex string.
* Optionally enforce exact byte count.
*/
function normalizeHex(hex: string, label: string, exactBytes?: number): string {
let normalized = hex;
if (normalized.startsWith("0x") || normalized.startsWith("0X")) {
normalized = normalized.slice(2);
}
if (
normalized.length === 0 ||
normalized.length % 2 !== 0 ||
!/^[0-9a-fA-F]+$/.test(normalized)
) {
throw new Error(`${label} must be a non-empty, even-length hex string`);
}
if (exactBytes !== undefined && normalized.length !== exactBytes * 2) {
throw new Error(
`${label} must be exactly ${exactBytes} bytes (${exactBytes * 2} hex characters)`
);
}
return normalized;
}
// ---------------------------------------------------------------------------
// Program
// ---------------------------------------------------------------------------
const program = new Command();
program
.name("validation")
.description(
"ERC-8004 on-chain agent validation: request validations from validators, " +
"submit validation responses, and query validation status, summaries, " +
"and paginated lists by agent or validator"
)
.version("0.1.0");
// ---------------------------------------------------------------------------
// request
// ---------------------------------------------------------------------------
program
.command("request")
.description(
"Request validation from a validator for an agent in the ERC-8004 validation registry. " +
"Caller (tx-sender) is the requester. The request hash must be a 32-byte SHA-256 hash " +
"of the request data. Requires an unlocked wallet."
)
.requiredOption(
"--validator <address>",
"Stacks address of the validator to request validation from"
)
.requiredOption(
"--agent-id <id>",
"Agent ID to request validation for (non-negative integer)"
)
.requiredOption(
"--request-uri <uri>",
"URI pointing to the validation request data"
)
.requiredOption(
"--request-hash <hex>",
"32-byte SHA-256 hash of the request data as a hex string"
)
.option(
"--fee <fee>",
'Fee preset ("low", "medium", "high") or micro-STX amount'
)
.option("--sponsored", "Submit as a sponsored transaction", false)
.action(
async (opts: {
validator: string;
agentId: string;
requestUri: string;
requestHash: string;
fee?: string;
sponsored: boolean;
}) => {
try {
const walletManager = getWalletManager();
const account = walletManager.getActiveAccount();
if (!account) {
throw new Error("No active wallet. Please unlock your wallet first.");
}
const agentId = parseInt(opts.agentId, 10);
if (isNaN(agentId) || agentId < 0) {
throw new Error("--agent-id must be a non-negative integer");
}
const normalizedHash = normalizeHex(opts.requestHash, "--request-hash", 32);
const requestHashBuf = Buffer.from(normalizedHash, "hex");
const service = new Erc8004Service(NETWORK);
const feeAmount = opts.fee
? await resolveFee(opts.fee, NETWORK, "contract_call")
: undefined;
const result = await service.requestValidation(
account,
opts.validator,
agentId,
opts.requestUri,
requestHashBuf,
feeAmount,
opts.sponsored
);
printJson({
success: true,
txid: result.txid,
message: `Validation requested from ${opts.validator} for agent ${agentId}.`,
validator: opts.validator,
agentId,
requestUri: opts.requestUri,
network: NETWORK,
explorerUrl: getExplorerTxUrl(result.txid, NETWORK),
});
} catch (error) {
handleError(error);
}
}
);
// ---------------------------------------------------------------------------
// respond
// ---------------------------------------------------------------------------
program
.command("respond")
.description(
"Submit a validation response for a pending validation request in the ERC-8004 validation registry. " +
"Only the validator specified in the original request can call this. " +
"Response must be an integer between 0 and 100. Can be called multiple times for progressive updates. " +
"Requires an unlocked wallet."
)
.requiredOption(
"--request-hash <hex>",
"32-byte SHA-256 hash of the original request as a hex string"
)
.requiredOption(
"--response <value>",
"Validation response score (integer between 0 and 100)"
)
.requiredOption(
"--response-uri <uri>",
"URI pointing to the validation response data"
)
.requiredOption(
"--response-hash <hex>",
"32-byte SHA-256 hash of the response data as a hex string"
)
.option("--tag <tag>", "Classification tag for the validation response", "")
.option(
"--fee <fee>",
'Fee preset ("low", "medium", "high") or micro-STX amount'
)
.option("--sponsored", "Submit as a sponsored transaction", false)
.action(
async (opts: {
requestHash: string;
response: string;
responseUri: string;
responseHash: string;
tag: string;
fee?: string;
sponsored: boolean;
}) => {
try {
const walletManager = getWalletManager();
const account = walletManager.getActiveAccount();
if (!account) {
throw new Error("No active wallet. Please unlock your wallet first.");
}
const response = parseInt(opts.response, 10);
if (isNaN(response) || response < 0 || response > 100) {
throw new Error("--response must be an integer between 0 and 100");
}
const normalizedRequestHash = normalizeHex(opts.requestHash, "--request-hash", 32);
const requestHashBuf = Buffer.from(normalizedRequestHash, "hex");
const normalizedResponseHash = normalizeHex(opts.responseHash, "--response-hash", 32);
const responseHashBuf = Buffer.from(normalizedResponseHash, "hex");
const service = new Erc8004Service(NETWORK);
const feeAmount = opts.fee
? await resolveFee(opts.fee, NETWORK, "contract_call")
: undefined;
const result = await service.submitValidationResponse(
account,
requestHashBuf,
response,
opts.responseUri,
responseHashBuf,
opts.tag || "",
feeAmount,
opts.sponsored
);
printJson({
success: true,
txid: result.txid,
message: `Validation response ${response} submitted for request hash ${opts.requestHash}.`,
response,
responseUri: opts.responseUri,
tag: opts.tag || "",
network: NETWORK,
explorerUrl: getExplorerTxUrl(result.txid, NETWORK),
});
} catch (error) {
handleError(error);
}
}
);
// ---------------------------------------------------------------------------
// get-status
// ---------------------------------------------------------------------------
program
.command("get-status")
.description(
"Get the status of a validation request by its 32-byte request hash. " +
"Returns validator, agent ID, response score, response hash, tag, last update block, " +
"and whether a response has been submitted. Does not require a wallet."
)
.requiredOption(
"--request-hash <hex>",
"32-byte SHA-256 hash of the validation request as a hex string"
)
.action(async (opts: { requestHash: string }) => {
try {
const normalizedHash = normalizeHex(opts.requestHash, "--request-hash", 32);
const requestHashBuf = Buffer.from(normalizedHash, "hex");
const service = new Erc8004Service(NETWORK);
const callerAddress = getCallerAddress();
const status = await service.getValidationStatus(requestHashBuf, callerAddress);
if (!status) {
printJson({
success: false,
requestHash: opts.requestHash,
message: "Validation request not found",
network: NETWORK,
});
return;
}
printJson({
success: true,
requestHash: opts.requestHash,
validator: status.validator,
agentId: status.agentId,
response: status.response,
responseHash: status.responseHash,
tag: status.tag,
lastUpdate: status.lastUpdate,
hasResponse: status.hasResponse,
network: NETWORK,
});
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// get-summary
// ---------------------------------------------------------------------------
program
.command("get-summary")
.description(
"Get the aggregated validation summary for an agent from the ERC-8004 validation registry. " +
"Returns the total validation count and average response score. Does not require a wallet."
)
.requiredOption(
"--agent-id <id>",
"Agent ID to query (non-negative integer)"
)
.action(async (opts: { agentId: string }) => {
try {
const agentId = parseInt(opts.agentId, 10);
if (isNaN(agentId) || agentId < 0) {
throw new Error("--agent-id must be a non-negative integer");
}
const service = new Erc8004Service(NETWORK);
const callerAddress = getCallerAddress();
const summary = await service.getValidationSummary(agentId, callerAddress);
printJson({
success: true,
agentId,
count: summary.count,
avgResponse: summary.avgResponse,
network: NETWORK,
});
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// get-agent-validations
// ---------------------------------------------------------------------------
program
.command("get-agent-validations")
.description(
"Get a paginated list of validation request hashes for an agent from the ERC-8004 validation registry. " +
"Returns request hashes as hex strings. Cursor-based pagination with page size 14. " +
"Does not require a wallet."
)
.requiredOption(
"--agent-id <id>",
"Agent ID to query (non-negative integer)"
)
.option(
"--cursor <cursor>",
"Pagination cursor (non-negative integer, from previous response)"
)
.action(async (opts: { agentId: string; cursor?: string }) => {
try {
const agentId = parseInt(opts.agentId, 10);
if (isNaN(agentId) || agentId < 0) {
throw new Error("--agent-id must be a non-negative integer");
}
let cursor: number | undefined;
if (opts.cursor !== undefined) {
cursor = parseInt(opts.cursor, 10);
if (isNaN(cursor) || cursor < 0) {
throw new Error("--cursor must be a non-negative integer");
}
}
const service = new Erc8004Service(NETWORK);
const callerAddress = getCallerAddress();
const page = await service.getAgentValidations(agentId, callerAddress, cursor);
printJson({
success: true,
agentId,
validations: page.validations,
cursor: page.cursor ?? null,
network: NETWORK,
});
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// get-validator-requests
// ---------------------------------------------------------------------------
program
.command("get-validator-requests")
.description(
"Get a paginated list of validation request hashes submitted to a validator from the ERC-8004 validation registry. " +
"Returns request hashes as hex strings. Cursor-based pagination with page size 14. " +
"Does not require a wallet."
)
.requiredOption(
"--validator <address>",
"Stacks address of the validator to query"
)
.option(
"--cursor <cursor>",
"Pagination cursor (non-negative integer, from previous response)"
)
.action(async (opts: { validator: string; cursor?: string }) => {
try {
let cursor: number | undefined;
if (opts.cursor !== undefined) {
cursor = parseInt(opts.cursor, 10);
if (isNaN(cursor) || cursor < 0) {
throw new Error("--cursor must be a non-negative integer");
}
}
const service = new Erc8004Service(NETWORK);
const callerAddress = getCallerAddress();
const page = await service.getValidatorRequests(opts.validator, callerAddress, cursor);
printJson({
success: true,
validator: opts.validator,
requests: page.requests,
cursor: page.cursor ?? null,
network: NETWORK,
});
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// Parse
// ---------------------------------------------------------------------------
program.parse(process.argv);