@@ -30,6 +30,12 @@ export default defineConfig({
|
||||
title: 'XO Stack',
|
||||
description: 'XO Stack',
|
||||
themeConfig: {
|
||||
// Set the logo
|
||||
logo: {
|
||||
light: '/logo-light.svg',
|
||||
dark: '/logo-dark.svg',
|
||||
},
|
||||
|
||||
// Enable search functionality.
|
||||
search: {
|
||||
provider: 'local',
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<!--.vitepress/theme/MyLayout.vue-->
|
||||
<script setup>
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
|
||||
const { Layout } = DefaultTheme
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout>
|
||||
<template #aside-outline-before>
|
||||
My custom sidebar top content
|
||||
</template>
|
||||
</Layout>
|
||||
</template>
|
||||
@@ -1,17 +1,13 @@
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import { h } from 'vue'
|
||||
import type { Theme } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import './style.css'
|
||||
import type { Theme } from 'vitepress';
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
import Layout from './Layout.vue';
|
||||
import './style.css';
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout: () => {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
// https://vitepress.dev/guide/extending-default-theme#layout-slots
|
||||
})
|
||||
},
|
||||
Layout,
|
||||
enhanceApp({ app, router, siteData }) {
|
||||
// ...
|
||||
}
|
||||
} satisfies Theme
|
||||
},
|
||||
} satisfies Theme;
|
||||
|
||||
@@ -70,6 +70,22 @@
|
||||
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||
}
|
||||
|
||||
.VPSidebarItem.level-2.is-active > .item > .indicator,
|
||||
.VPSidebarItem.level-3.is-active > .item > .indicator,
|
||||
.VPSidebarItem.level-4.is-active > .item > .indicator,
|
||||
.VPSidebarItem.level-5.is-active > .item > .indicator {
|
||||
background-color: #b2fb54 !important;
|
||||
}
|
||||
|
||||
.VPSidebarItem.level-0.is-active > .item .link > .text,
|
||||
.VPSidebarItem.level-1.is-active > .item .link > .text,
|
||||
.VPSidebarItem.level-2.is-active > .item .link > .text,
|
||||
.VPSidebarItem.level-3.is-active > .item .link > .text,
|
||||
.VPSidebarItem.level-4.is-active > .item .link > .text,
|
||||
.VPSidebarItem.level-5.is-active > .item .link > .text {
|
||||
color: #b2fb54 !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
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 |
@@ -10,8 +10,8 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build-old": "rollup -c"
|
||||
"build": "npm run clean && tsc",
|
||||
"clean": "rm -r ./dist"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
|
||||
@@ -8,3 +8,5 @@ export * from './public-key.js';
|
||||
export * from './satoshis.js';
|
||||
export * from './script.js';
|
||||
export * from './transaction.js';
|
||||
export * from './transaction-graph.js';
|
||||
export * from './transaction-hash.js';
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { Transaction } from '@xo/primitives';
|
||||
|
||||
import { binToHex, Output } from '@bitauth/libauth';
|
||||
|
||||
export interface UTXO extends Output {
|
||||
outpointTransactionHash: string;
|
||||
outpointIndex: number;
|
||||
}
|
||||
|
||||
export class TransactionGraph {
|
||||
private transactions: Map<string, Transaction>;
|
||||
|
||||
constructor() {
|
||||
this.transactions = new Map();
|
||||
}
|
||||
|
||||
addTransaction(tx: Transaction) {
|
||||
this.transactions.set(tx.toHashHex(), tx);
|
||||
}
|
||||
|
||||
removeTransaction(txHash: string) {
|
||||
this.transactions.delete(txHash);
|
||||
}
|
||||
|
||||
getTransaction(txHash: string): Transaction | undefined {
|
||||
return this.transactions.get(txHash);
|
||||
}
|
||||
|
||||
getAllTransactions(): Transaction[] {
|
||||
return Array.from(this.transactions.values());
|
||||
}
|
||||
|
||||
deriveUtxoState(): UTXO[] {
|
||||
const utxoMap = new Map<string, UTXO>();
|
||||
|
||||
// First, add all outputs as potential UTXOs
|
||||
for (const tx of this.transactions.values()) {
|
||||
tx.getOutputs().forEach((output, outpointIndex) => {
|
||||
const txHashHex = tx.toHashHex();
|
||||
|
||||
const utxo: UTXO = {
|
||||
outpointTransactionHash: txHashHex,
|
||||
outpointIndex,
|
||||
valueSatoshis: output.valueSatoshis,
|
||||
lockingBytecode: output.lockingBytecode,
|
||||
};
|
||||
utxoMap.set(`${txHashHex}:${outpointIndex}`, utxo);
|
||||
});
|
||||
}
|
||||
|
||||
// Then, remove all spent outputs
|
||||
for (const tx of this.transactions.values()) {
|
||||
tx.getInputs().forEach((input) => {
|
||||
const outpointTransactionHashHex = binToHex(
|
||||
input.outpointTransactionHash
|
||||
);
|
||||
|
||||
utxoMap.delete(`${outpointTransactionHashHex}:${input.outpointIndex}`);
|
||||
});
|
||||
}
|
||||
|
||||
return Array.from(utxoMap.values());
|
||||
}
|
||||
|
||||
isTransactionValid(tx: Transaction): boolean {
|
||||
const currentUtxos = this.deriveUtxoState();
|
||||
const utxoMap = new Map(
|
||||
currentUtxos.map((utxo) => [
|
||||
`${utxo.outpointTransactionHash}:${utxo.outpointIndex}`,
|
||||
utxo,
|
||||
])
|
||||
);
|
||||
|
||||
return tx.getInputs().every((input) => {
|
||||
const outpointTransactionHashHex = binToHex(
|
||||
input.outpointTransactionHash
|
||||
);
|
||||
return utxoMap.has(
|
||||
`${outpointTransactionHashHex}:${input.outpointIndex}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getDependentTransactions(txHash: string): Transaction[] {
|
||||
return this.getAllTransactions().filter((tx) => {
|
||||
tx.getInputs().some((input) => {
|
||||
const outpointTransactionHashHex = binToHex(
|
||||
input.outpointTransactionHash
|
||||
);
|
||||
return outpointTransactionHashHex === txHash;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBroadcastOrder(): Transaction[] {
|
||||
const visited = new Set<string>();
|
||||
const order: Transaction[] = [];
|
||||
|
||||
const visit = (txid: string) => {
|
||||
if (visited.has(txid)) return;
|
||||
|
||||
const tx = this.transactions.get(txid);
|
||||
if (!tx) return;
|
||||
|
||||
visited.add(txid);
|
||||
|
||||
// First, visit all transactions that this transaction depends on
|
||||
for (const input of tx.getInputs()) {
|
||||
const outpointTransactionHashHex = binToHex(
|
||||
input.outpointTransactionHash
|
||||
);
|
||||
visit(outpointTransactionHashHex);
|
||||
}
|
||||
|
||||
order.push(tx);
|
||||
};
|
||||
|
||||
// Start the topological sort from each transaction
|
||||
for (const tx of this.transactions.values()) {
|
||||
visit(tx.toHashHex());
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
binToHex,
|
||||
hexToBin,
|
||||
hashTransaction,
|
||||
isHex,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Transaction Hash Primitive.
|
||||
*/
|
||||
export class TransactionHash
|
||||
{
|
||||
public readonly bytes: Uint8Array;
|
||||
|
||||
/**
|
||||
* Instantiates TransactionHash from a Transaction Hash's bytes.
|
||||
*
|
||||
* @param bytes {Uint8Array} The Transaction Hash's bytes.
|
||||
*/
|
||||
constructor(bytes: Uint8Array)
|
||||
{
|
||||
// If bytes is not exactly 32, then this is an invalid transaction hash...
|
||||
if(bytes.length !== 32)
|
||||
{
|
||||
throw(new Error(`Invalid Transaction Hash: Expected 32 bytes but received ${bytes.length}`));
|
||||
}
|
||||
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates TransactionHash from a hex string.
|
||||
*
|
||||
* @param hex {string} The hex string.
|
||||
*
|
||||
* @throws {Error} If hex is invalid.
|
||||
*
|
||||
* @returns {TransactionHash} Instance of TransactionHash.
|
||||
*/
|
||||
public static fromHex(hex: string): TransactionHash
|
||||
{
|
||||
if(!isHex(hex))
|
||||
{
|
||||
throw(new Error('Invalid hex provided'));
|
||||
}
|
||||
|
||||
const bytes = hexToBin(hex);
|
||||
|
||||
return new TransactionHash(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates TransactionHash from a Transaction's bytes.
|
||||
*
|
||||
* @param transactionBytes {Uint8Array} The Transaction's bytes.
|
||||
*
|
||||
* @returns {TransactionHash} Instance of TransactionHash.
|
||||
*/
|
||||
public static fromTransactionBytes(transactionBytes: Uint8Array): TransactionHash
|
||||
{
|
||||
const transactionHash = hashTransaction(transactionBytes);
|
||||
|
||||
return new TransactionHash(hexToBin(transactionHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates TransactionHash from a Transaction's bytes in hex.
|
||||
*
|
||||
* @param transactionHex {Uint8Array} The Transaction's bytes in hex.
|
||||
*
|
||||
* @throws {Error} If hex is invalid.
|
||||
*
|
||||
* @returns {TransactionHash} Instance of TransactionHash.
|
||||
*/
|
||||
public static fromTransactionHex(transactionHex: string): TransactionHash
|
||||
{
|
||||
if(!isHex(transactionHex))
|
||||
{
|
||||
throw(new Error('Invalid hex provided'));
|
||||
}
|
||||
|
||||
const bytes = hexToBin(transactionHex);
|
||||
|
||||
return TransactionHash.fromTransactionBytes(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns transaction hash as a hex string.
|
||||
*
|
||||
* @returns {string} Hex string of the Transaction Hash.
|
||||
*/
|
||||
public toHex(): string
|
||||
{
|
||||
return binToHex(this.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns transaction hash as a Uint8Array.
|
||||
*
|
||||
* @returns {Uint8Array} Uint8Array of the Transaction Hash.
|
||||
*/
|
||||
public toBytes(): Uint8Array
|
||||
{
|
||||
return this.bytes;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TransactionHash } from './transaction-hash.js';
|
||||
|
||||
import {
|
||||
binToHex,
|
||||
hexToBin,
|
||||
@@ -69,6 +71,18 @@ export class Transaction {
|
||||
return binToHex(bytes);
|
||||
}
|
||||
|
||||
public toHash(): TransactionHash {
|
||||
return TransactionHash.fromTransactionBytes(this.toBytes());
|
||||
}
|
||||
|
||||
public toHashHex(): string {
|
||||
return this.toHash().toHex();
|
||||
}
|
||||
|
||||
public toHashBytes(): Uint8Array {
|
||||
return this.toHash().toBytes();
|
||||
}
|
||||
|
||||
public getVersion() {
|
||||
return this.transaction.version;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Transaction } from '@xo/primitives';
|
||||
import { AddressNotification, ElectrumRequest } from './blockchain-types.js';
|
||||
|
||||
import { ElectrumClient } from '@electrum-cash/network';
|
||||
@@ -75,6 +76,22 @@ export class Blockchain {
|
||||
return response;
|
||||
}
|
||||
|
||||
// TODO: async commit() method
|
||||
// This won't actually broadcast, this will just commit the transaction to our graph.
|
||||
// The reason we want this is because, for protos like BIP70, we want service to verify the tx and broadcast it.
|
||||
// And we just want to be able to monitor whether service did the needful (broadcasted it).
|
||||
// This should accept options: { waitForSeen: boolean }
|
||||
async commitTransaction(
|
||||
transaction: Transaction,
|
||||
opts: { waitUntilSeen: boolean } = { waitUntilSeen: true }
|
||||
) {
|
||||
console.log(transaction);
|
||||
console.log(opts);
|
||||
}
|
||||
|
||||
// TODO: async broadcast() method
|
||||
// This should accept options: { waitForSeen: boolean }
|
||||
|
||||
async subscribeAddress(address: string, callback: AddressCallback) {
|
||||
await this.electrumClient.subscribe(
|
||||
'blockchain.address.subscribe',
|
||||
|
||||
Reference in New Issue
Block a user