Author SHA1 Message Date
Harvmaster 8dbf98c2bc Merge branch 'xo-engine-support' into add-article-vision-to-reality-4 2026-10-12 03:43:06 +00:00
Harvmaster 949c5df2d8 Merge branch 'development' into add-article-vision-to-reality-4 2026-08-08 15:57:29 +00:00
Emergent Reasons e39d6508cc Merge branch 'fix-pipeline-replacing-mermaid' into 'development'
Update pipeline to Node 24. Update Lerna. Update Vitepress to Beta. Replace...

See merge request GeneralProtocols/xo/stack!149
2026-08-08 11:06:50 +00:00
HarvmasterandEmergent Reasons 1c3e920c85 Update pipeline to Node 24. Update Lerna. Update Vitepress to Beta. Replace... 2026-08-08 11:06:49 +00:00
emergent-reasons 8875e3f5ff recommended fixups 2026-08-07 15:56:52 +09:00
emergent-reasons cbb223cbda base material 2026-08-07 15:09:11 +09:00
12 changed files with 4751 additions and 5950 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
image: node:22
image: node:24
# We use stages for clarity of intent, and "needs" for dependencies and sequencing.
# "needs" should work in parallel where acceptable, and get any artifacts directly from the source step.
+4173 -5725
View File
File diff suppressed because it is too large Load Diff
+10 -9
View File
@@ -54,25 +54,26 @@
"devDependencies": {
"@eslint/js": "^9.10.0",
"@types/eslint__js": "^8.42.3",
"@typescript-eslint/eslint-plugin": "^8.5.0",
"@typescript-eslint/parser": "^8.5.0",
"@typescript-eslint/eslint-plugin": "^8.66.0",
"@typescript-eslint/parser": "^8.66.0",
"@vitest/coverage-v8": "^3.2.4",
"eslint": "^9.10.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^5.2.1",
"lerna": "^9.0.3",
"lerna": "^10.0.0",
"madge": "^8.0.0",
"prettier": "^3.3.3",
"typescript": "5.7.2",
"typescript-eslint": "^8.6.0",
"typescript": "5.9.3",
"typescript-eslint": "^8.66.0",
"vitest": "^3.2.4"
},
"overrides": {
"vitepress": {
"vite": "^7.1.10"
"nx": {
"brace-expansion": "5.0.9"
},
"vitepress-plugin-mermaid": {
"vite": "^7.1.10"
"lerna": {
"js-yaml": "4.3.1",
"tar": "7.5.22"
}
}
}
+3 -3
View File
@@ -17,13 +17,13 @@
"@monaco-editor/loader": "^1.5.0",
"@xocash/primitives": "*",
"@xocash/stack": "*",
"monaco-editor": "^0.52.2"
"monaco-editor": "^0.52.2",
"vitepress-mermaid-renderer": "^1.2.0"
},
"devDependencies": {
"typedoc": "^0.28.14",
"typedoc-plugin-markdown": "^4.3.2",
"typedoc-vitepress-theme": "^1.1.1",
"vitepress": "^1.6.4",
"vitepress-plugin-mermaid": "^2.0.17"
"vitepress": "2.0.0-alpha.18"
}
}
+189 -192
View File
@@ -1,5 +1,4 @@
import { defineConfig } from 'vitepress';
import { withMermaid } from 'vitepress-plugin-mermaid';
import typedocSidebar from '../api/typedoc-sidebar.json';
// Node imports for auto blog sidebar (works at docs/src/.vitepress)
@@ -39,8 +38,8 @@ function toPascalCase(str: string) {
.map((word) => {
if (word.length === 0) return '';
if (SPECIAL_CASES[word]) {
return SPECIAL_CASES[word];
if (word in SPECIAL_CASES) {
return SPECIAL_CASES[word as keyof typeof SPECIAL_CASES];
}
// Capitalize first letter, lowercase rest
@@ -114,210 +113,208 @@ function blogSidebar() {
}
// https://vitepress.dev/reference/site-config
export default withMermaid(
defineConfig({
outDir: '../dist',
title: 'XO Stack',
description:
'XO Stack: Building smart applications on the Bitcoin Cash Network',
head: [
// Favicon.
['link', { rel: 'icon', href: '/favicon.png' }],
// Basic OpenGraph tags
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:title', content: 'XO Stack' }],
[
'meta',
{
property: 'og:description',
content:
'XO Stack: Building smart applications on the Bitcoin Cash Network',
},
],
[
'meta',
{
property: 'og:image',
content: 'https://stack.xo.cash/stack-opengraph.jpg',
},
],
['meta', { property: 'og:url', content: 'https://stack.xo.cash/' }],
// Optional additional tags
['meta', { property: 'og:site_name', content: 'XO Stack' }],
// Twitter Card tags (optional but recommended)
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
['meta', { name: 'twitter:site', content: '@yourtwitterhandle' }],
],
themeConfig: {
// Set the logo
logo: {
light: '/logo-light.svg',
dark: '/logo-dark.svg',
export default defineConfig({
outDir: '../dist',
title: 'XO Stack',
description:
'XO Stack: Building smart applications on the Bitcoin Cash Network',
head: [
// Favicon.
['link', { rel: 'icon', href: '/favicon.png' }],
// Basic OpenGraph tags
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:title', content: 'XO Stack' }],
[
'meta',
{
property: 'og:description',
content:
'XO Stack: Building smart applications on the Bitcoin Cash Network',
},
],
[
'meta',
{
property: 'og:image',
content: 'https://stack.xo.cash/stack-opengraph.jpg',
},
],
['meta', { property: 'og:url', content: 'https://stack.xo.cash/' }],
// Enable search functionality.
// TODO: Re-enable once we release documentation.
/*
// Optional additional tags
['meta', { property: 'og:site_name', content: 'XO Stack' }],
// Twitter Card tags (optional but recommended)
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
['meta', { name: 'twitter:site', content: '@yourtwitterhandle' }],
],
themeConfig: {
// Set the logo
logo: {
light: '/logo-light.svg',
dark: '/logo-dark.svg',
},
// Enable search functionality.
// TODO: Re-enable once we release documentation.
/*
search: {
provider: 'local',
},
*/
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
// TODO: Re-enable once we release documentation
// { text: 'Documentation', link: '/quick-start' },
{ text: 'Blog', link: '/blog' },
{ text: 'Tools', link: '/tools/primitives' },
{ text: 'Playground', link: '/playground' },
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
// TODO: Re-enable once we release documentation
// { text: 'Documentation', link: '/quick-start' },
{ text: 'Blog', link: '/blog' },
{ text: 'Tools', link: '/tools/primitives' },
{ text: 'Playground', link: '/playground' },
],
sidebar: {
// Default Side Menu.
'/': [
{
text: 'Guide',
items: [
{ text: 'Quick Start', link: '/quick-start' },
{ text: 'Architecture', link: '/architecture' },
{ text: 'Best Practices', link: '/api-examples' },
{ text: 'Contributing', link: '/contributing' },
],
},
{
text: 'Frameworks',
items: [
{ text: 'Vue', link: '/frameworks/vue' },
{ text: 'React', link: '/frameworks/react' },
{ text: 'Svelte', link: '/frameworks/svelte' },
],
},
{
text: 'API',
items: convertNavToPascalCase(typedocSidebar),
},
],
sidebar: {
// Default Side Menu.
'/': [
{
text: 'Guide',
items: [
{ text: 'Quick Start', link: '/quick-start' },
{ text: 'Architecture', link: '/architecture' },
{ text: 'Best Practices', link: '/api-examples' },
{ text: 'Contributing', link: '/contributing' },
],
},
{
text: 'Frameworks',
items: [
{ text: 'Vue', link: '/frameworks/vue' },
{ text: 'React', link: '/frameworks/react' },
{ text: 'Svelte', link: '/frameworks/svelte' },
],
},
{
text: 'API',
items: convertNavToPascalCase(typedocSidebar),
},
],
// Side-menu for all routes under /tools/
'/tools/': [
{
text: 'XO Tools',
items: [
{
text: 'Primitives',
link: '/tools/primitives',
collapsed: false,
items: [
{ text: 'Address', link: '/tools/primitives#address' },
{
text: 'Block Header',
link: '/tools/primitives#block-header',
},
{ text: 'Bytes', link: '/tools/primitives#bytes' },
{
text: 'Encrypter',
link: '/tools/primitives#encrypter',
},
{
text: 'Hasher',
link: '/tools/primitives#hasher',
},
{
text: 'HD Private Node',
link: '/tools/primitives#hd-private-node',
},
{
text: 'HD Public Node',
link: '/tools/primitives#hd-public-node',
},
{
text: 'Mnemonic',
link: '/tools/primitives#mnemonic',
},
{
text: 'Private Key',
link: '/tools/primitives#private-key',
},
{
text: 'Public Key',
link: '/tools/primitives#public-key',
},
{
text: 'Satoshis',
link: '/tools/primitives#satoshis',
},
{
text: 'Script',
link: '/tools/primitives#script',
},
{
text: 'Transaction',
link: '/tools/primitives#transaction',
},
],
},
{
text: 'Stack',
collapsed: false,
items: [
{
text: 'Blockchain',
link: '/tools/stack/blockchain',
items: [
{
text: 'Electrum',
link: '/tools/stack/blockchain#electrum',
},
],
},
],
},
],
},
],
// Blog sidebar (auto-generated)
'/blog/': blogSidebar(),
},
socialLinks: [
// { icon: 'github', link: 'https://gitlab.com/GeneralProtocols/xo/stack' },
{ icon: 'x', link: 'https://x.com/xo_bitcoincash' },
// Side-menu for all routes under /tools/
'/tools/': [
{
text: 'XO Tools',
items: [
{
text: 'Primitives',
link: '/tools/primitives',
collapsed: false,
items: [
{ text: 'Address', link: '/tools/primitives#address' },
{
text: 'Block Header',
link: '/tools/primitives#block-header',
},
{ text: 'Bytes', link: '/tools/primitives#bytes' },
{
text: 'Encrypter',
link: '/tools/primitives#encrypter',
},
{
text: 'Hasher',
link: '/tools/primitives#hasher',
},
{
text: 'HD Private Node',
link: '/tools/primitives#hd-private-node',
},
{
text: 'HD Public Node',
link: '/tools/primitives#hd-public-node',
},
{
text: 'Mnemonic',
link: '/tools/primitives#mnemonic',
},
{
text: 'Private Key',
link: '/tools/primitives#private-key',
},
{
text: 'Public Key',
link: '/tools/primitives#public-key',
},
{
text: 'Satoshis',
link: '/tools/primitives#satoshis',
},
{
text: 'Script',
link: '/tools/primitives#script',
},
{
text: 'Transaction',
link: '/tools/primitives#transaction',
},
],
},
{
text: 'Stack',
collapsed: false,
items: [
{
text: 'Blockchain',
link: '/tools/stack/blockchain',
items: [
{
text: 'Electrum',
link: '/tools/stack/blockchain#electrum',
},
],
},
],
},
],
},
],
// This is the outline on the right-hand side of the page.
outline: {
level: [2, 4],
label: 'On this page',
},
// Blog sidebar (auto-generated)
'/blog/': blogSidebar(),
},
// Set the dev and build targets to "esnext"
// NOTE: We do this so that we can support top-level-await.
vite: {
esbuild: {
socialLinks: [
// { icon: 'github', link: 'https://gitlab.com/GeneralProtocols/xo/stack' },
{ icon: 'x', link: 'https://x.com/xo_bitcoincash' },
],
// This is the outline on the right-hand side of the page.
outline: {
level: [2, 4],
label: 'On this page',
},
},
// Set the dev and build targets to "esnext"
// NOTE: We do this so that we can support top-level-await.
vite: {
esbuild: {
target: 'esnext',
},
build: {
target: 'esnext',
},
optimizeDeps: {
esbuildOptions: {
target: 'esnext',
},
build: {
target: 'esnext',
},
optimizeDeps: {
esbuildOptions: {
target: 'esnext',
},
},
},
resolve: {
// Configure to use browser version of @xocash/stack
conditions: ['browser', 'import', 'module', 'default'],
},
// SSR has its own separate ssr.resolve.conditions that must be explicitly set since not follow global resolve conditions.
ssr: {
resolve: {
// Configure to use browser version of @xocash/stack
conditions: ['browser', 'import', 'module', 'default'],
},
// SSR has its own separate ssr.resolve.conditions that must be explicitly set since not follow global resolve conditions.
ssr: {
resolve: {
// Configure to use browser version of @xocash/stack
conditions: ['browser', 'import', 'module', 'default'],
},
},
},
}),
);
},
});
+50 -4
View File
@@ -1,15 +1,61 @@
// https://vitepress.dev/guide/custom-theme
import { h, nextTick, watch } from 'vue';
import type { Theme } from 'vitepress';
import { useData } from 'vitepress';
import DefaultTheme from 'vitepress/theme';
import Layout from './Layout.vue';
import './style.css';
import './custom.css';
import './tools.css';
import './mermaid-overrides.css';
import './mermaid-lightbox.client';
import { createMermaidRenderer } from 'vitepress-mermaid-renderer';
export default {
extends: DefaultTheme,
Layout,
enhanceApp({ _app, _router, _siteData }) {},
Layout: () => {
const { isDark, localeIndex } = useData();
const initMermaid = () => {
const mermaidRenderer = createMermaidRenderer({
theme: isDark.value ? 'dark' : 'forest',
});
mermaidRenderer.setToolbar({
showLanguageLabel: false,
downloadFormat: 'svg',
fullscreenMode: 'browser',
desktop: {
copyCode: 'enabled',
toggleFullscreen: 'enabled',
resetView: 'enabled',
zoomOut: 'enabled',
zoomIn: 'enabled',
zoomLevel: 'enabled',
download: 'enabled',
},
fullscreen: {
copyCode: 'disabled',
toggleFullscreen: 'enabled',
resetView: 'disabled',
zoomLevel: 'disabled',
download: 'enabled',
},
});
};
nextTick(initMermaid);
watch(
() => [isDark.value, localeIndex.value] as const,
() => {
initMermaid();
},
);
return h(Layout);
},
enhanceApp() {},
} satisfies Theme;
@@ -0,0 +1,295 @@
---
title: 'Vision to Reality 4: Revving the Engine'
date: 2026-08-05
description: Realization of the XO vision, part 4 covering recent improvements to the XO stack and a short roadmap.
author: Jonathan Silverblood
tags: [templates, engine, technical audience]
---
# Vision to Reality 4: Revving the Engine
This article draws up a short roadmap to set expectations for the upcoming BCH Blaze hackathon in November 2026 and covers recent improvements to the XO stack.
----
```mermaid
mindmap
root )Responsibilities(
[INVITATIONS]
(Connects)
📱 Apps
👛 Wallets
👥 Users
(Coordinates)
⚡ Actions
🧩 Variables
📥 Inputs
📤 Outputs
[TEMPLATES]
(Describes)
🧩 Variables
💸 Transactions
📥 Inputs
📤 Outputs
🏷️ Lockscripts
(Defines)
⚡ Actions
💸 Transactions
💾 Data
[ENGINE]
(Generates)
🔑 Keys
🪪 Identities
✉️ Invitations
💸 Transactions
(Manages)
🤫 Secrets
🧩 Variables
📤 Outputs
🏷️ Lockscripts
```
----
## Roadmap
### Tools and tutorials
In preparation for the Blaze hackathon we are planning to release additional articles and tooling to help participants build on XO.
- Publish article on high-level XO concepts
- Publish article on best practices when making templates
- Publish article on current tools and limitations
- Release alpha version of a command-line or text interface wallet
- Release alpha version of graphical XO interactive debug tool
### Token Support
Currently, the XO engine is only capable of working with tokens in a very limited capacity. For example, while it is possible to define token balance estimations in templates, they are currently not being used.
Full token support is a target goal and is one of the key area for improvement leading up to the hackathon, with explicit support for token genesis, token minting, coin selection, balance estimation and much more.
### Improved Naming
In templates today, the words `locking script` and `locking bytecode` are mixed and the result is vagueness and inconsistency.
All uses that directly refer to actual underlying bytecode will be renamed to either `lockingBytecode` or `unlockingBytecode`. Similarly, all references to `lockingScript` or `unlockingScript` will refer to named and documented entities that uses `lockingBytecode` or `unlockingBytecode` respectively in their definitions.
As a result, we will also introduce an `unlockingScripts` section similar to the `lockingScripts`. Here unlocking scripts can be described properly with names, descriptions and other metadata.
### Default Outputs Instead of Inheritance
One of the many goals of the XO stack is to ensure that no funds are lost. To this end, we previously added data to locking scripts that allowed the engine to classify and handle unknown transactions, when those transactions have outputs to monitored locking scripts in the engine.
Previously, any output to a known locking script that lacked classification by the engine, would inherit properties of the locking script and become an implicit default output.
Going forward, we are making this explicit by adding a reference to an explicit default output in locking scripts and moving all related functionality to this definition.
This has two big impacts. First, it makes it more explicit that a template is responsible for handling default outputs on locking scripts. Second, it encourages template authors to provide explicit names and descriptions for the default locking script and for any uncoordinated outputs that spend to it.
### Events
Currently, users of the XO stack have to poll for data when they need it. Going forward, we will implement proper event emission so that applications can know when new data is available. This will include all blockchain triggered changes, but also information on when the engine starts and completes state synchronization, when wallet balance estimations are updated and much more.
### Improved Documentation
While we are still doing early alpha releases and breaking changes are still to be expected, we will make a concentrated effort to improve the technical package documentation ahead of the BCH Blaze hackathon.
This includes tutorials on how to get started, better structure on the available documentation and more practical examples.
## Recent Changes
A lot has happened since our last article and we have been hard at work making template, engine and tooling updates, as well as presenting at the [BCH Bliss](https://www.bliss.cash) conference.
Here is a short summary of what has improved:
- new BIP43 compliant derivation scheme
- improved reservation handling
- added expiration date on invitations and resources
- introduce intents as a concept
- cleared up and isolated state better
- dynamic template roles instances
- added simple automated coin selection
- improved change handling with policies
- implemented a large number of new primitive data types
- various quality-of-life and developer experience improvements
### Derivation
Under the hood, a lot has changed with regards to how XO derives cryptographic keys. Previously, we used a scheme that was a variation of [BIP 44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) but that was **not** BIP44 compliant.
The new derivation paths are shorter and simpler and are instead based on [BIP 43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki):
> `m/XO'/ACCOUNT_OR_TEMPLATE'/USE_CASE'/INDEX`
Where `XO = 2415` and each template has its own hardened keyspace. Keys are further separated within each keyspace by a hardened `USE_CASE`.
This setup isolates keys that a template author might be able to influence from keys associated with other templates, or used by the engine in various ways.
For account-wide usage, the `ACCOUNT_OR_TEMPLATE` is set to 0, allowing for a non-templated keyspace, which also has hardened `USE_CASE` isolation.
### Reservations
When preparing to take action, the engine reserves resources that will be used by the action. This part of the code has been improved with better error reporting and more clear naming, and it is now possible for applications to reserve resources manually.
Technically, the reservation has changed from being a boolean, to being `reservedBy` with a reference. Requesting a reservation for an already reserved resource returns an error.
### Expirations
Participation in invitations now come with expiration dates, so that reserved resources can be restored when actions are not completed within expected timeframes.
Note that the expiration dates are advisory rather than enforced, and serve only to prevent permanent reservation of resources. If an action is attempted after an expiration has happened and the resources are still available, the action will still succeed.
### Intents
We have added a concept of reuseable intents which define the preconditions to creating resources in the engine.
This allowed some work to be specified upfront with the new `generate:` keyword, rather than being inferred by the engine after a requirement has been encountered.
For example, here is an intent to create a P2PKH change output:
```ts
{
output: 'changeOutput',
role: 'receiver',
generate: [ 'ownerKey' ],
}
```
Further, this reconciles all differences between starting actions and follow-up actions, and serves as the framework for creating various structures such as locking scripts, outputs and other data.
### State
We have moved all state representations to only exist in outputs or locking scripts, whereas some were defined in requirements previously.
Note that we are further planning to move the inherited output handling for uncoordinated outputs from the locking scripts into a referenced `defaultOutput`, which will fully move **all** templated state representation to exist only in outputs as originally intended.
### Slots
Templates can now specify a minimum and maximum number of instances each role requires via the `slots:` keyword.
This removes the need for fixed roles and allows for arbitrary and fungible role takers, such as pledgers in fundraising campaigns or signers in multisig wallets.
### Coin Selection
Before automated coin selection, applications had to provide their inputs manually and the engine was only able to unlock inputs from the same template.
With the initial implementation of naive coin selection, the engine can now pick coins that are selectable from any template to satisfy requirements for satoshis when performing an action.
### Change
In previous versions of the engine, change was not handled automatically and instead implementations had to provide the change output by hand.
Note that change is a difficult concept to get right, as there are usecases that have conflicting requirements. It is easy to break user expectation when doing naive coin selection:
- Spending from a multisig template and generating a singlesig change.
- Spending from a privacy focused template and generating a non-private change.
The long-term solution for this is introduction of a change policy which controls how the engine automates creation of change outputs.
As a result, templates can now define a change policy if they have specific needs:
```ts
// Sets optional default values to be used with this template.
defaults:
{
change:
{
output: 'changeOutput',
role: 'receiver',
},
}
```
The engine follows a strict order when choosing what change policy to apply, allowing for fine-grained control by the application:
- `override` policy if manually provided by the application
- `default` policy in the template linked to the spent input
- `fallback` policy previously configured by the application
*If no change policy is available, the engine will refuse to take action when change is required.*
With this policy framework in place, a privacy focused wallet can guarantee that change will never go to a non-private output, either by setting an `override` policy, or by only allowing templates with acceptable privacy.
At the same time, if a wallet does not have strict change requirements they can set a `fallback` policy instead, and will then use `default` policies from the templates they are spending from when available.
Currently, the engine only supports the template `default` and engine `fallback` policies. The manual `override` policy will be supported soon.
### Primitives
A number of new primitives have been added, and all primitives are validated on construction, including new run-time validation.
**New primitive: Satoshis**
In various places in XO code, we used to have **bigints** for satoshi values, but this has been changed in favor of a dedicated `satoshis` primitive which additionally validates that the amount falls in reasonable ranges representable by the **CashVM**.
**New primitive: Transaction Hash**
We used to represent transaction hashes as strings, but this has been changed in favor of a dedicated `transaction hash` primitive.
This change makes it less likely that another string is used in a place where we should be using a transaction hash, and the primitive further validates the length and format of transaction hashes.
**New primitive: Extended Public Keys**
Previously we used to have a single public key for each participant in an invitation, which resulted in key re-use and potential for adversaries to control what data to sign with a key, risking cryptographic information leak.
By adding an `extended public key` primitive and using that in invitations, each update to an invitation now signs with a unique key. This reduces possible attack surface by removing key re-use in invitations.
**New primitive: Timestamps**
Representing time in script has long been a solved problem and well defined by the **CashVM**, but representing time to end-users in templates required better tooling on the engine side.
By adding an `timestamp` primitive we can extend support for end-user presentation of time, starting with the `iso8601` standard formatting.
```ts
// Example CashASM demonstrating timestamp usage.
description: 'Last seen: <myTimestamp.iso8601>',
```
## Other improvements
### Passing Identifiers
Previously it was required that developers pass full objects for things like `invitations` every time they needed to interact with them. This has been changed so that only identifiers are needed for items that are cached by default.
```ts
const { invitationIdentifier } = await engine.createInvitation();
// Later, when signing the invitation
await engine.signInvitiation(invitationIdentifier);
```
### Isolated Database Per-Seed.
To avoid conflicts when using the same application loaded with multiple seeds, we have added database isolation on a per-seed basis.
This also makes it possible to run multiple instances of the engine with different seeds, allowing multiple accounts within the same application.
### Improved Invitation Handling
Invitations can now be easily shared with import and export functionality, creating a flexible starting point for building transport protocols and standards for sending invitations between different wallets.
To prevent possible leakage of cryptographic entropy, each step of an invitation is now signed by a unique key, rather than a shared key per-invitation.
After executing an action from an invitation, the resulting `data` or `transaction` data structures are returned to the caller.
### Template Validation
We have improved validation prior to importing templates with both syntax and range checks, and ensured that all errors are properly reported with actionable error messages.
### Applicaton State
In additional to templated state, we have added support for engine and application state.
This is used by the engine to remember things like how to create default receiving addresses, and can be used by applications to store things like wallet settings.
### Rigorous Testing
Many parts of the XO stack have significantly improved their automated testing for additional quality assurance.
### P2PKH
P2PKH is the current industry standard for wallets. We have continued to improve the P2PKH template with better documentation, more human language and more explicit handling.
+12 -3
View File
@@ -36,20 +36,26 @@ export class Encrypter {
...opts,
};
// Something about the browser crypto API. It needs a "clean" Uint8Array, where TS could see the construction or something
const cleanKey = new Uint8Array(key);
// Create a key derivation key
const keyMaterialBase = await crypto.subtle.importKey(
'raw',
key,
cleanKey,
'PBKDF2',
false,
['deriveBits', 'deriveKey'],
);
// Something about the browser crypto API. It needs a "clean" Uint8Array, where TS could see the construction or something
const cleanSalt = new Uint8Array(encryptionOpts.salt);
// Derive the actual encryption key
const derivedKey = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: encryptionOpts.salt,
salt: cleanSalt,
iterations: encryptionOpts.iterations,
hash: 'SHA-256',
},
@@ -87,6 +93,9 @@ export class Encrypter {
// Generate random IV
const iv = crypto.getRandomValues(new Uint8Array(this.opts.ivLength));
// Something about the browser crypto API. It needs a "clean" Uint8Array, where TS could see the construction or something
const cleanData = new Uint8Array(data);
// Encrypt the data
const encryptedData = await crypto.subtle.encrypt(
{
@@ -94,7 +103,7 @@ export class Encrypter {
iv,
},
this.key,
data,
cleanData,
);
// Combine IV and encrypted data
+1 -1
View File
@@ -41,7 +41,7 @@
"@generalprotocols/oracle-client": "^0.0.1-development.11878773826",
"@xocash/primitives": "*",
"async-mutex": "^0.5.0",
"better-sqlite3": "^11.8.1",
"better-sqlite3": "^13.0.3",
"idb": "8.0.3",
"json-to-graphql-query": "^2.3.0",
"msgpackr": "^1.11.2",
@@ -297,10 +297,16 @@ export class StoreRemoteHTTP<T = unknown> extends BaseStore<
'X-Timestamp': timestamp.toString(),
};
const requestOptions = {
const body = payload ? pack(payload) : pack({});
const bodyBuffer = new Uint8Array(body.buffer);
const requestOptions: RequestInit = {
method: 'POST',
headers,
body: payload ? pack(payload) : pack({}),
// Because we support the DOM types, it doesnt register this UInt8Array as an acceptable BodyInit, so we need to cast it to unknown.
// Could be fixed in a later version of typescript, but there are build issues atm.
// TODO: (not really, unless we intend to use this code) Remove the type cast.
body: bodyBuffer as unknown as BodyInit,
};
// Use the URL class to compile the full path.
@@ -4,19 +4,18 @@
// However, Chaingraph backend is currently broken for many queries, so let's not do this yet.
import { beforeAll, describe, it, expect } from 'vitest';
import {
BaseBlockchain,
BlockchainChaingraph,
BlockchainElectrum,
} from '../../src/index.js';
import { BaseBlockchain, BlockchainElectrum } from '../../src/index.js';
const adapters: Record<
string,
() => Promise<BaseBlockchain<unknown, unknown>>
> = {
chaingraph: async () => {
return await BlockchainChaingraph.from();
},
// Commenting out since the chaingraph endopoints we are hitting are currently broken.
// demo won't allow non-browser clients
// Pat's server isn't responding to block height requests correctly (or we are using an old version, which the demo server is also running).
// chaingraph: async () => {
// return await BlockchainChaingraph.from();
// },
electrum: async () => {
return await BlockchainElectrum.from();
},
@@ -59,7 +58,7 @@ describe.each(Object.entries(adapters))('%s Adapter', (name, createAdapter) => {
});
it(`${name}.fetchBlockHeader() `, async () => {
const result = await blockchain.fetchBlockHeader(testBlockHeight);
const result = await blockchain.fetchBlockHeader(1);
expect(result).toBeDefined();
expect(result.toHex()).toBeTypeOf('string');
@@ -6,7 +6,7 @@ import {
monitorSyncTipsSchema,
} from '../../src/blockchain/blockchain-chaingraph-helpers.js';
const chaingraphServer = 'https://demo.chaingraph.cash/v1/graphql';
const chaingraphServer = 'https://gql.chaingraph.pat.mn/v1/graphql';
describe('GraphQLClient', () => {
const graphQLClient = new GraphQLClient(chaingraphServer);