@chainreactionom/nano-mcp
Version:
NANO cryptocurrency wallet implementation for MCP with comprehensive testing
378 lines (319 loc) • 14 kB
JavaScript
import { wallet, tools, block } from 'nanocurrency-web';
import { Logger } from './logger.js';
import path from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';
// Load environment variables
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function testWalletTransfer() {
// Initialize logger
const logger = new Logger(path.join(__dirname, 'logs'));
try {
logger.log('TEST_START', 'Starting Wallet Transfer Test');
// Step 1: Create first wallet
logger.log('WALLET1_CREATE', 'Creating first wallet');
const wallet1 = await wallet.generate();
const wallet1Account = wallet1.accounts[0];
logger.log('WALLET1_CREATED', {
address: wallet1Account.address,
publicKey: wallet1Account.publicKey
});
console.log('\n=== WALLET 1 ADDRESS ===');
console.log('Please send exactly 0.00001 NANO to this address:');
console.log(wallet1Account.address);
console.log('Waiting for funds...');
// Step 2: Wait and check for incoming transaction
logger.log('WAITING_FOR_FUNDS', 'Waiting for incoming transaction');
let funded = false;
let startTime = Date.now();
const timeoutMs = 60000; // 1 minute timeout
while (!funded && (Date.now() - startTime) < timeoutMs) {
try {
const response = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'account_info',
account: wallet1Account.address
})
});
const data = await response.json();
if (data.balance) {
const balanceNano = tools.convert(data.balance, 'RAW', 'NANO');
logger.log('FUNDS_RECEIVED', {
account: wallet1Account.address,
balance: balanceNano
});
funded = true;
} else {
await sleep(5000); // Check every 5 seconds
process.stdout.write('.');
}
} catch (error) {
logger.logError('CHECK_BALANCE_ERROR', error);
await sleep(5000);
process.stdout.write('x');
}
}
if (!funded) {
throw new Error('Timeout waiting for funds');
}
// Step 3: Create second wallet
logger.log('WALLET2_CREATE', 'Creating second wallet');
const wallet2 = await wallet.generate();
const wallet2Account = wallet2.accounts[0];
logger.log('WALLET2_CREATED', {
address: wallet2Account.address,
publicKey: wallet2Account.publicKey
});
// Step 4: Get pending blocks for wallet1
logger.log('CHECKING_PENDING', 'Checking pending blocks for wallet 1');
const pendingResponse = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'pending',
account: wallet1Account.address,
count: '1',
source: true
})
});
const pendingData = await pendingResponse.json();
if (!pendingData.blocks || Object.keys(pendingData.blocks).length === 0) {
throw new Error('No pending blocks found');
}
// Step 5: Receive the pending block
logger.log('RECEIVING_BLOCK', 'Receiving pending block in wallet 1');
const [blockHash, blockInfo] = Object.entries(pendingData.blocks)[0];
const receiveBlockData = {
walletBalanceRaw: '0',
toAddress: wallet1Account.address,
representativeAddress: process.env.REPRESENTATIVE || 'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou',
frontier: '0000000000000000000000000000000000000000000000000000000000000000',
transactionHash: blockHash,
amountRaw: blockInfo.amount
};
const signedReceiveBlock = block.receive(receiveBlockData, wallet1Account.privateKey);
const processReceiveResponse = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'process',
json_block: 'true',
subtype: 'receive',
block: signedReceiveBlock
})
});
const receiveResult = await processReceiveResponse.json();
logger.log('BLOCK_RECEIVED', {
hash: receiveResult.hash
});
// Step 6: Send to wallet2
logger.log('SENDING_TO_WALLET2', 'Sending funds from wallet 1 to wallet 2');
const accountInfoResponse = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'account_info',
account: wallet1Account.address
})
});
const accountInfo = await accountInfoResponse.json();
const sendBlockData = {
walletBalanceRaw: accountInfo.balance,
fromAddress: wallet1Account.address,
toAddress: wallet2Account.address,
representativeAddress: accountInfo.representative,
frontier: accountInfo.frontier,
amountRaw: tools.convert('0.00001', 'NANO', 'RAW')
};
const signedSendBlock = block.send(sendBlockData, wallet1Account.privateKey);
const processSendResponse = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'process',
json_block: 'true',
subtype: 'send',
block: signedSendBlock
})
});
const sendResult = await processSendResponse.json();
logger.log('SENT_TO_WALLET2', {
hash: sendResult.hash,
from: wallet1Account.address,
to: wallet2Account.address,
amount: '0.00001'
});
// Step 7: Receive in wallet2
logger.log('RECEIVING_IN_WALLET2', 'Receiving funds in wallet 2');
await sleep(5000); // Wait for transaction to propagate
const pending2Response = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'pending',
account: wallet2Account.address,
count: '1',
source: true
})
});
const pending2Data = await pending2Response.json();
if (!pending2Data.blocks || Object.keys(pending2Data.blocks).length === 0) {
throw new Error('No pending blocks found for wallet 2');
}
const [blockHash2, blockInfo2] = Object.entries(pending2Data.blocks)[0];
const receiveBlock2Data = {
walletBalanceRaw: '0',
toAddress: wallet2Account.address,
representativeAddress: process.env.REPRESENTATIVE || 'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou',
frontier: '0000000000000000000000000000000000000000000000000000000000000000',
transactionHash: blockHash2,
amountRaw: blockInfo2.amount
};
const signedReceiveBlock2 = block.receive(receiveBlock2Data, wallet2Account.privateKey);
const processReceive2Response = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'process',
json_block: 'true',
subtype: 'receive',
block: signedReceiveBlock2
})
});
const receive2Result = await processReceive2Response.json();
logger.log('RECEIVED_IN_WALLET2', {
hash: receive2Result.hash
});
// Step 8: Send back to wallet1
logger.log('SENDING_BACK_TO_WALLET1', 'Sending funds back to wallet 1');
const account2InfoResponse = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'account_info',
account: wallet2Account.address
})
});
const account2Info = await account2InfoResponse.json();
const sendBack2Data = {
walletBalanceRaw: account2Info.balance,
fromAddress: wallet2Account.address,
toAddress: wallet1Account.address,
representativeAddress: account2Info.representative,
frontier: account2Info.frontier,
amountRaw: tools.convert('0.00001', 'NANO', 'RAW')
};
const signedSendBack2Block = block.send(sendBack2Data, wallet2Account.privateKey);
const processSendBack2Response = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'process',
json_block: 'true',
subtype: 'send',
block: signedSendBack2Block
})
});
const sendBack2Result = await processSendBack2Response.json();
logger.log('SENT_TO_WALLET1', {
hash: sendBack2Result.hash,
from: wallet2Account.address,
to: wallet1Account.address,
amount: '0.00001'
});
// Step 9: Receive back in wallet1
logger.log('RECEIVING_BACK_IN_WALLET1', 'Receiving funds back in wallet 1');
await sleep(5000); // Wait for transaction to propagate
const pending3Response = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'pending',
account: wallet1Account.address,
count: '1',
source: true
})
});
const pending3Data = await pending3Response.json();
if (!pending3Data.blocks || Object.keys(pending3Data.blocks).length === 0) {
throw new Error('No pending blocks found for final receive');
}
const [blockHash3, blockInfo3] = Object.entries(pending3Data.blocks)[0];
const receiveBlock3Data = {
walletBalanceRaw: accountInfo.balance,
toAddress: wallet1Account.address,
representativeAddress: accountInfo.representative,
frontier: accountInfo.frontier,
transactionHash: blockHash3,
amountRaw: blockInfo3.amount
};
const signedReceiveBlock3 = block.receive(receiveBlock3Data, wallet1Account.privateKey);
const processReceive3Response = await fetch(process.env.NODE_URL || 'https://proxy.nanos.cc/proxy', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'process',
json_block: 'true',
subtype: 'receive',
block: signedReceiveBlock3
})
});
const receive3Result = await processReceive3Response.json();
logger.log('RECEIVED_BACK_IN_WALLET1', {
hash: receive3Result.hash
});
// Test Summary
const testResults = {
total: 9,
passed: 9,
failed: 0,
duration: Date.now() - startTime,
wallet1: {
address: wallet1Account.address,
privateKey: wallet1Account.privateKey
},
wallet2: {
address: wallet2Account.address
}
};
logger.summarize(testResults);
logger.log('TEST_COMPLETE', 'Wallet Transfer Test completed successfully');
} catch (error) {
logger.logError('TEST_FAILURE', error);
throw error;
}
}
// Run the test
testWalletTransfer().catch(error => {
console.error('Test failed:', error);
process.exit(1);
});