diff --git a/README.md b/README.md index 8d2dfb4..748a51b 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,112 @@ The one constant is `deployment-info.json`. ## License [Open BSV License](./LICENSE.txt) + +# Locksmith + +### "For those of us who think purposely freezing our money for a cause is cool." + +### Locksmith is a Bitcoin SV (BSV) application that lets users lock a small amount of satoshis in a smart contract until a specified future block height. This time-lock is accompanied by a custom message and cannot be redeemed until the blockchain reaches the unlock height. + +## Features + +- Lock 10–1000 satoshis for 1–10 blocks. +- Attach a personal message to each lock. +- View current active locks and the number of blocks remaining. +- Automatically redeem funds once the lock expires. +- Lookup service and topic manager to manage the overlay. + +## Tech Stack + +| Layer | Technology | +|----------------|--------------------------------------------| +| Frontend | React + TypeScript + MUI | +| Smart Contract | sCrypt (Locksmith contract) | +| Blockchain | Bitcoin SV (via `@bsv/sdk`, `@bsv/overlay`) | +| Backend | Node.js + Express + MongoDB | +| Storage | MongoDB (lock metadata) | +| Overlay | LookupService + TopicManager via `@bsv/overlay` | + +## Screenshots + +The frontend allows you to lock satoshis with a reason and see other users' active locks when it is deployed using CARS (see above). +![App page](https://github.com/user-attachments/assets/9363c9a5-0535-4805-a376-a0322aeef074) + +## Getting Started + +### Prerequisites + +- Node.js ≥ v18 +- MongoDB running locally or remotely +- [BRC-100](https://github.com/bitcoin-sv/BRCs/blob/master/wallet/0100.md) Wallet integration (get your [Metanet Desktop](https://github.com/bitcoin-sv/metanet-desktop/releases) wallet) +- Docker (optional, but integral if using LARS (see above)) + +## Frontend Setup +```bash +cd frontend +npm i +npm run start +Visit: http://localhost:8090 +``` +## Backend Setup +```bash +cd backend +npm i +npm run build +npm run start +Server runs on: http://localhost:8080 +``` + +## Contracts +### Locksmith +Built using: ([sCrypt.io](https://scrypt.io/)) +```ts +class Locksmith extends SmartContract { + @prop() address: Addr + @prop() lockUntilHeight: bigint + @prop() message: ByteString + + @method(SigHash.ANYONECANPAY_NONE) + unlock(sig: Sig, pubKey: PubKey) { + assert(lock conditions...) + } +} +``` +## The contract enforces: +- Lock until a future block height +- Signature verification +- Message attachment for context + +## Lock Flow +### Lock funds +- User inputs satoshis, block duration, and message. +- Transaction is created using `walletClient.createAction()`. +- Funds are locked in a smart contract and broadcast via `SHIPBroadcaster()`. + +### Monitor locks +- The frontend polls the overlay every 10 seconds. +- Active locks are displayed with their remaining blocks. + +### Unlock funds +- When the lock expires, the app automatically constructs and broadcasts a redeeming transaction using `walletClient.createAction()` and the original contract and signature. + +## Overlay +[documentation](https://docs.projectbabbage.com/docs/concepts/overlays) +- Two overlay components handle indexing and filtering: +- LookupService: Tracks active locks in the MongoDB. +- TopicManager: Identifies outputs created using the Locksmith contract. + +## Directory Structure +```css +locksmith/ +├── frontend/ +│ └── App.tsx +├── backend/ +│ ├── src/ +│ │ ├── contracts/Locksmith.ts +│ │ ├── lookup-services/ +│ │ └── topic-managers/ +├── artifacts/ +├── types/ +├── mod.ts +``` diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fb3f3b3..821dc45 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,7 +6,7 @@ import { LinearProgress, Container } from '@mui/material' -import { lock, startBackgroundUnlockWatchman } from './utils/utils' +import { lock, startBackgroundUnlockWatchman, truncate } from './utils/utils' import { NoMncModal } from 'metanet-react-prompt' import { LookupResolver, Transaction, Utils, WalletClient } from '@bsv/sdk' import { HodlockerToken, Token } from './types/types' @@ -242,14 +242,16 @@ export const App: React.FC = () => {
} - }) => setMessage(e.target.value)} + inputProps={{ maxLength: 256 }} + onChange={(e: React.ChangeEvent) => { + const truncated = truncate(e.target.value, 256) + setMessage(truncated) + }} />

