LibAuth Comparison
This commit is contained in:
@@ -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>;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user