LibAuth Comparison

This commit is contained in:
Jim Hamill
2025-02-03 11:40:38 +00:00
parent 14506d4ca6
commit f2b182c882
6 changed files with 1152 additions and 72 deletions
-6
View File
@@ -109,12 +109,6 @@ export abstract class BaseWallet<
/**
* Get the address that this wallet should receive to.
*
* @remarks
*
* Not all wallets will have an address (e.g. a HD Wallet), but should still be able to return an address that:
* a) Change will be sent to.
* b) Funds should be received to.
*/
abstract getReceivingAddress(): Promise<Address>;
+306 -3
View File
@@ -1,3 +1,33 @@
import { BaseWallet } from './base-wallet.js';
import { type TransactionTemplate } from './types.js';
import { TransactionBuilder } from './transaction-builder.js';
import { type WalletServices } from './types.js';
import {
WalletP2PKH,
WalletP2PKHFactory,
useWalletP2PKH,
} from './wallet-p2pkh.js';
import {
type BaseBlockchain,
type BlockchainTransactions,
type BlockchainUTXOs,
} from '../blockchain';
import {
type BaseStore,
type StoreValueProxy,
StoreInMemory,
} from '../storage';
import {
type TokenBalances,
HDPrivateNode,
ExtMap,
calculateBalanceSats,
calculateBalanceTokens,
encodeExtendedJson,
} from '@xocash/primitives';
/**
* Placeholder.
*
@@ -7,8 +37,281 @@
* See remarks on Storage - it's semi-related and I'm still playing with approaches.
*/
// https://github.com/bitjson/libauth/blob/d3d610de088cd98f5c391fe62dce81874eb5d95a/src/lib/engine/types/wallet-set-types.ts
export class WalletSet {
constructor() {
console.log('TODO');
/*
export type WalletSetGenesisData = {
type: 'WalletSet';
} & ({ seed: string } | { mnemonic: string } | { xpriv: string });
export type WalletSetEvents = {
shouldMonitorUpdated: boolean;
stateUpdated: string | null;
};
export class WalletSet extends BaseWallet<WalletSetEvents> {
// Mutable State.
public shouldMonitor = false;
// Store values that are proxied for convenience/clarity.
// NOTE: We define these for clarity so that we can see the state stored with this wallet.
public genesisData: StoreValueProxy<WalletSetGenesisData>;
public wallets: { [id: string]: BaseWallet<any> } = {};
constructor(
protected adapters: WalletAdapters,
protected genesisData: WalletSetGenesisData,
) {
super();
// Initialize proxies to our stored data.
this.genesisData = this.store.proxy('genesis');
}
static async from<T extends typeof WalletSet>(
this: T,
services: WalletServices,
genesisData?: WalletSetGenesisData,
) {
// Assign in memory stores as defaults in case otherwise not provided.
const servicesFinal = {
...{
store: new StoreInMemory(),
cache: new StoreInMemory(),
},
...services,
};
// If we are attempting to create a new wallet...
if (genesisData) {
// Ensure that the provided store does not already contain data.
if (Object.keys(await servicesFinal.store.all()).length) {
throw new Error(`Provided store already contains data`);
}
// Store the wallet genesis data.
await servicesFinal.store.set<WalletSetGenesisData>(
'genesis',
genesisData,
);
}
// Retrieve our genesis data and extract the private key.
const retrievedGenesisData =
await servicesFinal.store.getOrFail<WalletSetGenesisData>('genesis');
let hdPrivateNode: HDPrivateNode;
// Instantiate the HDPrivateNode from the type specified in metadata.
if ('seed' in retrievedGenesisData) {
hdPrivateNode = HDPrivateNode.fromSeed(
hexToBin(retrievedGenesisData.seed),
);
} else if ('mnemonic' in retrievedGenesisData) {
hdPrivateNode = HDPrivateNode.fromMnemonic(retrievedGenesisData.mnemonic);
} else {
hdPrivateNode = HDPrivateNode.fromXPriv(retrievedGenesisData.xpriv);
}
// Return a new instance of WalletHD.
return new this(
servicesFinal.blockchain,
servicesFinal.store,
servicesFinal.cache,
hdPrivateNode,
walletFactory,
) as InstanceType<T>;
}
async start() {
// Get the genesis data.
const genesisData = await this.genesisData.getOrFail();
// Declare storage for our start promises.
const startPromises: Promise<void>[] = [];
// Wait for all of our start promises to complete.
await Promise.all(startPromises);
}
async stop() {
// Declare storage for our stop promises.
const stopPromises: Promise<void>[] = [];
// Wait for all of our stop promises to complete.
await Promise.all(stopPromises);
}
async destroy() {
this.removeAllListeners();
}
async getTransactions() {
// Declare storage for our fetch promises.
const fetchPromises = [];
// Wait for all of our fetch promises to complete.
const txs = await Promise.all(fetchPromises);
// Flatten (and deduplicate) the transactions.
const txsFlattened = ExtMap.flatten(txs);
// Emit an event to notify that transactions have been updated.
this.emit('transactionsUpdated', txsFlattened);
// Return the deduplicated transactions.
return txsFlattened;
}
async getUnspents() {
// Declare storage for our fetch promises.
const fetchPromises = [];
// Iterate over each chain path and fetch unspents for each wallet.
for (const chainWallets of Object.values(this.wallets)) {
for (const wallet of chainWallets) {
fetchPromises.push(wallet.getUnspents());
}
}
// Wait for all of our fetch promises to complete.
const unspents = await Promise.all(fetchPromises);
// Flatten them.
const unspentsFlattened = ExtMap.flatten(unspents);
// Return the unspents.
return unspentsFlattened;
}
async getUnspentDirectives() {
// Declare storage for our fetch promises.
const fetchPromises = [];
// Iterate over each chain path and fetch unspent directives for each wallet.
for (const chainWallets of Object.values(this.wallets)) {
for (const wallet of chainWallets) {
fetchPromises.push(wallet.getUnspentDirectives());
}
}
// Wait for all of our fetch promises to complete.
const unspentDirectives = await Promise.all(fetchPromises);
// Flatten them.
const unspentsFlattened = unspentDirectives.flat();
// Return the unspents.
return unspentsFlattened;
}
async getBalanceSats() {
// Fetch our unspents.
const unspents = await this.getUnspents();
// Get the individual UTXOs.
const utxos = unspents
.map((blockchainUTXO) => blockchainUTXO.utxo)
.toArray();
// Calculate the balance.
return calculateBalanceSats(utxos);
}
async getBalanceTokens(): Promise<TokenBalances> {
// Fetch our unspents.
const unspents = await this.getUnspents();
// Get the individual UTXOs.
const utxos = unspents
.map((blockchainUTXO) => blockchainUTXO.utxo)
.toArray();
// Calculate the token balances.
return calculateBalanceTokens(utxos);
}
async getAddresses() {
const chainPaths = [CHAIN_EXTERNAL, CHAIN_INTERNAL];
let addressPromises = [];
for (const chainPath of chainPaths) {
addressPromises.push(
...this.wallets[chainPath].map((wallet) =>
wallet.getReceivingAddress(),
),
);
}
const addresses = await Promise.all(addressPromises);
return ExtMap.fromArray(addresses, (address) => address.toLockscriptHex());
}
async getReceivingAddress(chainPath = CHAIN_EXTERNAL) {
// TODO: Derive a new wallet, not the first one you lazy fuckhead.
// You'll probably need to refactor your scan() approach.
let receivingWallet = this.wallets[chainPath][0];
// If there is no receiving wallet, create one.
// TODO: This is fucked. You need to rethink deriveWallets.
if (!receivingWallet) {
this.deriveWallets(1, chainPath);
receivingWallet = this.wallets[chainPath][0];
}
// Return the receiving address of the derived wallet.
return receivingWallet.privateKey.derivePublicKey().deriveAddress();
}
async signTransaction(
txTemplate: Partial<TransactionTemplate>,
): Promise<Uint8Array> {
// Populate the final transaction template by merging with defaults.
// NOTE: This is just a convenience to make "send" easier to use.
const txTemplateFinal: TransactionTemplate = {
...{
version: 2,
locktime: 0,
inputs: [],
outputs: [],
},
...txTemplate,
};
// Get the receiving address of this wallet.
// NOTE: As per BIP44 spec, we set the change address to use the internal chain.
const changeAddress = await this.getReceivingAddress(CHAIN_EXTERNAL);
// Get the change locking bytecode (change address) for this wallet.
const changeLockingBytecode = changeAddress.toLockscriptBytes();
// Fetch unspents that are usable by this wallet.
const unspents = await this.getUnspentDirectives();
// Create an instance of our transaction builder and populate with our Wallet's UTXOs.
// NOTE: We must deal with tokens first and satoshis last.
// The reason is that the change output will be determined based on transaction fee.
const txBuilder = new TransactionBuilder(txTemplateFinal, unspents)
.addTokensFromUnspents()
.addTokenChangeOutputs(changeLockingBytecode)
.addSatoshisFromUnspents()
.addSatoshiChangeOutput(changeLockingBytecode);
// Generate the transaction.
const generatedTxBytes = txBuilder.toBytes();
return generatedTxBytes;
}
async sendTransaction(txTemplate: Partial<TransactionTemplate>) {
// Sign the transaction template.
const generatedTxBytes = await this.signTransaction(txTemplate);
// Broadcast the transaction to the blockchain.
await this.blockchain.broadcastTransaction(generatedTxBytes);
}
}
*/