diff --git a/docs/.vitepress/theme/logo-dark.svg b/docs/.vitepress/theme/logo-dark.svg new file mode 100644 index 0000000..a8e5dd5 --- /dev/null +++ b/docs/.vitepress/theme/logo-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/.vitepress/theme/logo-light.svg b/docs/.vitepress/theme/logo-light.svg new file mode 100644 index 0000000..675c98d --- /dev/null +++ b/docs/.vitepress/theme/logo-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/frameworks/react.md b/docs/frameworks/react.md index ac8f2a1..4e5ba24 100644 --- a/docs/frameworks/react.md +++ b/docs/frameworks/react.md @@ -1,3 +1,10 @@ # React -TODO +## Install + +```sh +npm install @xo/react +``` + +## Usage + diff --git a/docs/frameworks/svelte.md b/docs/frameworks/svelte.md index da08a17..2db9b44 100644 --- a/docs/frameworks/svelte.md +++ b/docs/frameworks/svelte.md @@ -1,3 +1,10 @@ # Svelte -TODO +## Install + +```sh +npm install @xo/svelte +``` + +## Usage + diff --git a/docs/frameworks/vue.md b/docs/frameworks/vue.md index f16f9b9..cc632d7 100644 --- a/docs/frameworks/vue.md +++ b/docs/frameworks/vue.md @@ -1,3 +1,9 @@ # Vue -TODO +## Install + +```sh +npm install @xo/vue +``` + +## Usage diff --git a/examples/node/index.js b/examples/node/index.js index bbe771e..4a8fc7a 100644 --- a/examples/node/index.js +++ b/examples/node/index.js @@ -1,3 +1,4 @@ +import { Transaction } from '@xo/primitives'; import { Blockchain, WalletHD } from '@xo/wallet'; const blockchain = new Blockchain( @@ -8,24 +9,6 @@ const blockchain = new Blockchain( await blockchain.start(); -console.log( - await blockchain.request( - 'blockchain.address.get_history', - 'bitcoincash:qqyy3mss5vmthgnu0m5sm39pcfq8z799ku2nxernca', - 861_606, - 861_607 - ) -); - -/* -console.log( - await blockchain.request( - 'blockchain.transaction.get', - 'da776b9c273ec406f7ecd488950216aa17a04018201298f263e2fcdb0b903d29' - ) -); -*/ - const wallet = WalletHD.fromMnemonic( 'harsh gospel job ordinary garden clay eight cactus replace general prison permit', blockchain @@ -33,4 +16,12 @@ const wallet = WalletHD.fromMnemonic( await wallet.scan(); +const transactions = wallet.getTransactions(); + +transactions.forEach((tx) => { + blockchain.broadcastTransaction(tx); +}); + +console.log(blockchain.pendingTransactions.deriveUtxoState()); + console.log(wallet.privateKey()); diff --git a/packages/primitives/package.json b/packages/primitives/package.json index 913a124..e369b4f 100644 --- a/packages/primitives/package.json +++ b/packages/primitives/package.json @@ -10,7 +10,7 @@ "dist" ], "scripts": { - "build": "tsc --build --clean", + "build": "tsc --build --clean && tsc --build", "prepublishOnly": "npm run build" }, "author": "", diff --git a/packages/primitives/src/transaction-graph.ts b/packages/primitives/src/transaction-graph.ts index 65b867b..6a20b66 100644 --- a/packages/primitives/src/transaction-graph.ts +++ b/packages/primitives/src/transaction-graph.ts @@ -22,6 +22,35 @@ export class TransactionGraph { this.transactions.delete(txHash); } + removeDependentTransactions(txHash: string): string[] { + const removedTxids: string[] = []; + const toRemove: Set = new Set([txHash]); + + const findDependents = (id: string) => { + for (const [otherTxid, tx] of this.transactions) { + if ( + tx + .getInputs() + .some((input) => binToHex(input.outpointTransactionHash) === id) + ) { + toRemove.add(otherTxid); + findDependents(otherTxid); + } + } + }; + + findDependents(txHash); + + for (const id of toRemove) { + if (this.transactions.has(id)) { + this.transactions.delete(id); + removedTxids.push(id); + } + } + + return removedTxids; + } + getTransaction(txHash: string): Transaction | undefined { return this.transactions.get(txHash); } @@ -122,4 +151,10 @@ export class TransactionGraph { return order; } + + getNextTransactionToBroadcast(): Transaction | undefined { + // This is pretty inefficient in the sense we recompile the graph each time. + // But I'm not sure if that would really have much real-world performance impact. + return this.getBroadcastOrder()[0] ?? undefined; + } } diff --git a/packages/vue/package.json b/packages/vue/package.json index 086cd41..ebb1be9 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -10,7 +10,7 @@ "dist" ], "scripts": { - "build": "tsc --build --clean", + "build": "tsc --build --clean && tsc --build", "prepublishOnly": "npm run build" }, "author": "", diff --git a/packages/wallet/package.json b/packages/wallet/package.json index df91c35..7142dad 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -10,7 +10,7 @@ "dist" ], "scripts": { - "build": "tsc --build --clean", + "build": "tsc --build --clean && tsc --build", "prepublishOnly": "npm run build" }, "author": "", diff --git a/packages/wallet/src/wallet/blockchain.ts b/packages/wallet/src/wallet/blockchain.ts index 4bebdf7..2f8165a 100644 --- a/packages/wallet/src/wallet/blockchain.ts +++ b/packages/wallet/src/wallet/blockchain.ts @@ -88,11 +88,8 @@ export class Blockchain { transaction: Uint8Array | Transaction | string, opts: { waitUntilSeen: boolean } = { waitUntilSeen: true } ) { - if (typeof transaction === 'string') { - transaction = Transaction.fromHex(transaction); - } - if (transaction instanceof Uint8Array) { - transaction = Transaction.fromBytes(transaction); + if (!(transaction instanceof Transaction)) { + transaction = Transaction.fromAny(transaction); } this.pendingTransactions.addTransaction(transaction); @@ -107,16 +104,27 @@ export class Blockchain { transaction: Uint8Array | Transaction | string, opts: { waitUntilSeen: boolean } = { waitUntilSeen: true } ) { - if (typeof transaction === 'string') { - transaction = Transaction.fromHex(transaction); - } - if (transaction instanceof Uint8Array) { - transaction = Transaction.fromBytes(transaction); + if (!(transaction instanceof Transaction)) { + transaction = Transaction.fromAny(transaction); } this.pendingTransactions.addTransaction(transaction); - // TODO: Broadcast the transaction. + // Broadcast any pending transaction. + // TODO: Use while loop instead. + for (let tx of this.pendingTransactions.getBroadcastOrder()) { + try { + console.log('broadcasting', tx); + + this.pendingTransactions.removeTransaction(tx.toHashHex()); + } catch (error) { + this.pendingTransactions.removeDependentTransactions(tx.toHashHex()); + + throw new Error( + `Broadcast failed for ${transaction.getHashHex()}. Removing dependent transactions.` + ); + } + } // TODO: Wait for transaction to be seen on network. console.log(opts); diff --git a/packages/wallet/src/wallet/index.ts b/packages/wallet/src/wallet/index.ts index 6f23f5f..d83459a 100644 --- a/packages/wallet/src/wallet/index.ts +++ b/packages/wallet/src/wallet/index.ts @@ -3,3 +3,6 @@ export * from './blockchain-types.js'; export * from './storage.js'; export * from './wallet-hd.js'; export * from './wallet-p2pkh.js'; +export * from './wallet-p2sh20.js'; +export * from './wallet-p2sh32.js'; +export * from './wallet-set.js'; diff --git a/packages/wallet/src/wallet/storage.ts b/packages/wallet/src/wallet/storage.ts index e18471c..ed3e5a3 100644 --- a/packages/wallet/src/wallet/storage.ts +++ b/packages/wallet/src/wallet/storage.ts @@ -6,6 +6,14 @@ * We don't want to extend this - we want to compose it. * Reason being that we want to support multiple storage adapters. * And we should allow the possibility of having non-critical storage adapters fail (e.g. remote vs local). + * + * Additionally, consider how we'll store data for WalletSets. + * There will be child wallets. These will have their own data. + * And each child may then have its own wallet. And on and on. + * So, it's a tree hierarchy. + * We can probably emit a "storageUpdated" event that is registered by each parent wallet. + * The parent then "wraps" its own state into this and propagates it up to its parent. + * Then, when it reaches root, the storage adapter stashes all of the propagated data into actual storage. */ export class Storage { constructor() { diff --git a/packages/wallet/src/wallet/wallet-hd.ts b/packages/wallet/src/wallet/wallet-hd.ts index e2c704b..0867295 100644 --- a/packages/wallet/src/wallet/wallet-hd.ts +++ b/packages/wallet/src/wallet/wallet-hd.ts @@ -1,5 +1,5 @@ // Import the Stamp class to derive the private keys -import { HDPrivateNode } from '@xo/primitives'; +import { HDPrivateNode, Transaction } from '@xo/primitives'; import { EventEmitter } from '../utils/index.js'; import { Blockchain } from './blockchain.js'; import { @@ -232,6 +232,14 @@ export class WalletHD< return encodedTransaction; } + + getTransactions() { + // NOTE: We use a set so that we don't end up with duplicate transactions. + return this.wallets.reduce((txs, wallet) => { + wallet.transactions.forEach((tx) => txs.add(tx)); + return txs; // Make sure to return the accumulator + }, new Set()); + } } //----------------------------------------------------------------------------- diff --git a/packages/wallet/src/wallet/wallet-p2sh20.ts b/packages/wallet/src/wallet/wallet-p2sh20.ts new file mode 100644 index 0000000..0c9b044 --- /dev/null +++ b/packages/wallet/src/wallet/wallet-p2sh20.ts @@ -0,0 +1,11 @@ +/** + * Placeholder. + * + * @remarks + * TODO Use AddressData + */ +export class WalletP2SH20 { + constructor() { + console.log('TODO'); + } +} diff --git a/packages/wallet/src/wallet/wallet-p2sh32.ts b/packages/wallet/src/wallet/wallet-p2sh32.ts new file mode 100644 index 0000000..ab92153 --- /dev/null +++ b/packages/wallet/src/wallet/wallet-p2sh32.ts @@ -0,0 +1,11 @@ +/** + * Placeholder. + * + * @remarks + * TODO Use AddressData + */ +export class WalletP2SH32 { + constructor() { + console.log('TODO'); + } +} diff --git a/packages/wallet/src/wallet/wallet-set.ts b/packages/wallet/src/wallet/wallet-set.ts new file mode 100644 index 0000000..bb02f8e --- /dev/null +++ b/packages/wallet/src/wallet/wallet-set.ts @@ -0,0 +1,13 @@ +/** + * Placeholder. + * + * @remarks + * One difficult thing here is going to be storage of the subwallets. + * We're going to have child wallets and we need to propagate their storage to the root WalletSet. + * See remarks on Storage - it's semi-related and I'm still playing with approaches. + */ +export class WalletSet { + constructor() { + console.log('TODO'); + } +} diff --git a/typedoc.json b/typedoc.json index 8d874c9..1e034ad 100644 --- a/typedoc.json +++ b/typedoc.json @@ -14,13 +14,15 @@ "autoConfiguration": true, "format": "vitepress", "pretty": true, - "collapsed": false + "collapsed": true }, "indexFormat": "table", "parametersFormat": "table", "interfacePropertiesFormat": "table", "classPropertiesFormat": "table", "propertyMembersFormat": "table", + "typeDeclarationFormat": "table", + "enumMembersFormat": "table", "useCodeBlocks": true, "sort": [ "static-first"