Resolve "feat(Primitives): Have Bytes Primitive extends Uint8Array (if possible)"

This commit is contained in:
devperate
2025-04-28 02:49:25 +00:00
committed by Jim Hamill
parent 1d5e3131c8
commit d4398439c7
19 changed files with 296 additions and 269 deletions
+6
View File
@@ -11,18 +11,21 @@ The XO Stack is a comprehensive framework designed for building applications on
The XO Stack adopts an Object-Oriented design for several key reasons:
1. **Encapsulation and Data Privacy**
- Internal implementation details are hidden from consumers
- State management is contained within specific classes
- Clear boundaries between different system components
- Reduced likelihood of state-related bugs
2. **Adapter Pattern Benefits**
- Pluggable components that can be swapped without changing application code
- Easy to add new implementations for different environments
- Testing simplified through mock adapters
- Reduced vendor lock-in
3. **Developer Experience**
- Strong IDE support with autocomplete suggestions
- Type safety when using TypeScript
- Clear inheritance hierarchies
@@ -57,6 +60,7 @@ The storage adapter interface that defines how data is persisted:
- Query capabilities
Available implementations:
- **StoreInMemory**: Volatile storage for testing and development
- **StoreIndexedDB**: Browser-based persistent storage
- **StoreSQLite**: Native application storage solution
@@ -71,6 +75,7 @@ Defines the interface for interacting with the BCH network:
- Network status management
Available implementations:
- **BlockchainInMemory**: Mock blockchain for testing
- **BlockchainElectrum**: Production-ready Electrum protocol client
@@ -84,6 +89,7 @@ Represents different wallet types for managing BCH:
- Balance tracking
Available implementations:
- **WalletP2PKH**: Basic Pay-to-Public-Key-Hash wallet
- **WalletHD**: Hierarchical Deterministic wallet implementation
- **WalletXO**: Extended HD wallet with XO-specific features
+1
View File
@@ -78,6 +78,7 @@ Note that these are still unstable and will change in future.
:toFunctions="encrypter.to"
:methods="encrypter.methods"
/>
</div>
## Hasher
+42 -29
View File
@@ -18,11 +18,13 @@
**XO**
- Object-Oriented approach
- Built on-top of LibAuth.
- Declarative-style API (for ease of use)
- Adapter-based (can swap in/out blockchain adapters, storage adapters, etc).
- Layered packages:
1. @xocash/primitives
Network-naive layer offering common BCH primitives (Address, HDNodes, PrivateKey, PublicKey, etc).
@@ -116,13 +118,14 @@ This is still in flux, but we are anticipating (at least) three packages/layers:
outputs: [
{
lockingBytecode: Address.from('someAddress').toLockscriptBytes(),
valueSatoshis: 20000n
}
]
valueSatoshis: 20000n,
},
],
});
```
Tree-shaking is a big down-side of this in the sense that currently (with the exception of Google's `clojure`) no bundlers can shake unused class methods. However, apparently this is possible so **may** change in future: https://github.com/rollup/rollup/issues/349#issuecomment-2560719286
</details>
## Wallets and Wallet-Set/Root-Seed/Account
@@ -174,7 +177,7 @@ To minimize the amount of data necessary for recovery and maintain backwards com
The below is very rough (I'm not suggesting this as a practical implementation, but just to illustrate the approach vaguely).
| Key | Derivation (Rough Example) | Comments |
|------------------------|------------------------------------------------------------------|---------------------------------------|
| ---------------------- | ---------------------------------------------------------------- | ------------------------------------- |
| Default HD Wallet | Root Seed -> m/44'/145'/0' | Backwards compat with current Wallets |
| WalletId | hash256(hash256(rootSeed) + hash256('walletId')) | Identifier for Wallet |
| EncryptionKey | hash256(hash256(rootSeed) + hash256('encryptionKey')) | Encrpytion Key for Activities |
@@ -348,6 +351,7 @@ For XO, we want to try to make it so that:
TODO: Do we encrypt Activity IDs (UUIDV7)? Might be overkill. Also, incurrs additional compute + bandwidth costs (+28 bytes per UUIDV7 due to AES-GCM header).
There's also the problem that, when syncing from an encrypted remote, we would have to ensure we encrypt using the same AES-GCM IV, otherwise the values we generate locally for set-comparison would not be identical to the encrypted remote's values.
</details>
In the structure being considered currently, we index each activity using a UUIDV7 (which contains a timestamp).
@@ -356,6 +360,7 @@ In the structure being considered currently, we index each activity using a UUID
<summary>Why UUIDV7?</summary>
The original LibAuth proposal suggests using a millisecond timestamp as index (appended by entity), but we probably need more granularity as several Activities might occur on the same millisecond timestamp. UUIDV7 itself contains a timestamp (and is lexicographically sortable by timestamp), but then attaches additional randomness to enforce uniqueness.
</details>
```ts
@@ -364,7 +369,6 @@ export type Activities = { [uuidv7: string]: Activity };
... where the payload Activity itself looks something like this:
```ts
export type Activity<Type extends string, ValueType> = {
// NOTE: Timestamp will be automatically generated if not provided.
@@ -391,8 +395,14 @@ export type TransactionMemo = Activity<'TransactionMemo', string>;
export type XOWalletColor = Activity<'XOWalletColor', string>;
export type XOTransactionCategory = Activity<'XOTransactionCategory', string>;
export type XOFrozenCoin = Activity<'XOFrozenCoin', boolean>;
export type XOTemplateAction = Activity<'XOTemplateAction', TemplateActionPayload>;
export type XOTemplateActionResult = Activity<'XOTemplateActionResult', TemplateActionResultPayload>;
export type XOTemplateAction = Activity<
'XOTemplateAction',
TemplateActionPayload
>;
export type XOTemplateActionResult = Activity<
'XOTemplateActionResult',
TemplateActionResultPayload
>;
```
<details>
@@ -403,22 +413,26 @@ export type XOTemplateActionResult = Activity<'XOTemplateActionResult', Template
For example, if we had a structure like so:
```ts
export type WalletDetails = Activity<'WalletDetails', {
export type WalletDetails = Activity<
'WalletDetails',
{
name: string;
description: string
}>;
description: string;
}
>;
```
... it can open us up to a scenario like the following:
1. *Wallet A* and *Wallet B* are not connected to each other.
2. *Wallet A* creates a `WalletDetails` activity and sets the name to "New Wallet Name", while not modifying the `description`.
3. *Wallet B* creates a `WalletDetails` activity and sets the description to "New Wallet Description", while not modifying the `name`.
4. When we sync these devices under a "latest wins" policy for `WalletDetails`, there will be data loss: We will prioritize the latest WalletDetails activity (*Wallet B*), which means that the name set by Wallet A would be lost as the `WalletDetails` structure of *Wallet B* would take precedence over that of *Wallet A*.
1. _Wallet A_ and _Wallet B_ are not connected to each other.
2. _Wallet A_ creates a `WalletDetails` activity and sets the name to "New Wallet Name", while not modifying the `description`.
3. _Wallet B_ creates a `WalletDetails` activity and sets the description to "New Wallet Description", while not modifying the `name`.
4. When we sync these devices under a "latest wins" policy for `WalletDetails`, there will be data loss: We will prioritize the latest WalletDetails activity (_Wallet B_), which means that the name set by Wallet A would be lost as the `WalletDetails` structure of _Wallet B_ would take precedence over that of _Wallet A_.
The unfortunate trade-off here is that: we end up with more activities and syncing becomes more expensive. However, by grouping by what might mutate, we end up with much better consistency/reliability.
Immutable data (e.g. something like a `XOTemplateAction`) can obviously have more complex Activity payloads as we would never modify "just a part" of that.
</details>
Like the LibAuth proposal, the aim is that state will become "eventually consistent". We do this by syncing these individual activities between devices and then each device compiles a "snapshot" of the final state.
@@ -520,7 +534,9 @@ This might also yield other performance improvements when compiling snapshots in
```ts
// Pseudo-code for clarity.
const relevantActivitiesToBuildSnapshotWith = activities.filter(activity => activity !== null);
const relevantActivitiesToBuildSnapshotWith = activities.filter(
(activity) => activity !== null,
);
```
#### (Maybe) Using enums to represent "type"
@@ -632,44 +648,41 @@ Example of interface for an Action (still WIP):
```ts
interface Action {
// Human-readble name
name: string,
name: string;
// Human-readable description
description: string,
description: string;
// A list of transaction shapes that this action will execute sequentially.
// NOTE: We need the capability to execute multiple transactions in an action so that:
// 1. We can create minting transactions required for token genesis.
// 2. We can create "consolidation transactions" so that subsequent shapes that impose constraints on input counts, etc, will not have additional UTXOs appended.
// 3. Some contracts will simply require multi-tx flows for a given action.
transactions: Array<TransactionShape>,
transactions: Array<TransactionShape>;
// A list of "data" that should be returned to the Dapp.
// This represents the "state" that the Dapp (or Wallet) MUST store.
// The format is name (or alias) to a CashASM evaluation. Using a CashASM over raw variable names gives us flexibility in that:
// 1. We can conceal values derived by the wallet from the consuming service.
// 2. In future, format values for direct consumption by a follow-up action (e.g. imagine a generic settlement service)
// 3. In future, "transactions" may be made optional so that that an action can be used to just collect data (e.g. collect pubKeys for multisig setup or collect pubkeys/payout address for AnyHedge P2P Setup). TODO: Go into more detail about this.
resolves: { [name: string]: CashASM }
resolves: { [name: string]: CashASM };
}
```
Actions are then invoked something like:
```ts
const createOfferResult = await wallet.executeAction(
BCHGURU_TEMPLATE,
{
const createOfferResult = await wallet.executeAction(BCHGURU_TEMPLATE, {
// The action from the template to invoke.
action: 'bch_createOffer',
// The data to pass into this action.
// NOTE: In practice, these will need to be converted to Uint8Array. An 'encodeData' util function will be provided to help.
data: {
oraclePublicKey: '0x02d09db08af1ff4e8453919cc866a4be427d7bfe18f2c05e5444c196fcf6fd2818',
oraclePublicKey:
'0x02d09db08af1ff4e8453919cc866a4be427d7bfe18f2c05e5444c196fcf6fd2818',
player1Prediction: 100_000_000,
betAmount: 250_000,
// ...
}
}
);
},
});
```
The templates themselves are quite similar to LibAuth. It might be better to link to this document as many of the concepts/ideas will likely carry across to XO.
@@ -689,7 +702,7 @@ const domainSandboxSeed = sha256(sha256(rootSeed) + sha256(domain));
For CashConnect, the intent of this was to:
1. Allow recovery from root-seed only (this is not perfect, but if a user re-visits a domain, they will be able to recover their key associated with that domain).
- *The assumption of CashConnect was that the Wallet did not have the capability to store any P2SH data (this responsibility is relegated to the Dapp/Service).*
- _The assumption of CashConnect was that the Wallet did not have the capability to store any P2SH data (this responsibility is relegated to the Dapp/Service)._
2. Give the domain a permissable sandbox account ("key") it could use for construction of actions
- Create transactions that consolidate amounts for follow-up transactions
- Create Token Genesis transactions and
@@ -710,7 +723,7 @@ For CashConnect, the intent of this was to:
},
keys: {
privateKeys: {
sandboxKey: theDerivedKeyForThisDappOrTemplate
sandboxKey: theDerivedKeyForThisDappOrTemplate,
},
},
};
+6 -3
View File
@@ -209,7 +209,7 @@ export class Address {
type: 'P2SH20' | 'P2SH32' = 'P2SH32',
): Address {
// Return a new instance of address.
return this.fromRedeemScript(Bytes.fromHex(hex).toBytes(), type);
return this.fromRedeemScript(Bytes.fromHex(hex).toUint8Array(), type);
}
/**
@@ -257,7 +257,7 @@ export class Address {
* @returns {Address} Instance of Address.
*/
public static fromLockscriptHex(hex: string): Address {
return this.fromLockscriptBytes(Bytes.fromHex(hex).toBytes());
return this.fromLockscriptBytes(Bytes.fromHex(hex).toUint8Array());
}
/**
@@ -298,7 +298,10 @@ export class Address {
hash160Hex: string,
type: AddressType = 'P2PKH',
): Address {
return this.fromHash160Bytes(Bytes.fromHex(hash160Hex).toBytes(), type);
return this.fromHash160Bytes(
Bytes.fromHex(hash160Hex).toUint8Array(),
type,
);
}
/**
+3 -3
View File
@@ -73,7 +73,7 @@ export class BlockHeader {
* @throws Error if the hex string length is not 160
*/
static fromHex(hex: string): BlockHeader {
return this.fromBytes(Bytes.fromHex(hex).toBytes());
return this.fromBytes(Bytes.fromHex(hex).toUint8Array());
}
/**
@@ -119,7 +119,7 @@ export class BlockHeader {
* @returns 160-character hex string representing the block header
*/
toHex(): string {
return Bytes.fromBytes(this.toBytes()).toHex();
return Bytes.fromBytes(this.bytes).toHex();
}
/**
@@ -207,7 +207,7 @@ export class BlockHeader {
* @returns 32-byte array containing the block hash
*/
getHash(): Uint8Array {
return hash256(this.toBytes()).toReversed();
return hash256(this.bytes).toReversed();
}
/**
+15 -11
View File
@@ -15,16 +15,19 @@ import {
/**
* Bytes Primitive.
*/
export class Bytes {
protected readonly bytes: Uint8Array;
export class Bytes extends Uint8Array {
/**
* Instantiate Bytes from a Uint8Array.
*
* @param bytes {Uint8Array} The bytes to use.
*/
protected constructor(bytes: Uint8Array) {
this.bytes = bytes;
super(bytes);
}
// Override the constructor to ensure correct inheritance
static from(uint8Array: Uint8Array): Bytes {
return new Bytes(uint8Array);
}
/**
@@ -119,11 +122,11 @@ export class Bytes {
* @returns Bytes as a Base64 string.
*/
public toBase64(): string {
return binToBase64(this.bytes);
return binToBase64(this);
}
public toBigInt(): bigint {
return binToBigIntUintLE(this.bytes);
return binToBigIntUintLE(this);
}
/**
@@ -132,7 +135,7 @@ export class Bytes {
* @returns Bytes as a hex string.
*/
public toHex(): string {
return binToHex(this.bytes);
return binToHex(this);
}
/**
@@ -141,7 +144,7 @@ export class Bytes {
* @returns Bytes as a UTF8 string.
*/
public toUtf8(): string {
return binToUtf8(this.bytes);
return binToUtf8(this);
}
/**
@@ -149,11 +152,12 @@ export class Bytes {
*
* @returns Bytes as a Uint8Array.
*/
public toBytes(): Uint8Array {
return this.bytes;
public toUint8Array(): Uint8Array {
// NOTE: We clone the instance of Uint8Array so that we don't accidentally mutate it.
return new Uint8Array([...this]);
}
public invert(): Bytes {
return new Bytes(Uint8Array.from([...this.bytes].reverse()));
return new Bytes(Uint8Array.from([...this].reverse()));
}
}
+1 -1
View File
@@ -66,7 +66,7 @@ export class HDPrivateNode {
public static fromSeed(seed: Uint8Array | string): HDPrivateNode {
// Cast from hex if a string was provided.
if (typeof seed === 'string') {
seed = Bytes.fromHex(seed).toBytes();
seed = Bytes.fromHex(seed);
}
// If the seed is not 64 bytes, throw an error.
+11 -13
View File
@@ -98,7 +98,7 @@ export class PrivateKey {
*/
public static fromHex(privateKeyHex: string): PrivateKey {
// Convert the hex to binary (Uint8Array).
const privateKeyBin = Bytes.fromHex(privateKeyHex).toBytes();
const privateKeyBin = Bytes.fromHex(privateKeyHex);
// Create a Private Key Entity from the binary.
return new this(privateKeyBin);
@@ -268,7 +268,7 @@ export class PrivateKey {
*/
public signMessageRecoverableCompact(message: Uint8Array | string) {
if (typeof message === 'string') {
message = Bytes.fromUtf8(message).toBytes();
message = Bytes.fromUtf8(message);
}
const messageHash = sha256.hash(message);
@@ -292,7 +292,7 @@ export class PrivateKey {
*/
public signMessageCompact(message: Uint8Array | string) {
if (typeof message === 'string') {
message = Bytes.fromUtf8(message).toBytes();
message = Bytes.fromUtf8(message);
}
const messageHash = sha256.hash(message);
@@ -316,7 +316,7 @@ export class PrivateKey {
*/
public signMessageSchnorr(message: Uint8Array | string) {
if (typeof message === 'string') {
message = Bytes.fromUtf8(message).toBytes();
message = Bytes.fromUtf8(message);
}
const messageHash = sha256.hash(message);
@@ -340,7 +340,7 @@ export class PrivateKey {
*/
public signMessageDER(message: Uint8Array | string) {
if (typeof message === 'string') {
message = Bytes.fromUtf8(message).toBytes();
message = Bytes.fromUtf8(message);
}
const messageHash = sha256.hash(message);
@@ -363,10 +363,8 @@ export class PrivateKey {
*/
public signMessageElectron(message: string): Uint8Array {
// Compile the message.
const magicBytes = Bytes.fromUtf8(
'\x18Bitcoin Signed Message:\n',
).toBytes();
const messageBytes = Bytes.fromUtf8(message).toBytes();
const magicBytes = Bytes.fromUtf8('\x18Bitcoin Signed Message:\n');
const messageBytes = Bytes.fromUtf8(message);
const messageLengthBytes = bigIntToCompactUint(BigInt(messageBytes.length)); // hexToBin(messageBytes.length.toString(16)); //
const messageFinal = new Uint8Array([
...magicBytes,
@@ -406,7 +404,7 @@ export class PrivateKey {
// If a string was given, convert it from hex to bytes.
// TODO: Consider using Bytes class - it ensures valid hex.
if (typeof messageHash === 'string') {
messageHash = Bytes.fromHex(messageHash).toBytes();
messageHash = Bytes.fromHex(messageHash);
}
if (messageHash.length !== 32) {
@@ -441,7 +439,7 @@ export class PrivateKey {
// If a string was given, convert it from hex to bytes.
// TODO: Consider using Bytes class - it ensures valid hex.
if (typeof messageHash === 'string') {
messageHash = Bytes.fromHex(messageHash).toBytes();
messageHash = Bytes.fromHex(messageHash);
}
if (messageHash.length !== 32) {
@@ -476,7 +474,7 @@ export class PrivateKey {
// If a string was given, convert it from hex to bytes.
// TODO: Consider using Bytes class - it ensures valid hex.
if (typeof messageHash === 'string') {
messageHash = Bytes.fromHex(messageHash).toBytes();
messageHash = Bytes.fromHex(messageHash);
}
if (messageHash.length !== 32) {
@@ -511,7 +509,7 @@ export class PrivateKey {
// If a string was given, convert it from hex to bytes.
// TODO: Consider using Bytes class - it ensures valid hex.
if (typeof messageHash === 'string') {
messageHash = Bytes.fromHex(messageHash).toBytes();
messageHash = Bytes.fromHex(messageHash);
}
if (messageHash.length !== 32) {
+5 -5
View File
@@ -78,7 +78,7 @@ export class PublicKey {
* @returns The created Public Key entity.
*/
public static fromHex(publicKeyHex: string): PublicKey {
return new this(Bytes.fromHex(publicKeyHex).toBytes());
return new this(Bytes.fromHex(publicKeyHex));
}
/**
@@ -232,11 +232,11 @@ export class PublicKey {
signature: Uint8Array | string,
): boolean {
if (typeof messageHash === 'string') {
messageHash = Bytes.fromHex(messageHash).toBytes();
messageHash = Bytes.fromHex(messageHash);
}
if (typeof signature === 'string') {
signature = Bytes.fromHex(signature).toBytes();
signature = Bytes.fromHex(signature);
}
const validity = secp256k1.verifySignatureDER(
@@ -261,11 +261,11 @@ export class PublicKey {
signature: Uint8Array | string,
): boolean {
if (typeof messageHash === 'string') {
messageHash = Bytes.fromHex(messageHash).toBytes();
messageHash = Bytes.fromHex(messageHash);
}
if (typeof signature === 'string') {
signature = Bytes.fromHex(signature).toBytes();
signature = Bytes.fromHex(signature);
}
const validity = secp256k1.verifySignatureSchnorr(
+2 -2
View File
@@ -24,7 +24,7 @@ import {
* console.log(script.toHex());
*
* // Output as bytecode.
* console.log(script.toBytes());
* console.log(script);
* ```
*/
export class Script {
@@ -177,7 +177,7 @@ export class Script {
*/
public static fromHex(hex: string): Script {
// Return new Script entity constructed from the binary.
return this.fromBytes(Bytes.fromHex(hex).toBytes());
return this.fromBytes(Bytes.fromHex(hex));
}
/**
+4 -4
View File
@@ -212,7 +212,7 @@ export class Transaction extends BaseTransaction {
* @returns {Transaction} The created Transaction Primitive.
*/
public static fromHex(hex: string): Transaction {
const bytes = Bytes.fromHex(hex).toBytes();
const bytes = Bytes.fromHex(hex).toUint8Array();
return this.fromBytes(bytes);
}
@@ -319,7 +319,7 @@ export class TransactionResolved extends BaseTransaction {
* @returns {Transaction} The created Transaction Primitive.
*/
public static fromHex(hex: string, sourceOutputs: Array<any>) {
const bytes = Bytes.fromHex(hex).toBytes();
const bytes = Bytes.fromHex(hex).toUint8Array();
return this.fromBytes(bytes, sourceOutputs);
}
@@ -425,7 +425,7 @@ export class TransactionHash {
* @returns {TransactionHash} Instance of TransactionHash.
*/
public static fromHex(hex: string): TransactionHash {
const bytes = Bytes.fromHex(hex).toBytes();
const bytes = Bytes.fromHex(hex).toUint8Array();
return this.fromBytes(bytes);
}
@@ -469,7 +469,7 @@ export class TransactionHash {
* @returns {TransactionHash} Instance of TransactionHash.
*/
public static fromTransactionHex(transactionHex: string): TransactionHash {
const bytes = Bytes.fromHex(transactionHex).toBytes();
const bytes = Bytes.fromHex(transactionHex).toUint8Array();
return this.fromTransactionBytes(bytes);
}
+5 -5
View File
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import { Address } from '~/src/address';
import { Bytes } from '~/src/bytes';
import { Address } from '../src/address';
import { Bytes } from '../src/bytes';
describe('Success cases', () => {
// Test cases with different address representations
@@ -27,7 +27,7 @@ describe('Success cases', () => {
type: 'P2PKH',
payload: Bytes.fromHex(
'626025854e5a67ca5eb122e213f409ec842d45e4',
).toBytes(),
).toUint8Array(),
},
representations: {
cashAddr: 'bitcoincash:qp3xqfv9fedx0jj7ky3wyyl5p8kggt29usvlhrrxws',
@@ -35,13 +35,13 @@ describe('Success cases', () => {
tokenAddr: 'bitcoincash:zp3xqfv9fedx0jj7ky3wyyl5p8kggt29ust4yadq3r',
lockscriptBytes: Bytes.fromHex(
'76a914626025854e5a67ca5eb122e213f409ec842d45e488ac',
).toBytes(),
).toUint8Array(),
lockscriptHex: '76a914626025854e5a67ca5eb122e213f409ec842d45e488ac',
lockscriptASM:
'OP_DUP OP_HASH160 OP_PUSHBYTES_20 0x626025854e5a67ca5eb122e213f409ec842d45e4 OP_EQUALVERIFY OP_CHECKSIG',
hash160Bytes: Bytes.fromHex(
'626025854e5a67ca5eb122e213f409ec842d45e4',
).toBytes(),
).toUint8Array(),
hash160Hex: '626025854e5a67ca5eb122e213f409ec842d45e4',
},
},
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import { BlockHeader } from '~/src/block-header';
import { Bytes } from '~/src/bytes';
import { BlockHeader } from '../src/block-header';
import { Bytes } from '../src/bytes';
describe('Success cases', () => {
// Test cases with different block header representations
@@ -16,11 +16,11 @@ describe('Success cases', () => {
{
raw: Bytes.fromHex(
'0000602093ea70b294aeef69db85981bd387285824fc5af732e487010000000000000000135576e1ba9cae5509d60b4fbb9e66612f35b5ee8db264cb12612d46cc8db60b5e34a164d46b021864abbb1d',
).toBytes(),
).toUint8Array(),
representations: {
bytes: Bytes.fromHex(
'0000602093ea70b294aeef69db85981bd387285824fc5af732e487010000000000000000135576e1ba9cae5509d60b4fbb9e66612f35b5ee8db264cb12612d46cc8db60b5e34a164d46b021864abbb1d',
).toBytes(),
).toUint8Array(),
hex: '0000602093ea70b294aeef69db85981bd387285824fc5af732e487010000000000000000135576e1ba9cae5509d60b4fbb9e66612f35b5ee8db264cb12612d46cc8db60b5e34a164d46b021864abbb1d',
},
},
+2 -2
View File
@@ -51,7 +51,7 @@ describe('Success cases', () => {
},
toSeed: Bytes.fromHex(
'7066f43a742829f0edce7d8e3c31651df320f13913995a6500f4e515db372b35202227f923ff181defc35cea02f8468df39f8fa9d97a202d252e61562e4bbfb5',
).toBytes(),
).toUint8Array(),
toPhrase:
'goose just fan kidney barrel album wave crowd movie flame cousin exhaust',
toPhraseWithoutPassphrase:
@@ -124,7 +124,7 @@ describe('Success cases', () => {
},
toSeed: Bytes.fromHex(
'2f706756ab2083f7ce07c0c0fcde8f14f062da5de839f42763262b25e0d871f03dd2b561d55eaacc471c94da656ee7b0007fb48a64e31ec91a1a2ba706b46b89',
).toBytes(),
).toUint8Array(),
toPhrase:
'goose just fan kidney barrel album wave crowd movie flame cousin exhaust GeneralProtocols!',
toPhraseWithoutPassphrase:
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { Satoshis } from '~/src/satoshis';
import { Satoshis } from '../src/satoshis';
describe('Success cases', () => {
// Test cases with single representation for each unit
+7 -7
View File
@@ -17,11 +17,11 @@ describe('Success cases', () => {
{
raw: Bytes.fromHex(
'76a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac',
).toBytes(),
).toUint8Array(),
representations: {
bytes: Bytes.fromHex(
'76a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac',
).toBytes(),
).toUint8Array(),
asm: 'OP_DUP\nOP_HASH160\nOP_PUSHBYTES_20\n0x876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d6\nOP_EQUALVERIFY\nOP_CHECKSIG',
hex: '76a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac',
},
@@ -38,19 +38,19 @@ describe('Success cases', () => {
fromMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, representations }) => {
const script = Script[name](representations[key]);
expect(script.toRaw()).toStrictEqual(raw);
expect(script.toRaw()).toEqual(raw);
});
});
// Test generic from method
it.each(testCases)('from(Uint8Array)', ({ raw, representations }) => {
const script = Script.from(representations.bytes);
expect(script.toRaw()).toStrictEqual(raw);
expect(script.toRaw()).toEqual(raw);
});
it.each(testCases)('from(string)', ({ raw, representations }) => {
const script = Script.from(representations.hex);
expect(script.toRaw()).toStrictEqual(raw);
expect(script.toRaw()).toEqual(raw);
});
// Test to methods with their respective types
@@ -63,14 +63,14 @@ describe('Success cases', () => {
toMethods.forEach(({ name, key }) => {
it.each(testCases)(`${name}()`, ({ raw, representations }) => {
const script = Script.fromRaw(raw);
expect(script[name]()).toStrictEqual(representations[key]);
expect(script[name]()).toEqual(representations[key]);
});
});
// Test fromRaw specifically
it.each(testCases)('fromRaw() creates correct instance', ({ raw }) => {
const script = Script.fromRaw(raw);
expect(script.toRaw()).toStrictEqual(raw);
expect(script.toRaw()).toEqual(raw);
});
});
+3 -1
View File
@@ -157,7 +157,9 @@ export class SSEClient {
this.messageBuffer = new Uint8Array([...this.messageBuffer, ...chunk]);
const events: SSEvent[] = [];
const lines = this.textDecoder.decode(this.messageBuffer).split(/\r\n|\r|\n/);
const lines = this.textDecoder
.decode(this.messageBuffer)
.split(/\r\n|\r|\n/);
let currentEvent: Partial<SSEvent> = {};
let completeEventCount = 0;