diff --git a/frontend/src/utils/utils.ts b/frontend/src/utils/utils.ts index 0132b7d..bb4859c 100644 --- a/frontend/src/utils/utils.ts +++ b/frontend/src/utils/utils.ts @@ -65,8 +65,8 @@ export const lock = async ( setHodlocker: React.Dispatch>, hodlocker: HodlockerToken[] ): Promise => { - if (lockBlockCount < 0) { - throw new Error('Lock block count must be zero or positive') + if (lockBlockCount < 1 || lockBlockCount > 10) { + throw new Error('Lock block count must be positive and less than 10') } if (satoshis < 10 || satoshis > 1000) { throw new Error( @@ -76,6 +76,9 @@ export const lock = async ( if (message.length < 1) { throw new Error('Message is required for the lock') } + if (message.length > 260) { + throw new Error('Message is too long') + } const currentBlockHeightObj = await constants.walletClient.getHeight() const lockBlockHeight = currentBlockHeightObj.height + lockBlockCount diff --git a/package-lock.json b/package-lock.json index 135af4c..b125238 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,27 +21,8 @@ "ts-standard": "^12.0.2" }, "devDependencies": { - "@bsv/cars-cli": "^1.1.4", - "@bsv/lars": "1.4.0" - } - }, - "node_modules/@bsv/cars-cli": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@bsv/cars-cli/-/cars-cli-1.1.4.tgz", - "integrity": "sha512-KlkzrzBW9vWrSoM6VqqOAiAuNF7V9DXrBTXlMY3JfDQCuB5ACjYT0iFVbtbioBzUqIw8LSP9f5Hug1QgU/jb5g==", - "dev": true, - "dependencies": { - "@bsv/sdk": "^1.3.33", - "axios": "^1.4.0", - "chalk": "^4.1.2", - "cli-table3": "^0.6.5", - "commander": "^10.0.0", - "inquirer": "^12.1.0", - "ora": "^6.1.2", - "tar": "^6.1.11" - }, - "bin": { - "cars": "dist/index.js" + "@p2ppsr/cars-cli": "^1.2.13", + "@p2ppsr/lars": "^1.5.11" } }, "node_modules/@bsv/gasp": { @@ -53,45 +34,6 @@ "@bsv/sdk": "^1.1.6" } }, - "node_modules/@bsv/lars": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@bsv/lars/-/lars-1.4.0.tgz", - "integrity": "sha512-X9pwUSnV+FQYNogLD7vIxML7xdr0/Y2pxj7y+2ox4GfwjivrfFratgrhGoPK/NPzkxTUkpoKKrIboIlunFX5Hw==", - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "dependencies": { - "@bsv/sdk": "^1.4.12", - "@bsv/wallet-toolbox-client": "^1.2.30", - "axios": "^1.7.9", - "chalk": "^5.3.0", - "chokidar": "^3.5.3", - "commander": "^10.0.0", - "dotenv": "^16.3.1", - "figlet": "^1.8.0", - "fs-extra": "^11.1.1", - "inquirer": "^12.1.0", - "ngrok": "^5.0.0-beta.2", - "open": "^10.1.0", - "tsx": "^3.12.7", - "yaml": "^2.2.1" - }, - "bin": { - "lars": "dist/index.js" - } - }, - "node_modules/@bsv/lars/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@bsv/overlay": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/@bsv/overlay/-/overlay-0.1.27.tgz", @@ -110,15 +52,24 @@ "license": "SEE LICENSE IN LICENSE.txt" }, "node_modules/@bsv/wallet-toolbox-client": { - "version": "1.2.35", - "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-client/-/wallet-toolbox-client-1.2.35.tgz", - "integrity": "sha512-qW/4e6PhH9YZgxQPxSqCn3oLEuTwRihrH6LFJHtcU7f8nQwJWIso1AEDbyQhIkKY1bXmPS4BooDhmixpkX+cZw==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-client/-/wallet-toolbox-client-2.1.28.tgz", + "integrity": "sha512-kxkQXQxff0xk9IfWjSJW5xq1orlGfoG0ZYG3MFOmGXykMeTruYp++79ESIt0M9xtusMuB78ktaJvcdeuY1T5mg==", "dev": true, "license": "SEE LICENSE IN license.md", "dependencies": { - "@bsv/sdk": "^1.4.18" + "@bsv/sdk": "2.1.2", + "hash-wasm": "^4.12.0", + "idb": "^8.0.2" } }, + "node_modules/@bsv/wallet-toolbox-client/node_modules/@bsv/sdk": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-2.1.2.tgz", + "integrity": "sha512-648RLJEkbH+vFt5Q65orm3jdmZDnmLdt8YUpyQcctxVYwHvhEN/7RxMXjA+OnNLHZV1IOstqwXQ+4ar89KUs7Q==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt" + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -925,6 +876,19 @@ } } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -975,6 +939,79 @@ "node": ">= 8" } }, + "node_modules/@p2ppsr/cars-cli": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@p2ppsr/cars-cli/-/cars-cli-1.2.13.tgz", + "integrity": "sha512-l7yEdHP60DRLd9xwgn+JnCVfTPXLdS/wEN7rGcn7LonT0uZcWdvJ59rOMRhaL59o347Uo2VWEy4iZXZKU+Xzkg==", + "dev": true, + "dependencies": { + "@bsv/sdk": "^2.0.1", + "@bsv/wallet-toolbox-client": "^2.0.5", + "axios": "^1.4.0", + "chalk": "^4.1.2", + "cli-table3": "^0.6.5", + "commander": "^10.0.0", + "inquirer": "^12.1.0", + "ora": "^6.1.2", + "tar": "^7.5.7" + }, + "bin": { + "cars": "dist/index.js" + } + }, + "node_modules/@p2ppsr/cars-cli/node_modules/@bsv/sdk": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-2.1.4.tgz", + "integrity": "sha512-FrX5knwUZXPJHVaE9usoevwWqLItiWCFIr2jD20W3Ke6I+IHbDyivMF8MKtrWSeOxeanFx9v8YLeN6/qzAtlzQ==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt" + }, + "node_modules/@p2ppsr/lars": { + "version": "1.5.11", + "resolved": "https://registry.npmjs.org/@p2ppsr/lars/-/lars-1.5.11.tgz", + "integrity": "sha512-y+W79pnm2Zai/4lXelvJITd72mzAynjYALLwSBcL+jN/NHnu3DD6iCPhx/wAj5yC0lgxJJBnI36BO+Dmr7LAvg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "dependencies": { + "@bsv/sdk": "^2.0.14", + "@bsv/wallet-toolbox-client": "^2.1.22", + "axios": "^1.7.9", + "chalk": "^5.3.0", + "chokidar": "^3.5.3", + "commander": "^10.0.0", + "dotenv": "^16.3.1", + "figlet": "^1.8.0", + "fs-extra": "^11.1.1", + "inquirer": "^12.1.0", + "ngrok": "^5.0.0-beta.2", + "open": "^10.1.0", + "tsx": "^3.12.7", + "yaml": "^2.2.1" + }, + "bin": { + "lars": "dist/index.js" + } + }, + "node_modules/@p2ppsr/lars/node_modules/@bsv/sdk": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-2.1.4.tgz", + "integrity": "sha512-FrX5knwUZXPJHVaE9usoevwWqLItiWCFIr2jD20W3Ke6I+IHbDyivMF8MKtrWSeOxeanFx9v8YLeN6/qzAtlzQ==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt" + }, + "node_modules/@p2ppsr/lars/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1996,13 +2033,13 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/cli-cursor": { @@ -3803,32 +3840,6 @@ "node": ">=14.14" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4268,6 +4279,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash-wasm": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.12.0.tgz", + "integrity": "sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -4391,6 +4409,13 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz", + "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==", + "dev": true, + "license": "ISC" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5519,53 +5544,26 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "node": ">= 18" } }, "node_modules/mongodb": { @@ -7540,21 +7538,20 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/tarn": { @@ -8140,11 +8137,14 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/yaml": { "version": "2.7.1", diff --git a/package.json b/package.json index 97c64c8..6c427e1 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "overlay-services" ], "devDependencies": { - "@bsv/cars-cli": "^1.1.4", - "@bsv/lars": "1.4.0" + "@p2ppsr/cars-cli": "^1.2.13", + "@p2ppsr/lars": "^1.5.11" }, "dependencies": { "@bsv/overlay": "^0.1.27",