Skip to content

Commit

Permalink
feat: added wallet class and derive address feature
Browse files Browse the repository at this point in the history
Signed-off-by: theanmolsharma <anmolsharma0234@gmail.com>
  • Loading branch information
theanmolsharma committed Jan 6, 2024
1 parent 442ca78 commit c58cbdd
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ coverage

# test
test/wallet-db
test/wallet
77 changes: 76 additions & 1 deletion src/wallet/wallet.ts
Original file line number Diff line number Diff line change
@@ -1 +1,76 @@
export class Wallet {}
import { DbInterface } from './db';
import { NetworkInterface } from './network';
import { mnemonicToSeedSync } from 'bip39';
import { payments } from 'bitcoinjs-lib';
import BIP32Factory from 'bip32';
import * as ecc from 'tiny-secp256k1';

const bip32 = BIP32Factory(ecc);

export type WalletConfigOptions = {
db: DbInterface;
networkClient: NetworkInterface;
};

export class Wallet {
private readonly db: DbInterface;
private readonly network: NetworkInterface;
private seed: string;
private receiveDepth: number = 0;
private changeDepth: number = 0;

constructor(config: WalletConfigOptions) {
this.db = config.db;
this.network = config.networkClient;
this.seed = mnemonicToSeedSync(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
).toString('hex');
}

async init() {
await this.db.open();
}

async close() {
await this.db.setReceiveDepth(this.receiveDepth);
await this.db.setChangeDepth(this.changeDepth);

await this.db.close();
}

async load(mnemonic?: string) {
if (mnemonic) {
this.seed = mnemonicToSeedSync(mnemonic).toString('hex');
await this.db.setSeed(this.seed);
} else {
this.seed = await this.db.getSeed();
}
}

private deriveAddress(path: string): string {
const master = bip32.fromSeed(Buffer.from(this.seed, 'hex'));
const child = master.derivePath(path);
const { address } = payments.p2wpkh({
pubkey: child.publicKey,
network: this.network.network,
});

return address!;
}

async deriveReceiveAddress(): Promise<string> {
const path = `m/84'/0'/0'/0/${this.receiveDepth}`;
const address = this.deriveAddress(path);
await this.db.saveAddress(address, path);
this.receiveDepth++;
return address;
}

async deriveChangeAddress(): Promise<string> {
const path = `m/84'/0'/0'/1/${this.changeDepth}`;
const address = this.deriveAddress(path);
await this.db.saveAddress(address, path);
this.changeDepth++;
return address;
}
}
49 changes: 49 additions & 0 deletions test/wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { EsploraClient, Wallet, WalletDB } from '../src/wallet';

describe('Wallet', () => {
let wallet: Wallet;

beforeAll(async () => {
const walletDB = new WalletDB({
location: './test/wallet',
});

wallet = new Wallet({
db: walletDB,
networkClient: new EsploraClient({
protocol: 'https',
host: 'blockstream.info',
network: 'main',
}),
});
});

it('should initialise the wallet', async () => {
await wallet.init();
});

it('should load the wallet', async () => {
await wallet.load(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
);
});

it('should derive first receive address', async () => {
const address = await wallet.deriveReceiveAddress();
expect(address).toBe('bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu');
});

it('should derive second receive address', async () => {
const address = await wallet.deriveReceiveAddress();
expect(address).toBe('bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g');
});

it('should derive first change address', async () => {
const address = await wallet.deriveChangeAddress();
expect(address).toBe('bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el');
});

afterAll(async () => {
await wallet.close();
});
});

0 comments on commit c58cbdd

Please sign in to comment.