File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 80 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 80 KiB |
@@ -1,3 +1,10 @@
|
|||||||
# React
|
# React
|
||||||
|
|
||||||
TODO
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @xo/react
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
# Svelte
|
# Svelte
|
||||||
|
|
||||||
TODO
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @xo/svelte
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
# Vue
|
# Vue
|
||||||
|
|
||||||
TODO
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @xo/vue
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|||||||
+9
-18
@@ -1,3 +1,4 @@
|
|||||||
|
import { Transaction } from '@xo/primitives';
|
||||||
import { Blockchain, WalletHD } from '@xo/wallet';
|
import { Blockchain, WalletHD } from '@xo/wallet';
|
||||||
|
|
||||||
const blockchain = new Blockchain(
|
const blockchain = new Blockchain(
|
||||||
@@ -8,24 +9,6 @@ const blockchain = new Blockchain(
|
|||||||
|
|
||||||
await blockchain.start();
|
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(
|
const wallet = WalletHD.fromMnemonic(
|
||||||
'harsh gospel job ordinary garden clay eight cactus replace general prison permit',
|
'harsh gospel job ordinary garden clay eight cactus replace general prison permit',
|
||||||
blockchain
|
blockchain
|
||||||
@@ -33,4 +16,12 @@ const wallet = WalletHD.fromMnemonic(
|
|||||||
|
|
||||||
await wallet.scan();
|
await wallet.scan();
|
||||||
|
|
||||||
|
const transactions = wallet.getTransactions();
|
||||||
|
|
||||||
|
transactions.forEach((tx) => {
|
||||||
|
blockchain.broadcastTransaction(tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(blockchain.pendingTransactions.deriveUtxoState());
|
||||||
|
|
||||||
console.log(wallet.privateKey());
|
console.log(wallet.privateKey());
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --build --clean",
|
"build": "tsc --build --clean && tsc --build",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@@ -22,6 +22,35 @@ export class TransactionGraph {
|
|||||||
this.transactions.delete(txHash);
|
this.transactions.delete(txHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeDependentTransactions(txHash: string): string[] {
|
||||||
|
const removedTxids: string[] = [];
|
||||||
|
const toRemove: Set<string> = 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 {
|
getTransaction(txHash: string): Transaction | undefined {
|
||||||
return this.transactions.get(txHash);
|
return this.transactions.get(txHash);
|
||||||
}
|
}
|
||||||
@@ -122,4 +151,10 @@ export class TransactionGraph {
|
|||||||
|
|
||||||
return order;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --build --clean",
|
"build": "tsc --build --clean && tsc --build",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --build --clean",
|
"build": "tsc --build --clean && tsc --build",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@@ -88,11 +88,8 @@ export class Blockchain {
|
|||||||
transaction: Uint8Array | Transaction | string,
|
transaction: Uint8Array | Transaction | string,
|
||||||
opts: { waitUntilSeen: boolean } = { waitUntilSeen: true }
|
opts: { waitUntilSeen: boolean } = { waitUntilSeen: true }
|
||||||
) {
|
) {
|
||||||
if (typeof transaction === 'string') {
|
if (!(transaction instanceof Transaction)) {
|
||||||
transaction = Transaction.fromHex(transaction);
|
transaction = Transaction.fromAny(transaction);
|
||||||
}
|
|
||||||
if (transaction instanceof Uint8Array) {
|
|
||||||
transaction = Transaction.fromBytes(transaction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pendingTransactions.addTransaction(transaction);
|
this.pendingTransactions.addTransaction(transaction);
|
||||||
@@ -107,16 +104,27 @@ export class Blockchain {
|
|||||||
transaction: Uint8Array | Transaction | string,
|
transaction: Uint8Array | Transaction | string,
|
||||||
opts: { waitUntilSeen: boolean } = { waitUntilSeen: true }
|
opts: { waitUntilSeen: boolean } = { waitUntilSeen: true }
|
||||||
) {
|
) {
|
||||||
if (typeof transaction === 'string') {
|
if (!(transaction instanceof Transaction)) {
|
||||||
transaction = Transaction.fromHex(transaction);
|
transaction = Transaction.fromAny(transaction);
|
||||||
}
|
|
||||||
if (transaction instanceof Uint8Array) {
|
|
||||||
transaction = Transaction.fromBytes(transaction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pendingTransactions.addTransaction(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.
|
// TODO: Wait for transaction to be seen on network.
|
||||||
console.log(opts);
|
console.log(opts);
|
||||||
|
|||||||
@@ -3,3 +3,6 @@ export * from './blockchain-types.js';
|
|||||||
export * from './storage.js';
|
export * from './storage.js';
|
||||||
export * from './wallet-hd.js';
|
export * from './wallet-hd.js';
|
||||||
export * from './wallet-p2pkh.js';
|
export * from './wallet-p2pkh.js';
|
||||||
|
export * from './wallet-p2sh20.js';
|
||||||
|
export * from './wallet-p2sh32.js';
|
||||||
|
export * from './wallet-set.js';
|
||||||
|
|||||||
@@ -6,6 +6,14 @@
|
|||||||
* We don't want to extend this - we want to compose it.
|
* We don't want to extend this - we want to compose it.
|
||||||
* Reason being that we want to support multiple storage adapters.
|
* 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).
|
* 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 {
|
export class Storage {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Import the Stamp class to derive the private keys
|
// 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 { EventEmitter } from '../utils/index.js';
|
||||||
import { Blockchain } from './blockchain.js';
|
import { Blockchain } from './blockchain.js';
|
||||||
import {
|
import {
|
||||||
@@ -232,6 +232,14 @@ export class WalletHD<
|
|||||||
|
|
||||||
return encodedTransaction;
|
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<Transaction>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Placeholder.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* TODO Use AddressData
|
||||||
|
*/
|
||||||
|
export class WalletP2SH20 {
|
||||||
|
constructor() {
|
||||||
|
console.log('TODO');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Placeholder.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* TODO Use AddressData
|
||||||
|
*/
|
||||||
|
export class WalletP2SH32 {
|
||||||
|
constructor() {
|
||||||
|
console.log('TODO');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -14,13 +14,15 @@
|
|||||||
"autoConfiguration": true,
|
"autoConfiguration": true,
|
||||||
"format": "vitepress",
|
"format": "vitepress",
|
||||||
"pretty": true,
|
"pretty": true,
|
||||||
"collapsed": false
|
"collapsed": true
|
||||||
},
|
},
|
||||||
"indexFormat": "table",
|
"indexFormat": "table",
|
||||||
"parametersFormat": "table",
|
"parametersFormat": "table",
|
||||||
"interfacePropertiesFormat": "table",
|
"interfacePropertiesFormat": "table",
|
||||||
"classPropertiesFormat": "table",
|
"classPropertiesFormat": "table",
|
||||||
"propertyMembersFormat": "table",
|
"propertyMembersFormat": "table",
|
||||||
|
"typeDeclarationFormat": "table",
|
||||||
|
"enumMembersFormat": "table",
|
||||||
"useCodeBlocks": true,
|
"useCodeBlocks": true,
|
||||||
"sort": [
|
"sort": [
|
||||||
"static-first"
|
"static-first"
|
||||||
|
|||||||
Reference in New Issue
Block a user