Activities/Wallet Refactor

This commit is contained in:
Jim Hamill
2025-04-18 05:17:20 +00:00
parent 408d3b8d8c
commit 7e4508cf5a
18 changed files with 706 additions and 1111 deletions
+70 -114
View File
@@ -1,29 +1,21 @@
import { BaseWallet } from './base-wallet.js';
import { type WalletTransactions, type TransactionTemplate } from './types.js';
import { type TransactionTemplate, type WalletDependencies } from './types.js';
import { TransactionBuilder } from './transaction-builder.js';
import { type WalletServices } from './types.js';
import { type BaseBlockchain, type BlockchainTransaction } from '../blockchain';
import { type BaseStore, Activities, StoreInMemory } from '../storage';
import {
type BaseBlockchain,
type BlockchainTransaction,
type BlockchainUTXOs,
} from '../blockchain';
import { type BaseStore, StoreInMemory } from '../storage';
import {
Address,
ExtMap,
PrivateKey,
calculateBalanceSats,
calculateBalanceTokens,
encodeExtendedJson,
} from '@xocash/primitives';
import {
type Input,
binToHex,
hash256,
sha256,
utf8ToBin,
walletTemplateP2pkhNonHd,
walletTemplateToCompilerBCH,
@@ -34,12 +26,6 @@ export type WalletP2PKHGenesisData = {
privateKey: string;
};
export type WalletP2PKHEvents = {
transactionsUpdated: WalletTransactions;
unspentsUpdated: BlockchainUTXOs;
stateUpdated: string | null;
};
/**
* @example
* ```ts
@@ -50,58 +36,28 @@ export type WalletP2PKHEvents = {
* await walletP2PKH.startMonitoring();
* ```
*/
export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
// Internal State.
public isStarted = false;
public status: string | null = null;
export class WalletP2PKH extends BaseWallet {
static async from<T extends typeof WalletP2PKH>(
this: T,
services: WalletServices,
genesisData?: WalletP2PKHGenesisData,
dependencies: WalletDependencies,
genesisData: WalletP2PKHGenesisData,
) {
// Assign in memory stores as defaults in case otherwise not provided.
const servicesFinal = {
const dependenciesFinal = {
...{
store: new StoreInMemory(),
activities: await Activities.fromStores(StoreInMemory.from()),
cache: new StoreInMemory(),
},
...services,
...dependencies,
};
// If we are attempting to create a new wallet...
if (genesisData) {
// Ensure that the provided store does not already contain data.
if (await servicesFinal.store.get('genesis')) {
throw new Error(`Provided store already contains data`);
}
// Store the wallet genesis data.
await servicesFinal.store.set<WalletP2PKHGenesisData>(
'genesis',
genesisData,
);
}
// Retrieve our genesis data and extract the private key.
const retrievedGenesisData =
await servicesFinal.store.getOrFail<WalletP2PKHGenesisData>('genesis');
// Get the Private Key as hex.
const privateKey = PrivateKey.fromHex(retrievedGenesisData.privateKey);
// Return a new instance of Wallet P2PKH.
return new this(
servicesFinal.blockchain,
servicesFinal.store,
servicesFinal.cache,
privateKey,
) as InstanceType<T>;
return new this(dependenciesFinal, genesisData) as InstanceType<T>;
}
static fromBytes<T extends typeof WalletP2PKH>(
this: T,
services: WalletServices,
services: WalletDependencies,
privateKeyBytes: Uint8Array,
) {
// Create a Private Key entity from the provided bytes.
@@ -116,7 +72,7 @@ export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
static fromHex<T extends typeof WalletP2PKH>(
this: T,
services: WalletServices,
services: WalletDependencies,
privateKeyHex: string,
) {
// Create a Private Key entity from the provided bytes.
@@ -131,7 +87,7 @@ export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
static fromWIF<T extends typeof WalletP2PKH>(
this: T,
services: WalletServices,
services: WalletDependencies,
privateKeyWif: string,
) {
// Create a Private Key entity from the provided bytes.
@@ -146,7 +102,7 @@ export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
static generateRandom<T extends typeof WalletP2PKH>(
this: T,
services: WalletServices,
services: WalletDependencies,
) {
// Create a Private Key entity generated from random bytes.
const privateKey = PrivateKey.generateRandom();
@@ -158,17 +114,37 @@ export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
});
}
// Dependencies.
public readonly activities: Activities;
public readonly blockchain: BaseBlockchain;
public readonly cache: BaseStore;
// Genesis Data.
public readonly genesisData: WalletP2PKHGenesisData;
// Optimizations/Simplifications.
public readonly privateKey: PrivateKey;
// Mutable State.
public isStarted = false;
public status: string | null = null;
constructor(
// Dependencies.
public blockchain: BaseBlockchain,
// Persistent Storage.
public store: BaseStore,
// Cache.
public cache: BaseStore,
// Properties.
public privateKey: PrivateKey,
dependencies: Required<WalletDependencies>,
genesisData: WalletP2PKHGenesisData,
) {
super();
// Assign our dependencies.
this.activities = dependencies.activities;
this.blockchain = dependencies.blockchain;
this.cache = dependencies.cache;
// Assign our Genesis Data.
this.genesisData = genesisData;
// Optimizations/Simplifications.
this.privateKey = PrivateKey.fromHex(genesisData.privateKey);
}
async getId() {
@@ -180,6 +156,17 @@ export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
);
}
async deriveKey(keyName: string): Promise<Uint8Array> {
// Hash the genesis data using Hash256 and the key ("WalletID") for this derivation using Hash256.
const genesisDataHashed = hash256(
utf8ToBin(JSON.stringify(this.genesisData)),
);
const keyHashed = hash256(utf8ToBin(keyName));
// Combine the hashes and do one final hash for good measure.
return hash256(new Uint8Array([...genesisDataHashed, ...keyHashed]));
}
async getEncryptionKey() {
// NOTE: Use the cache to optimize future calls.
return await this.cache.getOrSet('encryptionKey', async () =>
@@ -194,6 +181,10 @@ export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
);
}
async getActivities(): Promise<Activities> {
return this.activities;
}
async start() {
// Do nothing if already started.
if (this.isStarted) {
@@ -396,22 +387,15 @@ export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
// Fetch unspents that are usable by this wallet.
const unspents = await this.getUnspentDirectives();
// Create an instance of our transaction builder.
const txBuilder = new TransactionBuilder(txTemplateFinal, unspents);
// Append necessary unspents to satisfy amount requirements.
// NOTE: The order here is very important.
// We must deal with tokens first and satoshis last.
// The reason is that the change output will be determined based on transaction fee.
// TODO: Consider replacing this with just a single util function.
txBuilder
.addTokensFromUnspents()
.addTokenChangeOutputs(changeLockingBytecode)
.addSatoshisFromUnspents()
.addSatoshiChangeOutput(changeLockingBytecode);
// Build the transaction.
const tx = TransactionBuilder.build(
txTemplateFinal,
unspents,
changeLockingBytecode,
);
// Generate the transaction.
const generatedTxBytes = txBuilder.toBytes();
const generatedTxBytes = tx.toBytes();
return generatedTxBytes;
}
@@ -434,34 +418,6 @@ export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
this.emit('stateUpdated', this.status);
}
}
async export() {
const allData = await this.store.all();
const asJson = encodeExtendedJson(allData);
return asJson;
}
async addActivity<T>(activity: T) {
// Generate an Activity ID.
// TODO: Look more into how LibAuth intends to do this.
const activityId = Date.now().toString();
// Store the data.
await this.store.set<T>(activityId, activity);
}
private async deriveKey(keyName: string): Promise<Uint8Array> {
// Get the genesis data for this wallet.
// TODO: This needs to change!
const genesisData = await this.store.getOrFail('genesis');
// Hash the genesis data using Hash256 and the key ("WalletID") for this derivation using Hash256.
const genesisDataHashed = hash256(utf8ToBin(JSON.stringify(genesisData)));
const keyHashed = hash256(utf8ToBin(keyName));
// Combine the hashes and do one final hash for good measure.
return hash256(new Uint8Array([...genesisDataHashed, ...keyHashed]));
}
}
//-----------------------------------------------------------------------------
@@ -469,13 +425,13 @@ export class WalletP2PKH extends BaseWallet<WalletP2PKHEvents> {
//-----------------------------------------------------------------------------
export type WalletP2PKHFactory<T extends WalletP2PKH = WalletP2PKH> = (
services: WalletServices,
genesisData?: WalletP2PKHGenesisData,
dependencies: WalletDependencies,
genesisData: WalletP2PKHGenesisData,
) => Promise<T>;
export async function useWalletP2PKH<T extends WalletP2PKH = WalletP2PKH>(
services: WalletServices,
genesisData?: WalletP2PKHGenesisData,
dependencies: WalletDependencies,
genesisData: WalletP2PKHGenesisData,
): Promise<T> {
return (await WalletP2PKH.from(services, genesisData)) as T;
return (await WalletP2PKH.from(dependencies, genesisData)) as T;
}