@bittensormcp/sign
v0.6.0
Published
Self-custody signing helper for BittensorMCP — your private key never leaves your machine
Maintainers
Readme
@bittensormcp/sign
Self-custody signing helper for BittensorMCP.
Agent? Start here: https://bittensormcp.com/llms.txt — the complete zero-to-premium onboarding (wallet generation, funding, auth, MCP connection, billing) as one runnable script.
Implements the A2 two-step protocol so your AI agent can stake, unstake, and transfer TAO without your private key ever leaving your machine.
How it works
- Agent calls a write tool (e.g.
bittensor_stake_add) → server returns anUNSIGNED_PAYLOAD - This package signs the payload locally with your sr25519 keypair
- The signature is sent to
bittensor_submit_signed→ server submits the extrinsic
Your private key never leaves your process. The signer callback receives payload bytes; your keypair stays with you.
Installation
npm install @bittensormcp/signUsage
import { signAndSubmit } from '@bittensormcp/sign';
import { Keyring } from '@polkadot/keyring';
const keyring = new Keyring({ type: 'sr25519' });
const keypair = keyring.addFromMnemonic('your twelve word mnemonic ...');
const result = await signAndSubmit({
endpoint: 'https://bittensormcp.com',
token: walletJwt, // from /api/auth/verify
tool: 'bittensor_stake_add',
args: {
hotkey: '5G1YourHotkeyHere',
amount: 0.01,
netuid: 21,
},
signer: (payload) => keypair.sign(payload),
});
console.log('txHash:', result.txHash);Supported tools
All 8 BittensorMCP write tools: bittensor_stake_add, bittensor_stake_remove,
bittensor_stake_move, bittensor_transfer, bittensor_register, bittensor_serve_axon,
bittensor_weights_set, bittensor_weights_commit.
Activating premium programmatically
Write tools require a premium subscription (0.1 TAO/month). Agents activate it without a browser:
import { activatePremium } from '@bittensormcp/sign';
const result = await activatePremium({ endpoint, token, signer: wallet.sign });
// → { subscriptionValidUntil, creditedDays, txHash }Resuming a wallet across runs
import { walletFromMnemonic } from '@bittensormcp/sign';
const wallet = await walletFromMnemonic(savedMnemonic); // same shape as generateWallet()Getting a wallet JWT
// 1. Get a challenge nonce
const { nonce } = await fetch(`${endpoint}/api/auth/challenge`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ss58: keypair.address, domain: 'bittensormcp.com' }),
}).then(r => r.json());
// 2. Sign the challenge
const message = `bittensormcp-auth:${nonce}:bittensormcp.com`;
const signature = u8aToHex(keypair.sign(stringToU8a(message)));
// 3. Exchange for a JWT
const { token } = await fetch(`${endpoint}/api/auth/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ss58: keypair.address, nonce, signature }),
}).then(r => r.json());Generating a wallet (no human-held keypair needed)
If your agent doesn't already have a coldkey, generate one locally — nothing leaves your process:
import { generateWallet, authenticate, signAndSubmit } from '@bittensormcp/sign';
const wallet = await generateWallet();
console.log(`Fund this address before writes: ${wallet.ss58}`);
// wallet.mnemonic is yours to store — this package never persists or sends it
const { token } = await authenticate({
endpoint: 'https://bittensormcp.com',
ss58: wallet.ss58,
signer: wallet.sign,
});
const result = await signAndSubmit({
endpoint: 'https://bittensormcp.com',
token,
tool: 'bittensor_stake_add',
args: { hotkey: '5G1...', amount: 0.01, netuid: 21 },
signer: wallet.sign,
});Wallet generation and authentication are fully agentic — no human needs to touch a browser extension. Funding the address with TAO is the one step that always needs a value source outside the system; that's not a limitation of this package, it's what self-custody means.
Security
- Zero postinstall scripts
@polkadot/util-cryptois the only runtime dependency, used solely bygenerateWallet()(lazy-imported —signAndSubmit/authenticatealone don't pull it in)- No network calls except to your configured
endpoint - Your private key is never passed to this package — only the
signercallback, andgenerateWallet()'s keypair never leaves the closure it's generated in
