Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QiHDWallet scan unit test #377

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions examples/transactions/send-qi-paymentcode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
const quais = require('../../lib/commonjs/quais');
require('dotenv').config();

async function main() {
// Create provider
const options = {usePathing: false};
const provider = new quais.JsonRpcProvider(process.env.RPC_URL, undefined, options);

// Create wallet and connect to provider
console.log(process.env.RPC_URL)
const aliceMnemonic = quais.Mnemonic.fromPhrase(process.env.MNEMONIC);
const aliceWallet = quais.QiHDWallet.fromMnemonic(aliceMnemonic);
aliceWallet.connect(provider);

// Get Alice payment code
const alicePaymentCode = aliceWallet.getPaymentCode(0);
console.log("Alice payment code: ", alicePaymentCode);

// Create Bob wallet
const BOB_MNEMONIC = "innocent perfect bus miss prevent night oval position aspect nut angle usage expose grace juice";
const bobMnemonic = quais.Mnemonic.fromPhrase(BOB_MNEMONIC);
const bobWallet = quais.QiHDWallet.fromMnemonic(bobMnemonic);
bobWallet.connect(provider);

// Get Bob payment code
const bobPaymentCode = bobWallet.getPaymentCode(0);
console.log("Bob payment code: ", bobPaymentCode);

// Open channel
aliceWallet.openChannel(bobPaymentCode);
bobWallet.openChannel(alicePaymentCode);

// Scan Alice wallet
console.log("...scanning alice wallet");
await aliceWallet.scan(quais.Zone.Cyprus1);

// log alice change wallet addresses
console.log("Alice change wallet addresses: ", aliceWallet.getChangeAddressesForZone(quais.Zone.Cyprus1).map(a => a.address));
// log alice external wallet addresses
console.log("Alice external wallet addresses: ", aliceWallet.getAddressesForZone(quais.Zone.Cyprus1).map(a => a.address));

// Scan Bob wallet
console.log("...scanning bob wallet");
await bobWallet.scan(quais.Zone.Cyprus1);

// Get Alice initial balance
console.log("...getting alice initial balance");
const aliceInitialBalance = await aliceWallet.getBalanceForZone(quais.Zone.Cyprus1);
console.log("Alice initial balance: ", aliceInitialBalance);

// Get Bob initial balance
console.log("...getting bob initial balance");
const bobInitialBalance = await bobWallet.getBalanceForZone(quais.Zone.Cyprus1);
console.log("Bob initial balance: ", bobInitialBalance);

// Send Qi
console.log("...sending qi to Bob");
const amountToSendToBob = 25000;
const tx = await aliceWallet.sendTransaction(bobPaymentCode, amountToSendToBob, quais.Zone.Cyprus1, quais.Zone.Cyprus1);
console.log("... Alice transaction sent. Waiting for receipt...");

// Wait for tx to be mined
const txReceipt = await tx.wait();
console.log("Alice's transaction receipt: ", txReceipt);

// Sync wallets
console.log("...syncing wallets");
await aliceWallet.sync(quais.Zone.Cyprus1);
await bobWallet.sync(quais.Zone.Cyprus1);

// Get Alice updated balance
console.log("...getting alice updated balance");
const aliceUpdatedBalance = await aliceWallet.getBalanceForZone(quais.Zone.Cyprus1);
console.log("Alice updated balance: ", aliceUpdatedBalance);

// Get Bob updated balance
console.log("...getting bob updated balance");
const bobUpdatedBalance = await bobWallet.getBalanceForZone(quais.Zone.Cyprus1);
console.log("Bob updated balance: ", bobUpdatedBalance);

// Bob sends Qi back to Alice
console.log("...sending qi back to Alice");
const amountToSendToAlice = 10000;
const tx2 = await bobWallet.sendTransaction(alicePaymentCode, amountToSendToAlice, quais.Zone.Cyprus1, quais.Zone.Cyprus1);
console.log("... Bob sent transaction. Waiting for receipt...");

// Wait for tx2 to be mined
const tx2Receipt = await tx2.wait();
console.log("Bob's transaction receipt: ", tx2Receipt);

// Sync wallets
await aliceWallet.sync(quais.Zone.Cyprus1);
await bobWallet.sync(quais.Zone.Cyprus1);

// Get Alice updated balance
console.log("...getting alice updated balance");
const aliceUpdatedBalance2 = await aliceWallet.getBalanceForZone(quais.Zone.Cyprus1);
console.log("Alice updated balance: ", aliceUpdatedBalance2);

// Get Bob updated balance
console.log("...getting bob updated balance");
const bobUpdatedBalance2 = await bobWallet.getBalanceForZone(quais.Zone.Cyprus1);
console.log("Bob updated balance: ", bobUpdatedBalance2);

}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
77 changes: 45 additions & 32 deletions src/_tests/unit/mockProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export class MockProvider implements Provider {
private _transactions: Map<string, TransactionResponse> = new Map();
private _blocks: Map<string, Block> = new Map();
private _balances: Map<string, bigint> = new Map();
private _lockedBalances: Map<string, bigint> = new Map();
private _outpoints: Map<string, Array<Outpoint>> = new Map();
private _eventHandlers: Map<
string,
Array<{
Expand All @@ -54,11 +56,27 @@ export class MockProvider implements Provider {
this._transactions.set(hash.toLowerCase(), tx);
}

public setOutpoints(address: string, outpoints: Array<Outpoint>): void {
this._outpoints.set(address.toLowerCase(), outpoints);
}

// Implementation of Provider interface methods
async getNetwork(): Promise<Network> {
return this._network;
}

public setBlock(key: string, block: Block): void {
this._blocks.set(key, block);
}

async getBlock(shard: Shard, blockHashOrBlockTag: BlockTag | string, prefetchTxs?: boolean): Promise<null | Block> {
if (typeof blockHashOrBlockTag === 'string') {
return this._blocks.get(blockHashOrBlockTag) ?? null;
}
// Handle block tag (latest, pending, etc.)
throw new Error('Method not implemented.');
}

async getBlockNumber(shard: Shard): Promise<number> {
return this._blockNumber;
}
Expand Down Expand Up @@ -87,17 +105,8 @@ export class MockProvider implements Provider {
throw new Error('Method not implemented.');
}

async getOutpointsByAddress(): Promise<Array<Outpoint>> {
// TODO: Implement
throw new Error('Method not implemented.');
}

async getBlock(shard: Shard, blockHashOrBlockTag: BlockTag | string, prefetchTxs?: boolean): Promise<null | Block> {
if (typeof blockHashOrBlockTag === 'string') {
return this._blocks.get(blockHashOrBlockTag) ?? null;
}
// Handle block tag (latest, pending, etc.)
throw new Error('Method not implemented.');
async getOutpointsByAddress(address: AddressLike): Promise<Array<Outpoint>> {
return this._outpoints.get(address.toString().toLowerCase()) ?? [];
}

async estimateFeeForQi(tx: QiPerformActionTransaction): Promise<bigint> {
Expand Down Expand Up @@ -233,64 +242,68 @@ export class MockProvider implements Provider {
throw new Error('Method not implemented.');
}

async getLockedBalance(): Promise<bigint> {
throw new Error('Method not implemented.');
public setLockedBalance(address: AddressLike, balance: bigint): void {
this._lockedBalances.set(address.toString().toLowerCase(), balance);
}

async getLockedBalance(address: AddressLike): Promise<bigint> {
return this._lockedBalances.get(address.toString().toLowerCase()) ?? BigInt(0);
}
async getTransactionCount(): Promise<number> {
throw new Error('Method not implemented.');
throw new Error('getTransactionCount: Method not implemented.');
}
async getCode(): Promise<string> {
throw new Error('Method not implemented.');
throw new Error('getCode: Method not implemented.');
}
async getStorage(): Promise<string> {
throw new Error('Method not implemented.');
throw new Error('getStorage: Method not implemented.');
}
async estimateGas(): Promise<bigint> {
throw new Error('Method not implemented.');
throw new Error('estimateGas: Method not implemented.');
}
async createAccessList(): Promise<AccessList> {
throw new Error('Method not implemented.');
throw new Error('createAccessList: Method not implemented.');
}
async call(): Promise<string> {
throw new Error('Method not implemented.');
throw new Error('call: Method not implemented.');
}
async getTransaction(): Promise<null | TransactionResponse> {
throw new Error('Method not implemented.');
throw new Error('getTransaction: Method not implemented.');
}
async getTransactionReceipt(): Promise<null | TransactionReceipt> {
throw new Error('Method not implemented.');
throw new Error('getTransactionReceipt: Method not implemented.');
}
async getTransactionResult(): Promise<null | string> {
throw new Error('Method not implemented.');
throw new Error('getTransactionResult: Method not implemented.');
}
async getLogs(): Promise<Array<Log>> {
throw new Error('Method not implemented.');
throw new Error('getLogs: Method not implemented.');
}
async waitForTransaction(): Promise<null | TransactionReceipt> {
throw new Error('Method not implemented.');
throw new Error('waitForTransaction: Method not implemented.');
}
async waitForBlock(): Promise<Block> {
throw new Error('Method not implemented.');
throw new Error('waitForBlock: Method not implemented.');
}
async getProtocolExpansionNumber(): Promise<number> {
throw new Error('Method not implemented.');
throw new Error('getProtocolExpansionNumber: Method not implemented.');
}
async getTxPoolContent(zone: Zone): Promise<txpoolContentResponse> {
throw new Error('Method not implemented.');
throw new Error('getTxPoolContent: Method not implemented.');
}
async txPoolInspect(zone: Zone): Promise<txpoolInspectResponse> {
throw new Error('Method not implemented.');
throw new Error('txPoolInspect: Method not implemented.');
}
async getQiRateAtBlock(): Promise<bigint> {
throw new Error('Method not implemented.');
throw new Error('getQiRateAtBlock: Method not implemented.');
}
async getLatestQiRate(): Promise<bigint> {
throw new Error('Method not implemented.');
throw new Error('getLatestQiRate: Method not implemented.');
}
async getQuaiRateAtBlock(): Promise<bigint> {
throw new Error('Method not implemented.');
throw new Error('getQuaiRateAtBlock: Method not implemented.');
}
async getLatestQuaiRate(): Promise<bigint> {
throw new Error('Method not implemented.');
throw new Error('getLatestQuaiRate: Method not implemented.');
}
}
109 changes: 109 additions & 0 deletions src/_tests/unit/qihdwallet-scan.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import assert from 'assert';
import { loadTests } from '../utils.js';
import { Mnemonic, QiHDWallet, Zone, OutpointInfo, Block, QiAddressInfo } from '../../index.js';
import { Outpoint } from '../../transaction/utxo.js';
import { MockProvider } from './mockProvider.js';

process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});

import dotenv from 'dotenv';
const env = process.env.NODE_ENV || 'development';
dotenv.config({ path: `.env.${env}` });
dotenv.config({ path: `.env`, override: false });

interface ScanTestCase {
name: string;
mnemonic: string;
provider_outpoints: Array<{
address: string;
outpoints: Array<Outpoint>;
}>;
provider_locked_balance: Array<{
address: string;
balance: number;
}>;
provider_balance: Array<{
address: string;
balance: number;
}>;
provider_blocks: Array<{
key: string;
block: Block;
}>;
expected_external_addresses: Array<QiAddressInfo>;
expected_change_addresses: Array<QiAddressInfo>;
expected_outpoints_info: Array<OutpointInfo>;
expected_balance: number;
}

describe('QiHDWallet scan', async function () {
const tests = loadTests<ScanTestCase>('qi-wallet-scan');

for (const test of tests) {
this.timeout(1200000);

const mockProvider = new MockProvider();

// set the provider outpoints
for (const outpoint of test.provider_outpoints) {
mockProvider.setOutpoints(outpoint.address, outpoint.outpoints);
}

// set the provider blocks
for (const block of test.provider_blocks) {
mockProvider.setBlock(block.key, block.block);
}

// set the provider locked balace
for (const lockedBalance of test.provider_locked_balance) {
mockProvider.setLockedBalance(lockedBalance.address, BigInt(lockedBalance.balance));
}

// set the provider balance
for (const balance of test.provider_balance) {
mockProvider.setBalance(balance.address, BigInt(balance.balance));
}

const mnemonic = Mnemonic.fromPhrase(test.mnemonic);
const wallet = QiHDWallet.fromMnemonic(mnemonic);
wallet.connect(mockProvider);
it('it scans the wallet with no errors', async function () {
try {
await wallet.scan(Zone.Cyprus1);
assert.ok(true, '====> TESTING: scan completed');
} catch (error) {
console.error('====> TESTING: error: ', error);
assert.fail('====> TESTING: error: ', error);
}
});
it('validates expected external addresses', async function () {
const externalAddresses = wallet.getAddressesForZone(Zone.Cyprus1);
const sortedExternalAddresses = externalAddresses.sort((a, b) => a.address.localeCompare(b.address));
const sortedExpectedExternalAddresses = test.expected_external_addresses.sort((a, b) =>
a.address.localeCompare(b.address),
);
assert.deepEqual(sortedExternalAddresses, sortedExpectedExternalAddresses);
});

it('validates expected change addresses', async function () {
const changeAddresses = wallet.getChangeAddressesForZone(Zone.Cyprus1);
const sortedChangeAddresses = changeAddresses.sort((a, b) => a.address.localeCompare(b.address));
const sortedExpectedChangeAddresses = test.expected_change_addresses.sort((a, b) =>
a.address.localeCompare(b.address),
);
assert.deepEqual(sortedChangeAddresses, sortedExpectedChangeAddresses);
});

it('validates wallet balance', async function () {
const balance = await wallet.getBalanceForZone(Zone.Cyprus1);
assert.equal(balance.toString(), test.expected_balance.toString());
});

it('validates expected outpoints info', async function () {
assert.deepEqual(wallet.getOutpoints(Zone.Cyprus1), test.expected_outpoints_info);
});
}
});
Loading
Loading