Monorepo stuff

Signed-off-by: James Zuccon <zuccon@gmail.com>
This commit is contained in:
James Zuccon
2024-09-02 20:05:35 +10:00
parent f2210e631a
commit 2ac7af4069
62 changed files with 13227 additions and 149 deletions
+5
View File
@@ -8,3 +8,8 @@ yarn.lock
yarn-error.log
unit-tests.xml
e2e-tests.xml
# Ignore Vitepress builds/cache/generated-API.
docs/.vitepress/dist
docs/.vitepress/cache
docs/api
+31
View File
@@ -0,0 +1,31 @@
image: node:20
stages:
- docs
cache:
# The cache is shared for same values of cache key
# Here, we use CI_PIPELINE_ID which is defined as:
# "The instance-level ID of the current pipeline. This ID is unique across all projects on the GitLab instance."
# This should ensure that we only cache the "node_modules" folder for this given pipeline instance.
# Refer to this docs to know more about the predefined variables used in the key here: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
key: $CI_PIPELINE_ID
# configure the path to be cached
paths:
- node_modules/
before_script:
- npm install
- npm run build
# Publish the documentation to GitLab Pages (for now).
pages:
stage: publish
script:
- npm run docs:build
- mv docs/.vitepress/dist public
artifacts:
paths:
- public
only:
- main
+93 -1
View File
@@ -1,3 +1,95 @@
# XO Stack
TODO
This is a monorepo project containing multiple packages managed with NPM workspaces.
## Project Structure
```
xo/
├── packages/
│ ├── primitives/
│ ├── wallet/
│ └── vue/
├── package.json
└── README.md
```
## Prerequisites
- Node.js (version 20.0.0 or later)
- NPM (version 7 or later, which includes workspaces support)
## Installation
To install all dependencies for all packages in the monorepo, run the following command from the root directory:
```bash
npm install
```
This command will install dependencies for the root project and all packages in the `packages/` directory.
## Building Packages
To build all packages, run the following command from the root directory:
```bash
npm run build
```
This will execute the build script for each package in the monorepo.
To build a specific package, you can use the following command:
```bash
npm run build --workspace=@my-monorepo/<package-name>
```
Replace `<package-name>` with the name of the package you want to build (e.g., primitives, network-wallet, or vue).
## Running Tests
To run tests for all packages:
```bash
npm test
```
To run tests for a specific package:
```bash
npm test --workspace=@my-monorepo/<package-name>
```
## Development
During development, you can use the following command to watch for changes and rebuild packages automatically:
```bash
npm run dev
```
## Useful NPM Workspace Commands
- List all packages in the workspace:
```bash
npm ls -w
```
- Run a command in a specific package:
```bash
npm run <command> --workspace=@my-monorepo/<package-name>
```
- Add a dependency to a specific package:
```bash
npm install <dependency> --workspace=@my-monorepo/<package-name>
```
## Contributing
Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+80
View File
@@ -0,0 +1,80 @@
import { defineConfig } from 'vitepress';
import typedocSidebar from '../api/typedoc-sidebar.json';
function capitalizeFirstLetter(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// NOTE: We can filter the generated sidebar to produce relevant links to documentation.
// See the contents of the typedocSidear import for structure.
function classesOnlySidebar() {
return typedocSidebar.map((rootItem) => {
const srcItem = rootItem.items.find(
(childItem) => childItem.text === 'src'
);
const classes = srcItem?.items.find(
(childItem) => childItem.text === 'Classes'
);
return {
...rootItem,
text: capitalizeFirstLetter(rootItem.text),
items: classes?.items,
};
});
}
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: 'XO Stack',
description: 'XO Stack',
themeConfig: {
// Enable search functionality.
search: {
provider: 'local',
},
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Tools', link: '/tools' },
],
sidebar: [
{
text: 'Guide',
items: [
{ text: 'Quick Start', link: '/markdown-examples' },
{ text: 'Best Practices', link: '/api-examples' },
],
},
{
text: 'Frameworks',
items: [
{ text: 'Vue', link: '/frameworks/vue' },
{ text: 'React', link: '/frameworks/react' },
{ text: 'Svelte', link: '/frameworks/svelte' },
],
},
{
text: 'API',
// NOTE: In future, we may want to manually specify API items in the sidebar.
// This would allow us to keep some experimental things undocumented.
// But, for now, let's use an autogenerated sidebar so that we don't have to maintain this.
// We can polish it later.
items: classesOnlySidebar(),
},
],
socialLinks: [
{ icon: 'github', link: 'https://gitlab.com/GeneralProtocols/xo/stack' },
],
// This is the outline on the right-hand side of the page.
outline: {
level: [2, 3],
label: 'On this page',
},
},
});
+17
View File
@@ -0,0 +1,17 @@
// https://vitepress.dev/guide/custom-theme
import { h } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import './style.css'
export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
// https://vitepress.dev/guide/extending-default-theme#layout-slots
})
},
enhanceApp({ app, router, siteData }) {
// ...
}
} satisfies Theme
+139
View File
@@ -0,0 +1,139 @@
/**
* Customize default theme styling by overriding CSS variables:
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
*/
/**
* Colors
*
* Each colors have exact same color scale system with 3 levels of solid
* colors with different brightness, and 1 soft color.
*
* - `XXX-1`: The most solid color used mainly for colored text. It must
* satisfy the contrast ratio against when used on top of `XXX-soft`.
*
* - `XXX-2`: The color used mainly for hover state of the button.
*
* - `XXX-3`: The color for solid background, such as bg color of the button.
* It must satisfy the contrast ratio with pure white (#ffffff) text on
* top of it.
*
* - `XXX-soft`: The color used for subtle background such as custom container
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
* on top of it.
*
* The soft color must be semi transparent alpha channel. This is crucial
* because it allows adding multiple "soft" colors on top of each other
* to create a accent, such as when having inline code block inside
* custom containers.
*
* - `default`: The color used purely for subtle indication without any
* special meanings attched to it such as bg color for menu hover state.
*
* - `brand`: Used for primary brand colors, such as link text, button with
* brand theme, etc.
*
* - `tip`: Used to indicate useful information. The default theme uses the
* brand color for this by default.
*
* - `warning`: Used to indicate warning to the users. Used in custom
* container, badges, etc.
*
* - `danger`: Used to show error, or dangerous message to the users. Used
* in custom container, badges, etc.
* -------------------------------------------------------------------------- */
:root {
--vp-c-default-1: var(--vp-c-gray-1);
--vp-c-default-2: var(--vp-c-gray-2);
--vp-c-default-3: var(--vp-c-gray-3);
--vp-c-default-soft: var(--vp-c-gray-soft);
--vp-c-brand-1: var(--vp-c-indigo-1);
--vp-c-brand-2: var(--vp-c-indigo-2);
--vp-c-brand-3: var(--vp-c-indigo-3);
--vp-c-brand-soft: var(--vp-c-indigo-soft);
--vp-c-tip-1: var(--vp-c-brand-1);
--vp-c-tip-2: var(--vp-c-brand-2);
--vp-c-tip-3: var(--vp-c-brand-3);
--vp-c-tip-soft: var(--vp-c-brand-soft);
--vp-c-warning-1: var(--vp-c-yellow-1);
--vp-c-warning-2: var(--vp-c-yellow-2);
--vp-c-warning-3: var(--vp-c-yellow-3);
--vp-c-warning-soft: var(--vp-c-yellow-soft);
--vp-c-danger-1: var(--vp-c-red-1);
--vp-c-danger-2: var(--vp-c-red-2);
--vp-c-danger-3: var(--vp-c-red-3);
--vp-c-danger-soft: var(--vp-c-red-soft);
}
/**
* Component: Button
* -------------------------------------------------------------------------- */
:root {
--vp-button-brand-border: transparent;
--vp-button-brand-text: var(--vp-c-white);
--vp-button-brand-bg: var(--vp-c-brand-3);
--vp-button-brand-hover-border: transparent;
--vp-button-brand-hover-text: var(--vp-c-white);
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
--vp-button-brand-active-border: transparent;
--vp-button-brand-active-text: var(--vp-c-white);
--vp-button-brand-active-bg: var(--vp-c-brand-1);
}
/**
* Component: Home
* -------------------------------------------------------------------------- */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(
120deg,
#bd34fe 30%,
#41d1ff
);
--vp-home-hero-image-background-image: linear-gradient(
-45deg,
#bd34fe 50%,
#47caff 50%
);
--vp-home-hero-image-filter: blur(44px);
}
@media (min-width: 640px) {
:root {
--vp-home-hero-image-filter: blur(56px);
}
}
@media (min-width: 960px) {
:root {
--vp-home-hero-image-filter: blur(68px);
}
}
/**
* Component: Custom Block
* -------------------------------------------------------------------------- */
:root {
--vp-custom-block-tip-border: transparent;
--vp-custom-block-tip-text: var(--vp-c-text-1);
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
}
/**
* Component: Algolia
* -------------------------------------------------------------------------- */
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand-1) !important;
}
+49
View File
@@ -0,0 +1,49 @@
---
outline: deep
---
# Runtime API Examples
This page demonstrates usage of some of the runtime APIs provided by VitePress.
The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
```md
<script setup>
import { useData } from 'vitepress'
const { theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
```
<script setup>
import { useData } from 'vitepress'
const { site, theme, page, frontmatter } = useData()
</script>
## Results
### Theme Data
<pre>{{ theme }}</pre>
### Page Data
<pre>{{ page }}</pre>
### Page Frontmatter
<pre>{{ frontmatter }}</pre>
## More
Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
+3
View File
@@ -0,0 +1,3 @@
# React
TODO
+3
View File
@@ -0,0 +1,3 @@
# Svelte
TODO
+3
View File
@@ -0,0 +1,3 @@
# Vue
TODO
+36
View File
@@ -0,0 +1,36 @@
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
hero:
name: "XO Stack"
text: "XO Stack"
tagline: My great project tagline
actions:
- theme: brand
text: Quick Start
link: /markdown-examples
- theme: alt
text: APIs
link: /api
features:
- title: Feature A
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
- title: Feature B
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
- title: Feature C
details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
---
I haven't played with custom theming much yet and have been working with more the configuration side of things for Docs.
However, it does seem like there's quite a lot of flexibility with VitePress.
We probably won't want to create an entirely custom theme, but extend the default instead:
https://vitepress.dev/guide/extending-default-theme
Also, don't take what's in these docs here literally. I'm still toying around with things.
But having the API docs generate is probably quite useful in even the early-dev/experimental phase: It allows us to get a feel for the API.
+85
View File
@@ -0,0 +1,85 @@
# Markdown Extension Examples
This page demonstrates some of the built-in markdown extensions provided by VitePress.
## Syntax Highlighting
VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting:
**Input**
````md
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
````
**Output**
```js{4}
export default {
data () {
return {
msg: 'Highlighted!'
}
}
}
```
## Custom Containers
**Input**
```md
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
```
**Output**
::: info
This is an info box.
:::
::: tip
This is a tip.
:::
::: warning
This is a warning.
:::
::: danger
This is a dangerous warning.
:::
::: details
This is a details block.
:::
## More
Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown).
+7
View File
@@ -0,0 +1,7 @@
# Tools
May want to have a Tools/Sandbox section.
I'd leave until later before exploring this more, but it sounds like we can (technically) create custom components.
https://vitepress.dev/guide/using-vue
+36
View File
@@ -0,0 +1,36 @@
import { Blockchain, WalletHD } from '@xo/wallet';
const blockchain = new Blockchain(
['bch.imaginary.cash', 'cashnode.bch.ninja', 'electrum.imaginary.cash'],
'Test App',
'1.5'
);
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
);
await wallet.scan();
console.log(wallet.privateKey());
+598
View File
@@ -0,0 +1,598 @@
{
"name": "@xo/node-demo",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@xo/node-demo",
"version": "1.0.0",
"dependencies": {
"@xo/network-wallet": "file:../../packages/network-wallet",
"@xo/primitives": "file:../../packages/primitives"
},
"devDependencies": {
"vite": "^4.0.0"
}
},
"../../packages/network-wallet": {},
"../../packages/primitives": {
"name": "@xo/primitives",
"version": "0.0.1",
"license": "ISC"
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@xo/network-wallet": {
"resolved": "../../packages/network-wallet",
"link": true
},
"node_modules/@xo/primitives": {
"resolved": "../../packages/primitives",
"link": true
},
"node_modules/esbuild": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.18.20"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"dev": true,
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.4.43",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.43.tgz",
"integrity": "sha512-gJAQVYbh5R3gYm33FijzCZj7CHyQ3hWMgJMprLUlIYqCwTeZhBQ19wp0e9mA25BUbEvY5+EXuuaAjqQsrBxQBQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.1",
"source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/rollup": {
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"dev": true,
"license": "MIT",
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=14.18.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vite": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
"rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@xo/node-demo",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@xo/primitives": "file:../../packages/primitives",
"@xo/network-wallet": "file:../../packages/network-wallet"
},
"devDependencies": {
"vite": "^4.0.0"
}
}
+3
View File
@@ -0,0 +1,3 @@
These will be removed (or moved) later, but please leave them for now.
These are experiments that I (Jim) am doing on Reactivity and I don't want to lose this code.
+87
View File
@@ -0,0 +1,87 @@
import React, { useSyncExternalStore, useMemo } from 'react';
import { Blockchain, WalletHD } from '../wallet/index.js';
import { WalletP2PKHReact, useWalletP2PKH } from './wallet-p2pkh-react.js';
import { HdPrivateNodeValid } from '@bitauth/libauth';
class WalletHDReact extends WalletHD<WalletP2PKHReact> {
private rWallets: Array<WalletP2PKHReact> = [];
private rShouldMonitor: boolean = false;
private walletsListeners: Set<() => void> = new Set();
private shouldMonitorListeners: Set<() => void> = new Set();
constructor(
node: HdPrivateNodeValid,
electrum: Blockchain,
walletFactory = (privateKeyBytes: Uint8Array, electrum: Blockchain) =>
new WalletP2PKHReact(privateKeyBytes, electrum)
) {
super(node, electrum, walletFactory);
this.events.on('walletsUpdated', (wallets) => {
this.rWallets = wallets;
this.setupWalletListeners();
this.walletsListeners.forEach((listener) => listener());
});
this.events.on('shouldMonitorUpdated', (shouldMonitor) => {
this.rShouldMonitor = shouldMonitor;
this.shouldMonitorListeners.forEach((listener) => listener());
});
}
subscribeToWallets = (listener: () => void) => {
this.walletsListeners.add(listener);
return () => {
this.walletsListeners.delete(listener);
};
};
subscribeToShouldMonitor = (listener: () => void) => {
this.shouldMonitorListeners.add(listener);
return () => {
this.shouldMonitorListeners.delete(listener);
};
};
getWallets = () => {
return this.rWallets;
};
getShouldMonitor = () => {
return this.rShouldMonitor;
};
private setupWalletListeners() {
this.rWallets.forEach((wallet) => {
wallet.subscribeToUnspents(() => {
this.walletsListeners.forEach((listener) => listener());
});
wallet.subscribeToTransactions(() => {
this.walletsListeners.forEach((listener) => listener());
});
});
}
}
export function useWalletHD(node: HdPrivateNodeValid, electrum: Blockchain) {
const walletHD = useMemo(
() => new WalletHDReact(node, electrum),
[node, electrum]
);
const wallets = useSyncExternalStore(
walletHD.subscribeToWallets,
walletHD.getWallets
);
const shouldMonitor = useSyncExternalStore(
walletHD.subscribeToShouldMonitor,
walletHD.getShouldMonitor
);
const balance = useMemo(() => {
return wallets.reduce((total, wallet) => total + wallet.getBalance(), 0);
}, [wallets]);
return { walletHD, wallets, shouldMonitor, balance };
}
+74
View File
@@ -0,0 +1,74 @@
import { useSyncExternalStore } from 'react';
import { Transaction } from '../primitives/index.js';
import {
WalletP2PKH,
Blockchain,
AddressListUnspent,
} from '../wallet/index.js';
export function useWalletP2PKH(
privateKeyBytes: Uint8Array,
electrum: Blockchain
) {
const wallet = React.useMemo(
() => new WalletP2PKHReact(privateKeyBytes, electrum),
[privateKeyBytes, electrum]
);
const unspents = useSyncExternalStore(
wallet.subscribeToUnspents,
wallet.getUnspents
);
const transactions = useSyncExternalStore(
wallet.subscribeToTransactions,
wallet.getTransactions
);
const balance = React.useMemo(() => {
return unspents.reduce((total, unspent) => total + unspent.value, 0);
}, [unspents]);
return { wallet, unspents, transactions, balance };
}
class WalletP2PKHReact extends WalletP2PKH {
private rUnspents: AddressListUnspent['response'] = [];
private rTransactions: Array<Transaction> = [];
private unspentsListeners: Set<() => void> = new Set();
private transactionsListeners: Set<() => void> = new Set();
constructor(privateKeyBytes: Uint8Array, blockchain: Blockchain) {
super(privateKeyBytes, blockchain);
this.events.on('unspentsUpdated', (unspents) => {
this.rUnspents = unspents;
this.unspentsListeners.forEach((listener) => listener());
});
this.events.on('transactionsUpdated', (transactions) => {
this.rTransactions = transactions;
this.transactionsListeners.forEach((listener) => listener());
});
}
subscribeToUnspents = (listener: () => void) => {
this.unspentsListeners.add(listener);
return () => {
this.unspentsListeners.delete(listener);
};
};
subscribeToTransactions = (listener: () => void) => {
this.transactionsListeners.add(listener);
return () => {
this.transactionsListeners.delete(listener);
};
};
getUnspents = () => {
return this.rUnspents;
};
getTransactions = () => {
return this.rTransactions;
};
}
+61
View File
@@ -0,0 +1,61 @@
import { writable, derived } from 'svelte/store';
import type { Writable, Readable } from 'svelte/store';
import { WalletP2PKH, WalletHD } from '../wallet/index.js'; // Adjust import path as needed
import type { AddressListUnspent, Blockchain } from '../wallet/index.js'; // Adjust import path as needed
import type { Transaction } from '../primitives/index.js';
import type { HdPrivateNodeValid } from '@bitauth/libauth';
export class WalletP2PKHSvelte extends WalletP2PKH {
// Svelte stores
public unspents: Writable<AddressListUnspent['response']> = writable([]);
public transactions: Writable<Array<Transaction>> = writable([]);
// Derived store for balance
public balance: Readable<number> = derived(this.unspents, ($unspents) =>
$unspents.reduce((total, unspent) => total + unspent.value, 0)
);
constructor(privateKeyBytes: Uint8Array, electrum: Blockchain) {
super(privateKeyBytes, electrum);
this.events.on('unspentsUpdated', (unspents) => {
this.unspents.set(unspents);
});
this.events.on('transactionsUpdated', (transactions) => {
this.transactions.set(transactions);
});
}
}
export function useWalletP2PKHSvelte(
privateKeyBytes: Uint8Array,
electrum: Blockchain
): WalletP2PKHSvelte {
return new WalletP2PKHSvelte(privateKeyBytes, electrum);
}
export class WalletHDSvelte extends WalletHD<WalletP2PKHSvelte> {
// Svelte stores
public shouldMonitor: Writable<boolean> = writable(false);
public wallets: Writable<Array<WalletP2PKHSvelte>> = writable([]);
// Derived store for total balance
public balance: Readable<number> = derived(this.wallets, ($wallets) =>
$wallets.reduce((total, wallet) => {
let walletBalance = 0;
wallet.balance.subscribe((value) => (walletBalance = value))();
return total + walletBalance;
}, 0)
);
constructor(
node: HdPrivateNodeValid,
electrum: Blockchain,
walletFactory = useWalletP2PKHSvelte
) {
super(node, electrum, walletFactory);
this.events.on('walletsUpdated', (wallets) => this.wallets.set(wallets));
this.events.on('shouldMonitorUpdated', (shouldMonitor) =>
this.shouldMonitor.set(shouldMonitor)
);
}
}
-35
View File
@@ -1,35 +0,0 @@
{
"tags": {
"allowUnknownTags": false
},
"source": {
"include": ["lib/", "./README.md"],
"includePattern": "\\.(jsx|js|ts|tsx)$",
"excludePattern": "(node_modules/|docs)"
},
"plugins": ["node_modules/better-docs/typescript"],
"opts": {
"template": "node_modules/docdash",
"encoding": "utf8",
"destination": "public/",
"recurse": true,
"verbose": false
},
"templates": {
"cleverLinks": false,
"monospaceLinks": false,
"default": {
"outputSourceFiles": true,
"includeDate": true,
"useLongnameInNav": true
}
},
"docdash": {
"collapse": false,
"static": true,
"sort": true,
"search": false,
"private": false,
"typedefs": true
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"packages": [
"packages/*"
],
"version": "0.0.0"
}
+10947
View File
File diff suppressed because it is too large Load Diff
+18 -20
View File
@@ -7,16 +7,20 @@
"module": "dist/index.mjs.js",
"typings": "dist/index.d.ts",
"sideEffects": false,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "npm run clean && npm run compile && npm run rollup && npm run post-processing",
"compile": "tsc",
"clean": "del dist",
"post-processing": "del ./dist/module",
"docs": "jsdoc -c jsdoc.json",
"build": "lerna run build",
"build:primitives": "lerna run build --scope=@xo/primitives --include-dependencies",
"build:wallet": "lerna run build --scope=@xo/wallet --include-dependencies",
"build:vue": "lerna run build --scope=@xo/vue --include-dependencies",
"docs:api": "typedoc --skipErrorChecking",
"docs:dev": "npm run docs:api && vitepress dev docs",
"docs:build": "npm run docs:api && vitepress build docs",
"docs:preview": "npm run docs:api && vitepress preview docs",
"format": "prettier --write \"**/*.{js,ts,md,json}\" --ignore-path .gitignore",
"lint": "eslint .",
"rollup": "rollup -c",
"spellcheck": "cspell '**'",
"test": "ava"
},
"repository": {
@@ -36,25 +40,19 @@
},
"homepage": "https://gitlab.com/GeneralProtocols/xo/stack",
"dependencies": {
"@bitauth/libauth": "^3.0.0",
"@bitauth/libauth": "^3.0.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.1.0",
"lerna": "^8.1.8",
"prettier": "^2.5.1",
"rollup": "^3.29.2",
"rollup-plugin-dts": "^6.0.2",
"tsc-watch": "^6.0.4",
"typescript": "^5.1.3"
},
"ava": {
"extensions": {
"ts": "module"
},
"nodeArguments": [
"--loader=ts-node/esm"
]
"typedoc": "^0.26.6",
"typedoc-plugin-markdown": "^4.2.6",
"typedoc-vitepress-theme": "^1.0.1",
"typescript": "^5.1.3",
"vitepress": "^1.3.4"
}
}
+3
View File
@@ -0,0 +1,3 @@
# XO Primitives
TODO
+18
View File
@@ -0,0 +1,18 @@
{
"name": "@xo/primitives",
"version": "0.0.1",
"description": "XO Stack Primitives",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"build-old": "rollup -c"
},
"author": "",
"license": "ISC"
}
+142
View File
@@ -0,0 +1,142 @@
import {
binToHex,
hexToBin,
isHex,
base64ToBin,
binToBase64,
isBase64,
binToUtf8,
utf8ToBin,
bigIntToBinUint64LE,
bigIntToBinUintLE,
binToBigIntUintLE,
} from '@bitauth/libauth';
/**
* Bytes Primitive.
*/
export class Bytes {
public readonly bytes: Uint8Array;
/**
* Instantiate Bytes from a Uint8Array.
*
* @param bytes {Uint8Array} The bytes to use.
*/
constructor(bytes: Uint8Array) {
this.bytes = bytes;
}
/**
* Instantiates bytes from a Base64 string.
*
* @param utf8 {string} The Base64 string.
*
* @throws {Error} If Base64 is invalid.
*
* @returns {Bytes} Instance of Bytes.
*/
public static fromBase64(base64: string): Bytes {
// If the Base64 is invalid, throw an error.
if (!isBase64(base64)) {
throw new Error('Invalid base64 string');
}
return new Bytes(base64ToBin(base64));
}
/**
* Instantiates bytes from a BigInt (LE encoded).
*
* @param number {number} The bigint number.
*
* @throws {Error} If number is invalid.
*
* @returns {Bytes} Instance of Bytes.
*/
public static fromBigInt(number: bigint): Bytes {
return new Bytes(bigIntToBinUintLE(number));
}
/**
* Instantiates bytes from a BigInt (64 Bit LE encoded).
*
* @param number {number} The bigint number.
*
* @throws {Error} If number is invalid.
*
* @returns {Bytes} Instance of Bytes.
*/
public static fromBigInt64(number: bigint): Bytes {
return new Bytes(bigIntToBinUint64LE(number));
}
/**
* Instantiates bytes from a hex string.
*
* @param hex {string} The hex string.
*
* @throws {Error} If hex is invalid.
*
* @returns {Bytes} Instance of Bytes.
*/
public static fromHex(hex: string): Bytes {
// If the hex is invalid, throw an error.
if (!isHex(hex)) {
throw new Error('Invalid hexidecimal string');
}
return new Bytes(hexToBin(hex));
}
/**
* Instantiates bytes from a UTF8 string.
*
* @param utf8 {string} The UTF8 string.
*
* @returns {Bytes} Instance of Bytes.
*/
public static fromUtf8(utf8: string): Bytes {
return new Bytes(utf8ToBin(utf8));
}
/**
* Return bytes as a Base64 string.
*
* @returns {string} Bytes as a Base64 string.
*/
public toBase64(): string {
return binToBase64(this.bytes);
}
public toBigInt(): bigint {
return binToBigIntUintLE(this.bytes);
}
/**
* Return bytes as a hex string.
*
* @returns {string} Bytes as a hex string.
*/
public toHex(): string {
return binToHex(this.bytes);
}
/**
* Return bytes as a UTF8 string
*
* @returns {string} Bytes as a UTF8 string.
*/
public toUtf8(): string {
return binToUtf8(this.bytes);
}
/**
* Return bytes as a Uint8Array
*
* @returns {Uint8Array} Bytes as a Uint8Array.
*/
public toBytes(): Uint8Array {
return this.bytes;
}
}
+58
View File
@@ -0,0 +1,58 @@
import {
ripemd160,
sha256,
} from '@bitauth/libauth';
/**
* Crypto Module.
*/
export class Crypto
{
/**
* SHA256 hash the given input
*
* @param input {Uint8Array} The input to hash.
*
* @returns {Uint8Array} The SHA256 Hash of the given input.
*/
public static sha256(input: Uint8Array): Uint8Array
{
return sha256.hash(input);
}
/**
* RIPEMD160 hash the given input
*
* @param input {Uint8Array} The input to hash.
*
* @returns {Uint8Array} The RIPEMD160 Hash of the given input.
*/
public static ripemd160(input: Uint8Array): Uint8Array
{
return ripemd160.hash(input);
}
/**
* Double SHA256 hash the given input
*
* @param input {Uint8Array} The input to hash.
*
* @returns {Uint8Array} The Double SHA256 Hash of the given input.
*/
public static hash256(input: Uint8Array): Uint8Array
{
return sha256.hash(sha256.hash(input));
}
/**
* RIPEMD160 and then SHA256 hash the given input
*
* @param input {Uint16Array} The input to hash.
*
* @returns {Uint8Array} The RIPEMD160+SHA256 Hash of the given input.
*/
public static hash160(input: Uint8Array): Uint8Array
{
return ripemd160.hash(sha256.hash(input));
}
}
@@ -1,7 +1,10 @@
export * from './address.js';
export * from './bytes.js';
export * from './crypto.js';
export * from './hd-private-node.js';
export * from './hd-public-node.js';
export * from './private-key.js';
export * from './public-key.js';
export * from './satoshis.js';
export * from './script.js';
export * from './transaction.js';
@@ -89,7 +89,7 @@ export class Satoshis {
*/
public static roundToDigits(numberToRound: number, digits: number): number {
// Set the options of the Number Format object.
const options = {
const options: Intl.NumberFormatOptions = {
style: 'decimal',
minimumFractionDigits: digits,
maximumFractionDigits: digits,
+145
View File
@@ -0,0 +1,145 @@
import {
assembleBytecodeBCH,
disassembleBytecodeBCH,
base58AddressToLockingBytecode,
cashAddressToLockingBytecode,
binToHex,
hexToBin,
isHex,
} from '@bitauth/libauth';
/**
* Script Entity.
*/
export class Script {
public readonly bytes: Uint8Array;
/**
* Construct a new Script
*
* @param bytes {Uint8Array} The raw bytes of the Script.
*/
constructor(bytes: Uint8Array) {
this.bytes = bytes;
}
/**
* Create a Script Entity from the given Assembly String.
*
* @param asm {string} String of assembly code.
*
* @throws {Error} If script cannot be decoded.
*
* @returns {Script} The created Script Entity.
*/
public static fromASM(asm: string): Script {
// Attempt to convert the given ASM to bytecode.
const assembleResult = assembleBytecodeBCH(asm);
// If a string is returned, this indicates an error...
if (typeof assembleResult === 'string') {
throw new Error(assembleResult);
}
// If the success flag is not marked, this indicates an error...
if (!assembleResult.success) {
throw new Error('Could not assemble bytecode from assembly.');
}
// Return new Script Entity from the assembled bytecode.
return new Script(assembleResult.bytecode);
}
/**
* Create a Script Entity from the given bytes (as hexadecimal string).
*
* @param hex {string} Raw script bytes encoded as hexadecimal.
*
* @throws {Error} If script cannot be decoded.
*
* @returns {Script} The created Script Entity.
*/
public static fromHex(hex: string): Script {
// Ensure that the given string is hex.
if (!isHex(hex)) {
throw new Error(`The given string (${hex}) is not valid hexadecimal.`);
}
// Conver the given hex to binary (Uint8Array).
const scriptBin = hexToBin(hex);
// Return new Script entity constructed from the binary.
return new Script(scriptBin);
}
/**
* Create a Script Entity from the given CashAddr.
*
* @param cashAddr {string} The Address in CashAddr format.
*
* @throws {Error} If address cannot be decoded.
*
* @returns {Script} The created Script Entity.
*/
public static fromCashAddr(cashAddr: string): Script {
const decodeResult = cashAddressToLockingBytecode(cashAddr);
if (typeof decodeResult === 'string') {
throw new Error(decodeResult);
}
return new Script(decodeResult.bytecode);
}
/**
* Create a Script Entity from the given Legacy Address.
*
* @param legacyAddress {string} The Address in Legacy format.
*
* @throws {Error} If address cannot be decoded.
*
* @returns {Script} The created Script Entity.
*/
public static fromLegacyAddress(legacyAddress: string): Script {
const decodeResult = base58AddressToLockingBytecode(legacyAddress);
if (typeof decodeResult === 'string') {
throw new Error(decodeResult);
}
return new Script(decodeResult.bytecode);
}
/**
* Convert this script to a human-readable string of assembly.
*
* @returns {string} Human-readable assembly.
*/
public toASM(joinWith = '\n'): string {
const disassembledBytecode = disassembleBytecodeBCH(this.bytes);
if (joinWith) {
return disassembledBytecode.split(' ').join(joinWith);
}
return disassembledBytecode;
}
/**
* Get the raw bytecode for this script.
*
* @returns {Uint8Array} The compiled bytes.
*/
public toBytes(): Uint8Array {
return this.bytes;
}
/**
* Get the raw bytecode for this script as a hex string.
*
* @returns {string} The compiled bytes as a hex string.
*/
public toHex(): string {
return binToHex(this.bytes);
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
+3
View File
@@ -0,0 +1,3 @@
# XO Wallet
TODO
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@xo/vue",
"version": "0.0.1",
"description": "XO Stack Wallet - Vue",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"build-old": "rollup -c",
"prepublishOnly": "npm run build"
},
"author": "",
"license": "ISC",
"dependencies": {
"@xo/primitives": "^0.0.1",
"@xo/wallet": "^0.0.1",
"vue": "^3.4.38"
}
}
@@ -1,5 +1,4 @@
import { ElectrumService } from 'src/services/electrum.js';
import { WalletHD } from 'src/libcash/wallet/index.js';
import { Blockchain, WalletHD } from '@xo/wallet';
import { WalletP2PKHVue, useWalletP2PKHVue } from './wallet-p2pkh-vue.js';
import { computed, ref, shallowRef } from 'vue';
@@ -21,7 +20,7 @@ export class WalletHDVue extends WalletHD<WalletP2PKHVue> {
constructor(
node: HdPrivateNodeValid,
electrum: ElectrumService,
electrum: Blockchain,
walletFactory = useWalletP2PKHVue
) {
super(node, electrum, walletFactory);
@@ -1,11 +1,18 @@
import { WalletP2PKH } from 'src/libcash/wallet/index.js';
import { type AddressListUnspent } from 'src/services/electrum-types';
import { ElectrumService } from 'src/services/electrum.js';
import { Transaction } from 'src/libcash/primitives/index.js';
import { Transaction } from '@xo/primitives';
import { type AddressListUnspent, Blockchain, WalletP2PKH } from '@xo/wallet';
import { computed, shallowRef } from 'vue';
/**
* @example
* ```ts
* const walletP2PKH = WalletP2PKHVue.fromMnemonic('12 words');
* ```
*
* Reactive variables are prefixed with `r`.
* ```vue
* <div>{{ walletP2PKH.rBalance.value }}</div>
* ```
*/
export class WalletP2PKHVue extends WalletP2PKH {
// Reactive State.
public rUnspents = shallowRef<AddressListUnspent['response']>([]);
@@ -19,7 +26,7 @@ export class WalletP2PKHVue extends WalletP2PKH {
);
});
constructor(privateKeyBytes: Uint8Array, electrum: ElectrumService) {
constructor(privateKeyBytes: Uint8Array, electrum: Blockchain) {
super(privateKeyBytes, electrum);
this.events.on('unspentsUpdated', (unspents) => {
@@ -34,7 +41,7 @@ export class WalletP2PKHVue extends WalletP2PKH {
export function useWalletP2PKHVue(
privateKeyBytes: Uint8Array,
electrum: ElectrumService
electrum: Blockchain
): WalletP2PKHVue {
return new WalletP2PKHVue(privateKeyBytes, electrum);
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
+3
View File
@@ -0,0 +1,3 @@
# XO Wallet
TODO
+23
View File
@@ -0,0 +1,23 @@
{
"name": "@xo/wallet",
"version": "0.0.1",
"description": "XO Stack Wallet",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"build-old": "rollup -c",
"prepublishOnly": "npm run build"
},
"author": "",
"license": "ISC",
"dependencies": {
"@electrum-cash/network": "^3.3.0",
"@xo/primitives": "^0.0.1"
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from './utils/index.js';
export * from './wallet/index.js';
@@ -0,0 +1,131 @@
// TODO: I think @monsterbitar (Jonathan) is working on something like this too.
// So I haven't bothered making this complete. We'll just use his once it's done.
// Or, if he's busy with other stuff, we can complete this and create an MR into Electrum-Cash.
import type { RequestResponse } from '@electrum-cash/network';
// TODO: This should be imported from electrum-cash library.
// But it is not exported yet.
export type RPCParameter = string | number | boolean | null;
export interface ElectrumRequest {
method: string;
params: Array<RPCParameter>;
response: RequestResponse;
}
export interface AddressGetBalance extends ElectrumRequest {
method: 'blockchain.address.get_balance';
params: [string];
response: {
confirmed: number;
unconfirmed: number;
};
}
export interface AddressGetHistory extends ElectrumRequest {
method: 'blockchain.address.get_history';
// TODO: RPCParameter accepts a null type, but this should probably be changed to undefined instead.
params: [string, number | null, number | null];
response: Array<{
height: number;
tx_hash: string;
fee?: number;
}>;
}
export interface AddressGetMempool extends ElectrumRequest {
method: 'blockchain.address.get_mempool';
params: [string];
response: Array<{
height: number;
tx_hash: string;
fee?: number;
}>;
}
export interface AddressGetScriptHash extends ElectrumRequest {
method: 'blockchain.address.get_scripthash';
params: [string];
response: string;
}
export interface AddressListUnspent extends ElectrumRequest {
method: 'blockchain.address.listunspent';
params: [string, 'include_tokens' | 'exclude_tokens' | 'tokens_only'];
response: Array<{
height: number;
token_data?: {
amount: string;
category: string;
nft?: {
capability: 'none' | 'mutable' | 'minting';
commitment: string;
};
};
tx_hash: string;
tx_pos: number;
value: number;
}>;
}
export interface AddressSubscribe extends ElectrumRequest {
method: 'blockchain.address.subscribe';
params: [string];
// [Address, Status]
response: [string, string | null];
}
export interface AddressUnsubscribe extends ElectrumRequest {
method: 'blockchain.address.unsubscribe';
params: [string];
response: boolean;
}
export interface ScriptHashGetBalance extends ElectrumRequest {
method: 'blockchain.scripthash.get_balance';
params: [string];
response: {
confirmed: number;
unconfirmed: number;
};
}
export interface TransactionBroadcast extends ElectrumRequest {
method: 'blockchain.transaction.broadcast';
params: [string];
response: string;
}
export interface TransactionGet extends ElectrumRequest {
method: 'blockchain.transaction.get';
params: [string, boolean];
response: string;
}
export interface TransactionGetVerbose extends ElectrumRequest {
method: 'blockchain.transaction.get';
params: [string, true];
response: {
// TODO
};
}
export interface TransactionSubscribe extends ElectrumRequest {
method: 'blockchain.transaction.subscribe';
params: [string];
response: number | null;
}
export interface TransactionUnsubscribe extends ElectrumRequest {
method: 'blockchain.transaction.unsubscribe';
params: [string];
response: boolean;
}
export interface AddressNotification {
jsonrpc: '2.0';
method: 'blockchain.address.subscribe';
// [Address, Status]
params: [string, string | null];
}
+122
View File
@@ -0,0 +1,122 @@
import { AddressNotification, ElectrumRequest } from './blockchain-types.js';
import { ElectrumClient } from '@electrum-cash/network';
export type AddressCallback = (status: string | null) => void;
// TODO: Rename this to Blockcain Service and refactor such that it can store local state.
// For local state, we'll want to track a tree of dependencies locally.
// Might be able to copy whatever structure a proper node uses (DAG?).
export class Blockchain {
electrumClient!: ElectrumClient;
// Address subscriptions.
addressSubscriptions: { [address: string]: AddressCallback } = {};
constructor(
public readonly servers: string[],
public readonly application = 'BCHApp',
public readonly version = '1.5'
) {}
async start(): Promise<void> {
// TODO: We want to use a Cluster instead of a Client, but as of 2022-07-07 there are issues with Subscriptions on Cluster.
// See: https://gitlab.com/GeneralProtocols/anyhedge/whitelabel/frontend/-/issues/76
// Iterate over each server in our list of servers.
// NOTE: We are still using an Electrum Client due to the above bug.
// But, if we cannot connect to the first server, we will try the second (and third if given, etc) as a fallback.
for (const electrumServer of this.servers) {
// Initialize an Electrum client using the given server.
const electrum = new ElectrumClient(
this.application,
this.version,
electrumServer,
50004,
'wss'
);
// Attempt to connect to this Electrum Server.
try {
await electrum.connect();
} catch (error) {
console.warn(error);
// If it fails to connect, try the next server in our list.
continue;
}
// Save the client.
this.electrumClient = electrum;
// Setup notification handler.
this.electrumClient.on('notification', this.onNotification.bind(this));
// Return to prevent further execution.
return;
}
// Throw an error if we failed to connect to any of our Electrum Servers.
throw new Error(
'Failed to connect to Electrum. Please try restarting your browser and ensure you are currently connected to the internet.'
);
}
async request<T extends ElectrumRequest>(
endpoint: T['method'],
...params: T['params']
): Promise<T['response']> {
const response = await this.electrumClient.request(endpoint, ...params);
if (response instanceof Error) {
throw response;
}
return response;
}
async subscribeAddress(address: string, callback: AddressCallback) {
await this.electrumClient.subscribe(
'blockchain.address.subscribe',
address
);
this.addressSubscriptions[address] = callback;
}
async unsubscribeAddress(address: string, _callback: AddressCallback) {
if (!this.addressSubscriptions[address]) {
return;
}
delete this.addressSubscriptions[address];
try {
await this.electrumClient.unsubscribe(
'blockchain.address.unsubscribe',
address
);
} catch (error) {
console.warn(`${error}`);
}
}
onNotification(data: AddressNotification) {
// Handle Address notification.
if (data.method === 'blockchain.address.subscribe') {
const address = data.params[0];
const status = data.params[1];
const subscription = this.addressSubscriptions[address];
if (!subscription) {
console.warn(
`Notification for address ${address} subscribed to, but has no handler`
);
return;
}
// Trigger the subscription's callback.
subscription(status);
}
}
}
@@ -1,9 +1,9 @@
import { ElectrumService } from 'src/services/electrum.js';
import { Blockchain } from './blockchain.js';
export type ServiceConfig<BlockchainType, WalletType> = {
createBlockchain: () => BlockchainType;
createWallet: (
privateKeyBytes: Uint8Array,
electrum: ElectrumService
electrum: Blockchain
) => WalletType;
};
+5
View File
@@ -0,0 +1,5 @@
export * from './blockchain.js';
export * from './blockchain-types.js';
export * from './storage.js';
export * from './wallet-hd.js';
export * from './wallet-p2pkh.js';
+14
View File
@@ -0,0 +1,14 @@
/**
* TODO
*
* @remarks
* Storage should support Adapters.
* 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).
*/
export class Storage {
constructor() {
console.log('TODO');
}
}
@@ -1,7 +1,7 @@
import { PrivateKey, Transaction } from 'src/libcash/primitives/index.js';
import { EventEmitter } from 'src/libcash/utils/index.js';
import { type AddressListUnspent } from 'src/services/electrum-types';
import { ElectrumService } from 'src/services/electrum';
import { type AddressListUnspent } from './blockchain-types.js';
import { Blockchain } from './blockchain.js';
import { PrivateKey, Transaction } from '@xo/primitives';
import {
type CompilerBCH,
@@ -17,9 +17,9 @@ export type InputTemplateBCH = InputTemplate<
export abstract class WalletBase extends PrivateKey {
// Dependencies/Services
public electrum: ElectrumService;
public electrum: Blockchain;
constructor(privateKeyBytes: Uint8Array, electrum: ElectrumService) {
constructor(privateKeyBytes: Uint8Array, electrum: Blockchain) {
super(privateKeyBytes);
this.electrum = electrum;
}
@@ -1,7 +1,7 @@
// Import the Stamp class to derive the private keys
import { HDPrivateNode } from 'src/libcash/primitives/index.js';
import { EventEmitter } from 'src/libcash/utils/index.js';
import { ElectrumService } from 'src/services/electrum';
import { HDPrivateNode } from '@xo/primitives';
import { EventEmitter } from '../utils/index.js';
import { Blockchain } from './blockchain.js';
import {
WalletP2PKH,
WalletP2PKHFactory,
@@ -36,7 +36,7 @@ export class WalletHD<
constructor(
public node: HdPrivateNodeValid,
public electrum: ElectrumService,
public electrum: Blockchain,
private walletFactory: WalletP2PKHFactory<WalletType> = useWalletP2PKH
) {
// Instantiate parent class.
@@ -50,7 +50,7 @@ export class WalletHD<
static fromMnemonic<T extends WalletHD<any>>(
this: WalletHDConstructor<T>,
mnemonic: string,
electrum: ElectrumService,
electrum: Blockchain,
walletFactory?: WalletP2PKHFactory<any>
): T {
// Derive the seed from the mnemonic.
@@ -1,11 +1,11 @@
import { Transaction } from 'src/libcash/primitives/index.js';
import { EventEmitter } from 'src/libcash/utils/index.js';
import { Transaction } from '@xo/primitives';
import { EventEmitter } from '../utils/index.js';
import {
type AddressListUnspent,
type AddressGetHistory,
type TransactionGet,
} from 'src/services/electrum-types';
import { ElectrumService } from 'src/services/electrum';
} from './blockchain-types.js';
import { Blockchain } from './blockchain.js';
import { WalletBase } from './wallet-base.js';
@@ -20,6 +20,16 @@ export type WalletP2PKHEvents = {
unspentsUpdated: AddressListUnspent['response'];
};
/**
* @example
* ```ts
* // Instantiate P2PKH Wallet.
* const walletP2PKH = WalletP2PKH.fromWIF('someWif');
*
* // Start monitoring the balance.
* await walletP2PKH.startMonitoring();
* ```
*/
export class WalletP2PKH extends WalletBase {
// Events.
public events: EventEmitter<WalletP2PKHEvents> = new EventEmitter();
@@ -28,7 +38,7 @@ export class WalletP2PKH extends WalletBase {
public transactions: Array<Transaction> = [];
public unspents: AddressListUnspent['response'] = [];
constructor(privateKeyBytes: Uint8Array, electrum: ElectrumService) {
constructor(privateKeyBytes: Uint8Array, electrum: Blockchain) {
super(privateKeyBytes, electrum);
// Monitor the following properties and emit an event when they change.
@@ -162,12 +172,12 @@ export class WalletP2PKH extends WalletBase {
export type WalletP2PKHFactory<T extends WalletP2PKH = WalletP2PKH> = (
privateKeyBytes: Uint8Array,
electrum: ElectrumService
electrum: Blockchain
) => T;
export function useWalletP2PKH<T extends WalletP2PKH = WalletP2PKH>(
privateKeyBytes: Uint8Array,
electrum: ElectrumService
electrum: Blockchain
): T {
return new WalletP2PKH(privateKeyBytes, electrum) as T;
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
-30
View File
@@ -1,30 +0,0 @@
// Import package.json to reduce name repetition.
// eslint-disable-next-line import/extensions
import pkg from './package.json';
import dts from 'rollup-plugin-dts';
export default [
// Build main module in both ES and CJS
{
// Specify entry point for building
input: 'dist/module/lib/index.js',
// Omit all dependencies and peer dependencies from the final bundle
external: [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
],
// Produce outputs for ES and CJS
output: [
{ file: pkg.module, format: 'es' },
{ file: pkg.main, format: 'cjs' },
],
},
// Build type definitions for the library
{
input: 'dist/module/lib/index.d.ts',
output: [{ file: pkg.typings, format: 'es' }],
plugins: [dts()],
},
];
-2
View File
@@ -1,2 +0,0 @@
export * from './wallet-hd.js';
export * from './wallet-p2pkh.js';
+15 -29
View File
@@ -1,38 +1,24 @@
{
"compilerOptions": {
// Target esnext (rollup step will transpile to CJS and EJS)
"target": "esnext",
// Use node as module resolution strategy for best compatibility with dependencies
"moduleResolution": "node",
"module": "esnext",
// Set the root directory and output directory
"rootDir": "./src",
"outDir": "./build/lib",
// Allow ES modules to be used
"esModuleInterop": true,
// Allow importing .json files in project
"resolveJsonModule": true,
// Generate declarations, declaration maps and sourcemaps
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
// Use strict Typescript rules
"outDir": "./dist",
"rootDir": "src",
"strict": true,
"strictPropertyInitialization": true,
// Allow use of ES2017 and DOM features/libraries
"lib": ["es2017", "dom"]
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
"*": [
"src/*"
]
}
},
// Exclude these directories from the Typescript transpile
"exclude": ["node_modules/**", "dist/**", "public/**"],
// Do not compile upon save - this feature requires tsc-w(atch)
"compileOnSave": true
"exclude": [
"node_modules",
"dist"
]
}
+28
View File
@@ -0,0 +1,28 @@
{
"entryPoints": [
"./packages/primitives/src/index.ts",
"./packages/wallet/src/index.ts",
"./packages/vue/src/index.ts"
],
"plugin": [
"typedoc-plugin-markdown",
"typedoc-vitepress-theme"
],
"docsRoot": "./docs",
"out": "./docs/api",
"sidebar": {
"autoConfiguration": true,
"format": "vitepress",
"pretty": true,
"collapsed": false
},
"indexFormat": "table",
"parametersFormat": "table",
"interfacePropertiesFormat": "table",
"classPropertiesFormat": "table",
"propertyMembersFormat": "table",
"useCodeBlocks": true,
"sort": [
"static-first"
]
}