@chainreactionom/nano-mcp
Version:
NANO cryptocurrency wallet implementation for MCP with comprehensive testing
172 lines (143 loc) • 5.91 kB
text/typescript
import fetch from 'node-fetch';
import { tools } from 'nanocurrency-web';
import { convert } from 'nanocurrency-web';
import { config } from '../config/global';
import { ConfigValidationResult } from '../types/config';
import { AccountInfo, Block, PendingBlocks } from '../types/nano';
export class NanoTransactions {
private apiUrl: string;
private rpcKey: string;
private gpuKey: string;
private defaultRepresentative: string;
private config: any;
constructor(customConfig?: Partial<{
apiUrl: string;
rpcKey: string;
gpuKey: string;
defaultRepresentative: string;
}>, config?: any) {
const globalConfig = config.getNanoConfig();
this.apiUrl = customConfig?.apiUrl || globalConfig.rpcUrl;
this.rpcKey = customConfig?.rpcKey || globalConfig.rpcKey;
this.gpuKey = customConfig?.gpuKey || globalConfig.gpuKey;
this.defaultRepresentative = customConfig?.defaultRepresentative || globalConfig.defaultRepresentative;
this.config = config;
const errors = config.validateConfig();
if (errors.length > 0) {
throw new Error(`Configuration errors: ${errors.join(', ')}`);
}
}
private async rpcCall(action: string, params: Record<string, any> = {}) {
const response = await fetch(this.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.rpcKey}`
},
body: JSON.stringify({
action,
...params
})
});
if (!response.ok) {
throw new Error(`RPC call failed: ${response.statusText}`);
}
return response.json();
}
async validateConfig(errors: string[]): Promise<ConfigValidationResult> {
if (errors.length > 0) {
throw new Error(`Configuration errors: ${errors.join(', ')}`);
}
return { isValid: true, errors: [], warnings: [] };
}
async generateWork(hash: string): Promise<string> {
const result = await this.makeRequest('work_generate', { hash });
return result.work as string;
}
async getAccountInfo(account: string): Promise<AccountInfo> {
const info = await this.makeRequest('account_info', { account });
return info as AccountInfo;
}
async getPendingBlocks(account: string): Promise<PendingBlocks> {
const pending = await this.makeRequest('pending', { account });
return pending as PendingBlocks;
}
async createOpenBlock(address: string, privateKey: string, sourceBlock: string, sourceAmount: string): Promise<Block> {
const publicKey = tools.getPublicKey(privateKey);
const account = address;
const previous = '0000000000000000000000000000000000000000000000000000000000000000';
const representative = this.defaultRepresentative;
const work = await this.generateWork(previous);
const block = {
type: 'state',
account,
previous,
representative,
balance: sourceAmount,
link: sourceBlock
};
const signature = tools.sign(JSON.stringify(block), privateKey);
return this.rpcCall('process', {
json_block: 'true',
subtype: 'open',
block: {
...block,
signature,
work
}
});
}
async createSendBlock(fromAddress: string, privateKey: string, toAddress: string, amount: string, accountInfo: AccountInfo): Promise<Block> {
const publicKey = tools.getPublicKey(privateKey);
const account = fromAddress; // Use provided address
if (!tools.validateAddress(account)) {
throw new Error('Invalid sender address');
}
const previous = accountInfo.frontier;
const rawAmount = convert('NANO', 'raw', amount);
const balance = (BigInt(accountInfo.balance) - BigInt(rawAmount)).toString();
const work = await this.generateWork(previous);
const block = {
type: 'state',
account,
previous,
representative: accountInfo.representative,
balance,
link: tools.getPublicKey(toAddress)
};
const signature = tools.sign(JSON.stringify(block), privateKey);
return this.rpcCall('process', {
json_block: 'true',
subtype: 'send',
block: {
...block,
signature,
work
}
});
}
async receiveAllPending(address: string, privateKey: string): Promise<Block[]> {
const accountInfo = await this.getAccountInfo(address) as AccountInfo;
const pending = await this.getPendingBlocks(address) as PendingBlocks;
const blocks: Block[] = [];
for (const [hash, details] of Object.entries(pending.blocks)) {
const previous = accountInfo.frontier;
const representative = accountInfo.representative;
const newBalance = (BigInt(accountInfo.balance) + BigInt((details as any).amount)).toString();
const block: Block = {
type: 'state',
account: address,
previous,
representative,
balance: newBalance,
link: hash
};
blocks.push(block);
}
return blocks;
}
private async makeRequest(method: string, params: any): Promise<any> {
// Implementation
return {};
}
}