├── .github └── workflows │ └── build-and-test-package.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── NOTICE.md ├── README.md ├── biome.json ├── docs ├── ledger-app-installing-unix.md └── ledger-app-installing-windows.md ├── package.json ├── pnpm-lock.yaml ├── src ├── commands │ ├── app │ │ ├── attestInput.spec.ts │ │ ├── attestInput.ts │ │ ├── deriveAddress.ts │ │ ├── getAppName.ts │ │ ├── getExtendedPublicKey.ts │ │ ├── getVersion.ts │ │ ├── index.ts │ │ └── signTx.ts │ └── os │ │ ├── closeApp.ts │ │ ├── getCurrentAppInfo.ts │ │ ├── index.ts │ │ └── openApp.ts ├── device.spec.ts ├── device.ts ├── erg.spec.ts ├── erg.ts ├── serialization │ ├── byteWriter.ts │ ├── utils.spec.ts │ └── utils.ts └── types │ ├── attestedBox.ts │ ├── bip32-path.d.ts │ ├── internal.ts │ └── public.ts ├── tsconfig.json ├── tsup.config.ts └── vitest.config.ts /.github/workflows/build-and-test-package.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test package 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18, 20, 22] 16 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: pnpm/action-setup@v4 21 | with: 22 | version: 9 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: "pnpm" 28 | - run: pnpm install --frozen-lockfile 29 | - run: pnpm run build 30 | - run: pnpm test:unit 31 | - run: pnpm test:format 32 | - run: pnpm test:lint 33 | - run: pnpm cov:check 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | 105 | # dist/ directory 106 | dist/ 107 | 108 | .npmrc 109 | .DS_Store -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "biomejs.biome", 4 | "streetsidesoftware.code-spell-checker", 5 | "vitest.explorer" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "explorer.fileNesting.enabled": true, 3 | "explorer.fileNesting.patterns": { 4 | "*.ts": "${capture}.*.ts, ${capture}.js" 5 | }, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "biomejs.biome" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 arobsn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | # NOTICE 2 | 3 | TypeScript Library: @lopatnov/sample-library 4 | 5 | Copyright: 2020 Oleksandr Lopatnov 6 | 7 | This library based on @lopatnov/sample-library 8 | Template link: 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://badgen.net/npm/v/ledger-ergo-js)](https://www.npmjs.com/package/ledger-ergo-js) 2 | [![License](https://badgen.net/github/license/anon-br/ledger-ergo-js)](https://github.com/anon-br/ledger-ergo-js/blob/master/LICENSE) 3 | 4 | # ledger-ergo-js 5 | 6 | JS Library for communication with Ledger Hardware Wallets. 7 | This library is compatible with the [Ledger Ergo Application](https://github.com/tesseract-one/ledger-app-ergo). 8 | 9 | ## Docs 10 | 11 | - [[Windows] Sideloading Ergo Ledger App into a Ledger Nano S device](/docs/ledger-app-installing-windows.md) 12 | - [[Linux/MacOS] Sideloading Ergo Ledger App into a Ledger Nano S device](/docs/ledger-app-installing-unix.md) 13 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", 3 | "files": { 4 | "ignore": ["**/coverage/*", "**/dist/*", "**/node_modules/*", "**/package.json"] 5 | }, 6 | "organizeImports": { 7 | "enabled": true 8 | }, 9 | "formatter": { 10 | "indentStyle": "space", 11 | "lineWidth": 90 12 | }, 13 | "javascript": { 14 | "formatter": { 15 | "trailingCommas": "none" 16 | } 17 | }, 18 | "linter": { 19 | "enabled": true, 20 | "rules": { 21 | "recommended": true, 22 | "style": { 23 | "noParameterAssign": { "level": "off" } 24 | }, 25 | "suspicious": { 26 | "noConsoleLog": { "level": "error" }, 27 | "noAssignInExpressions": { "level": "off" }, 28 | "noConstEnum": { "level": "off" } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/ledger-app-installing-unix.md: -------------------------------------------------------------------------------- 1 | # Side-loading Ergo Ledger App into a Ledger Nano S device on Linux/MacOS 2 | 3 | To install the [Ergo Ledger App](https://github.com/tesseract-one/ledger-app-ergo) we need [virtualenv](https://docs.python.org/3/library/venv.html), this module provides support for creating lightweight “virtual environments”, optionally isolated from the system. 4 | 5 | ## Requirements 6 | 7 | - Ledger Nano S device 8 | - Linux or Mac OS 9 | 10 | ## Steps 11 | 12 | 1. Execute these commands on the terminal to install virtualenv. 13 | 14 | ``` 15 | sudo apt install python3-pip 16 | pip3 install virtualenv 17 | ``` 18 | 19 | 2. Download and extract the latest Ergo Ledger App binary files from https://github.com/tesseract-one/ledger-app-ergo/actions/runs/2315938025; 20 | 21 | 3. Connect and unlock your Ledger Nano S device to your computer; 22 | 23 | 4. Navigate to extracted Ledger App folder; 24 | 25 | 5. Execute these commands on the terminal to run `virtulenv`: 26 | 27 | ``` 28 | virtualenv -p python3 ledger 29 | source ledger/bin/activate 30 | pip3 install ledgerblue 31 | ``` 32 | 33 | 6. Now it's time to deploy the binary file `app.hex` into the Ledger device: 34 | 35 | ``` 36 | python -m ledgerblue.loadApp --targetId 0x31100004 --apdu --tlv --fileName app.hex --appName Ergo --appFlags 0xe0 37 | ``` 38 | 39 | While the process is running, follow the instructions on the device screen to validate and accept the app installation. 40 | -------------------------------------------------------------------------------- /docs/ledger-app-installing-windows.md: -------------------------------------------------------------------------------- 1 | # Side-loading Ergo Ledger App into a Ledger Nano S device on Windows 2 | 3 | To install the [Ergo Ledger App](https://github.com/tesseract-one/ledger-app-ergo) we need [virtualenv](https://docs.python.org/3/library/venv.html), this module provides support for creating lightweight “virtual environments”, optionally isolated from the system. 4 | 5 | ## Requirements 6 | 7 | - Ledger Nano S device 8 | - Windows OS 9 | 10 | ## Steps 11 | 12 | 1. Install python3.x if you don’t have it already installed (you may need to restart the computer) from https://www.python.org/downloads/ 13 | 14 | 2. Download and extract the latest Ergo Ledger App binary files from https://github.com/tesseract-one/ledger-app-ergo/actions/runs/2315938025; 15 | 16 | 3. Using the command prompt, navigate to extracted Ledger App folder; 17 | 18 | 4. Execute these commands on the terminal to install pip and virtualenv. 19 | 20 | ``` 21 | $ py -m ensurepip –upgrade 22 | $ pip3 install virtualenv 23 | ``` 24 | 25 | 5. Execute these commands on the command prompt to run `virtulenv` and activate it: 26 | 27 | ``` 28 | $ python -m venv .\venv 29 | $ venv\Scripts\activate 30 | ``` 31 | 32 | 6. Install Ledger Blue: 33 | 34 | ``` 35 | (venv)$ pip install ledgerblue 36 | ``` 37 | 38 | 7. Connect and unlock your Ledger Nano S device to your computer; 39 | 40 | 8. Now it's time to deploy the binary file `app.hex` into the Ledger device: 41 | 42 | ``` 43 | (venv)$ python -m ledgerblue.loadApp --targetId 0x31100004 --apdu --tlv --fileName app.hex --appName Ergo --appFlags 0xe0 44 | ``` 45 | 46 | While the process is running, follow the instructions on the device screen to validate and accept the app installation. 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ledger-ergo-js", 3 | "version": "0.2.1", 4 | "license": "MIT", 5 | "author": "arobsn", 6 | "description": "An Ergo Platform JS Library for communication with Ledger Hardware Wallets.", 7 | "homepage": "", 8 | "keywords": [ 9 | "ergo", 10 | "ledger", 11 | "library", 12 | "hardwallet", 13 | "wallet" 14 | ], 15 | "engines": { 16 | "node": ">=18", 17 | "pnpm": ">=9" 18 | }, 19 | "type": "module", 20 | "main": "./dist/erg.cjs", 21 | "module": "./dist/erg.js", 22 | "types": "./dist/erg.d.ts", 23 | "exports": { 24 | ".": { 25 | "import": { 26 | "types": "./dist/erg.d.ts", 27 | "default": "./dist/erg.js" 28 | }, 29 | "require": { 30 | "types": "./dist/erg.d.ts", 31 | "default": "./dist/erg.cjs" 32 | } 33 | } 34 | }, 35 | "files": [ 36 | "dist" 37 | ], 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/anon-br/ledger-ergo-js.git" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/anon-br/ledger-ergo-js/issues" 44 | }, 45 | "scripts": { 46 | "build": "tsup", 47 | "fix:format": "biome format --write", 48 | "fix:lint": "biome lint --write", 49 | "test:lint": "biome lint", 50 | "test:format": "biome format", 51 | "test:unit": "vitest run --no-coverage --environment=node", 52 | "watch:unit": "vitest --no-coverage --reporter=dot", 53 | "cov:check": "vitest run --coverage", 54 | "cov:open": "vitest run --coverage ; open-cli coverage/index.html" 55 | }, 56 | "publishConfig": { 57 | "access": "public" 58 | }, 59 | "dependencies": { 60 | "@fleet-sdk/common": "^0.8.5", 61 | "@fleet-sdk/core": "^0.8.5", 62 | "@fleet-sdk/crypto": "^0.8.5", 63 | "bip32-path": "^0.4.2" 64 | }, 65 | "devDependencies": { 66 | "@biomejs/biome": "^1.9.4", 67 | "@ledgerhq/hw-transport": "^6.31.4", 68 | "@ledgerhq/hw-transport-mocker": "^6.29.4", 69 | "@types/node": "^22.15.21", 70 | "@vitest/coverage-v8": "^3.1.4", 71 | "open-cli": "^8.0.0", 72 | "tsup": "^8.5.0", 73 | "typescript": "^5.8.3", 74 | "vitest": "^3.1.4" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@fleet-sdk/common': 12 | specifier: ^0.8.5 13 | version: 0.8.5 14 | '@fleet-sdk/core': 15 | specifier: ^0.8.5 16 | version: 0.8.5 17 | '@fleet-sdk/crypto': 18 | specifier: ^0.8.5 19 | version: 0.8.5 20 | bip32-path: 21 | specifier: ^0.4.2 22 | version: 0.4.2 23 | devDependencies: 24 | '@biomejs/biome': 25 | specifier: ^1.9.4 26 | version: 1.9.4 27 | '@ledgerhq/hw-transport': 28 | specifier: ^6.31.4 29 | version: 6.31.4 30 | '@ledgerhq/hw-transport-mocker': 31 | specifier: ^6.29.4 32 | version: 6.29.4 33 | '@types/node': 34 | specifier: ^22.15.21 35 | version: 22.15.21 36 | '@vitest/coverage-v8': 37 | specifier: ^3.1.4 38 | version: 3.1.4(vitest@3.1.4(@types/node@22.15.21)(yaml@2.4.5)) 39 | open-cli: 40 | specifier: ^8.0.0 41 | version: 8.0.0 42 | tsup: 43 | specifier: ^8.5.0 44 | version: 8.5.0(postcss@8.5.3)(typescript@5.8.3)(yaml@2.4.5) 45 | typescript: 46 | specifier: ^5.8.3 47 | version: 5.8.3 48 | vitest: 49 | specifier: ^3.1.4 50 | version: 3.1.4(@types/node@22.15.21)(yaml@2.4.5) 51 | 52 | packages: 53 | 54 | '@ampproject/remapping@2.3.0': 55 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 56 | engines: {node: '>=6.0.0'} 57 | 58 | '@babel/helper-string-parser@7.27.1': 59 | resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} 60 | engines: {node: '>=6.9.0'} 61 | 62 | '@babel/helper-validator-identifier@7.27.1': 63 | resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} 64 | engines: {node: '>=6.9.0'} 65 | 66 | '@babel/parser@7.27.2': 67 | resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==} 68 | engines: {node: '>=6.0.0'} 69 | hasBin: true 70 | 71 | '@babel/types@7.27.1': 72 | resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} 73 | engines: {node: '>=6.9.0'} 74 | 75 | '@bcoe/v8-coverage@1.0.2': 76 | resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} 77 | engines: {node: '>=18'} 78 | 79 | '@biomejs/biome@1.9.4': 80 | resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} 81 | engines: {node: '>=14.21.3'} 82 | hasBin: true 83 | 84 | '@biomejs/cli-darwin-arm64@1.9.4': 85 | resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} 86 | engines: {node: '>=14.21.3'} 87 | cpu: [arm64] 88 | os: [darwin] 89 | 90 | '@biomejs/cli-darwin-x64@1.9.4': 91 | resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} 92 | engines: {node: '>=14.21.3'} 93 | cpu: [x64] 94 | os: [darwin] 95 | 96 | '@biomejs/cli-linux-arm64-musl@1.9.4': 97 | resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} 98 | engines: {node: '>=14.21.3'} 99 | cpu: [arm64] 100 | os: [linux] 101 | 102 | '@biomejs/cli-linux-arm64@1.9.4': 103 | resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} 104 | engines: {node: '>=14.21.3'} 105 | cpu: [arm64] 106 | os: [linux] 107 | 108 | '@biomejs/cli-linux-x64-musl@1.9.4': 109 | resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} 110 | engines: {node: '>=14.21.3'} 111 | cpu: [x64] 112 | os: [linux] 113 | 114 | '@biomejs/cli-linux-x64@1.9.4': 115 | resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} 116 | engines: {node: '>=14.21.3'} 117 | cpu: [x64] 118 | os: [linux] 119 | 120 | '@biomejs/cli-win32-arm64@1.9.4': 121 | resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} 122 | engines: {node: '>=14.21.3'} 123 | cpu: [arm64] 124 | os: [win32] 125 | 126 | '@biomejs/cli-win32-x64@1.9.4': 127 | resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} 128 | engines: {node: '>=14.21.3'} 129 | cpu: [x64] 130 | os: [win32] 131 | 132 | '@esbuild/aix-ppc64@0.25.4': 133 | resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} 134 | engines: {node: '>=18'} 135 | cpu: [ppc64] 136 | os: [aix] 137 | 138 | '@esbuild/android-arm64@0.25.4': 139 | resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} 140 | engines: {node: '>=18'} 141 | cpu: [arm64] 142 | os: [android] 143 | 144 | '@esbuild/android-arm@0.25.4': 145 | resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} 146 | engines: {node: '>=18'} 147 | cpu: [arm] 148 | os: [android] 149 | 150 | '@esbuild/android-x64@0.25.4': 151 | resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} 152 | engines: {node: '>=18'} 153 | cpu: [x64] 154 | os: [android] 155 | 156 | '@esbuild/darwin-arm64@0.25.4': 157 | resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} 158 | engines: {node: '>=18'} 159 | cpu: [arm64] 160 | os: [darwin] 161 | 162 | '@esbuild/darwin-x64@0.25.4': 163 | resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} 164 | engines: {node: '>=18'} 165 | cpu: [x64] 166 | os: [darwin] 167 | 168 | '@esbuild/freebsd-arm64@0.25.4': 169 | resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} 170 | engines: {node: '>=18'} 171 | cpu: [arm64] 172 | os: [freebsd] 173 | 174 | '@esbuild/freebsd-x64@0.25.4': 175 | resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} 176 | engines: {node: '>=18'} 177 | cpu: [x64] 178 | os: [freebsd] 179 | 180 | '@esbuild/linux-arm64@0.25.4': 181 | resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} 182 | engines: {node: '>=18'} 183 | cpu: [arm64] 184 | os: [linux] 185 | 186 | '@esbuild/linux-arm@0.25.4': 187 | resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} 188 | engines: {node: '>=18'} 189 | cpu: [arm] 190 | os: [linux] 191 | 192 | '@esbuild/linux-ia32@0.25.4': 193 | resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} 194 | engines: {node: '>=18'} 195 | cpu: [ia32] 196 | os: [linux] 197 | 198 | '@esbuild/linux-loong64@0.25.4': 199 | resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} 200 | engines: {node: '>=18'} 201 | cpu: [loong64] 202 | os: [linux] 203 | 204 | '@esbuild/linux-mips64el@0.25.4': 205 | resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} 206 | engines: {node: '>=18'} 207 | cpu: [mips64el] 208 | os: [linux] 209 | 210 | '@esbuild/linux-ppc64@0.25.4': 211 | resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} 212 | engines: {node: '>=18'} 213 | cpu: [ppc64] 214 | os: [linux] 215 | 216 | '@esbuild/linux-riscv64@0.25.4': 217 | resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} 218 | engines: {node: '>=18'} 219 | cpu: [riscv64] 220 | os: [linux] 221 | 222 | '@esbuild/linux-s390x@0.25.4': 223 | resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} 224 | engines: {node: '>=18'} 225 | cpu: [s390x] 226 | os: [linux] 227 | 228 | '@esbuild/linux-x64@0.25.4': 229 | resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} 230 | engines: {node: '>=18'} 231 | cpu: [x64] 232 | os: [linux] 233 | 234 | '@esbuild/netbsd-arm64@0.25.4': 235 | resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} 236 | engines: {node: '>=18'} 237 | cpu: [arm64] 238 | os: [netbsd] 239 | 240 | '@esbuild/netbsd-x64@0.25.4': 241 | resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} 242 | engines: {node: '>=18'} 243 | cpu: [x64] 244 | os: [netbsd] 245 | 246 | '@esbuild/openbsd-arm64@0.25.4': 247 | resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} 248 | engines: {node: '>=18'} 249 | cpu: [arm64] 250 | os: [openbsd] 251 | 252 | '@esbuild/openbsd-x64@0.25.4': 253 | resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} 254 | engines: {node: '>=18'} 255 | cpu: [x64] 256 | os: [openbsd] 257 | 258 | '@esbuild/sunos-x64@0.25.4': 259 | resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} 260 | engines: {node: '>=18'} 261 | cpu: [x64] 262 | os: [sunos] 263 | 264 | '@esbuild/win32-arm64@0.25.4': 265 | resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} 266 | engines: {node: '>=18'} 267 | cpu: [arm64] 268 | os: [win32] 269 | 270 | '@esbuild/win32-ia32@0.25.4': 271 | resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} 272 | engines: {node: '>=18'} 273 | cpu: [ia32] 274 | os: [win32] 275 | 276 | '@esbuild/win32-x64@0.25.4': 277 | resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} 278 | engines: {node: '>=18'} 279 | cpu: [x64] 280 | os: [win32] 281 | 282 | '@fleet-sdk/common@0.8.5': 283 | resolution: {integrity: sha512-A/R7U2jOVNpBzlB81YsWY9uMlfxwbqalUoPRQR4SSHnB7T1z2WPUsup3WEt7uA74CSQTIleCHECItoDIRA1jpg==} 284 | engines: {node: '>=18'} 285 | 286 | '@fleet-sdk/core@0.8.5': 287 | resolution: {integrity: sha512-YKnjmPLstLWsCpih9LxpqMH05/+EtDK8KRW9AAzRgxTIpPNArrXYrYDMSWfC7sFgKRxJVM9AWHYubKMcWYlRDA==} 288 | engines: {node: '>=18'} 289 | 290 | '@fleet-sdk/crypto@0.8.5': 291 | resolution: {integrity: sha512-EQ+xdDZLXtXeFZb1507ugc+LM9LLTVMsnrA8s0u/78VRikdt3doKwNvD0Ycno7JXo1hAMu0R6T1o/zoafXcmtQ==} 292 | engines: {node: '>=18'} 293 | 294 | '@fleet-sdk/serializer@0.8.5': 295 | resolution: {integrity: sha512-TaF1owAcfYMpLFWtI1lP4boq042+7+fxqrkUd+dh5Feh+4ke8y8/2mMXca01PQuBTkML1Gq1CFouyZXo+J2V4w==} 296 | engines: {node: '>=18'} 297 | 298 | '@isaacs/cliui@8.0.2': 299 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 300 | engines: {node: '>=12'} 301 | 302 | '@istanbuljs/schema@0.1.3': 303 | resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} 304 | engines: {node: '>=8'} 305 | 306 | '@jridgewell/gen-mapping@0.3.8': 307 | resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} 308 | engines: {node: '>=6.0.0'} 309 | 310 | '@jridgewell/resolve-uri@3.1.2': 311 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 312 | engines: {node: '>=6.0.0'} 313 | 314 | '@jridgewell/set-array@1.2.1': 315 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 316 | engines: {node: '>=6.0.0'} 317 | 318 | '@jridgewell/sourcemap-codec@1.5.0': 319 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 320 | 321 | '@jridgewell/trace-mapping@0.3.25': 322 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 323 | 324 | '@ledgerhq/devices@8.4.4': 325 | resolution: {integrity: sha512-sz/ryhe/R687RHtevIE9RlKaV8kkKykUV4k29e7GAVwzHX1gqG+O75cu1NCJUHLbp3eABV5FdvZejqRUlLis9A==} 326 | 327 | '@ledgerhq/errors@6.19.1': 328 | resolution: {integrity: sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw==} 329 | 330 | '@ledgerhq/hw-transport-mocker@6.29.4': 331 | resolution: {integrity: sha512-CLDIpQ/eqU8qrCYGY9MyHa+oMgqs6PuNkWtqbcaS4AzNx8L/9bv7y8CZwCjxX6oB/2ZEq42RlL6oZ6Ou3oHnoQ==} 332 | 333 | '@ledgerhq/hw-transport@6.31.4': 334 | resolution: {integrity: sha512-6c1ir/cXWJm5dCWdq55NPgCJ3UuKuuxRvf//Xs36Bq9BwkV2YaRQhZITAkads83l07NAdR16hkTWqqpwFMaI6A==} 335 | 336 | '@ledgerhq/logs@6.12.0': 337 | resolution: {integrity: sha512-ExDoj1QV5eC6TEbMdLUMMk9cfvNKhhv5gXol4SmULRVCx/3iyCPhJ74nsb3S0Vb+/f+XujBEj3vQn5+cwS0fNA==} 338 | 339 | '@noble/hashes@1.8.0': 340 | resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} 341 | engines: {node: ^14.21.3 || >=16} 342 | 343 | '@pkgjs/parseargs@0.11.0': 344 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 345 | engines: {node: '>=14'} 346 | 347 | '@rollup/rollup-android-arm-eabi@4.41.0': 348 | resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==} 349 | cpu: [arm] 350 | os: [android] 351 | 352 | '@rollup/rollup-android-arm64@4.41.0': 353 | resolution: {integrity: sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==} 354 | cpu: [arm64] 355 | os: [android] 356 | 357 | '@rollup/rollup-darwin-arm64@4.41.0': 358 | resolution: {integrity: sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==} 359 | cpu: [arm64] 360 | os: [darwin] 361 | 362 | '@rollup/rollup-darwin-x64@4.41.0': 363 | resolution: {integrity: sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==} 364 | cpu: [x64] 365 | os: [darwin] 366 | 367 | '@rollup/rollup-freebsd-arm64@4.41.0': 368 | resolution: {integrity: sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==} 369 | cpu: [arm64] 370 | os: [freebsd] 371 | 372 | '@rollup/rollup-freebsd-x64@4.41.0': 373 | resolution: {integrity: sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==} 374 | cpu: [x64] 375 | os: [freebsd] 376 | 377 | '@rollup/rollup-linux-arm-gnueabihf@4.41.0': 378 | resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} 379 | cpu: [arm] 380 | os: [linux] 381 | 382 | '@rollup/rollup-linux-arm-musleabihf@4.41.0': 383 | resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} 384 | cpu: [arm] 385 | os: [linux] 386 | 387 | '@rollup/rollup-linux-arm64-gnu@4.41.0': 388 | resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} 389 | cpu: [arm64] 390 | os: [linux] 391 | 392 | '@rollup/rollup-linux-arm64-musl@4.41.0': 393 | resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} 394 | cpu: [arm64] 395 | os: [linux] 396 | 397 | '@rollup/rollup-linux-loongarch64-gnu@4.41.0': 398 | resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} 399 | cpu: [loong64] 400 | os: [linux] 401 | 402 | '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': 403 | resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} 404 | cpu: [ppc64] 405 | os: [linux] 406 | 407 | '@rollup/rollup-linux-riscv64-gnu@4.41.0': 408 | resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} 409 | cpu: [riscv64] 410 | os: [linux] 411 | 412 | '@rollup/rollup-linux-riscv64-musl@4.41.0': 413 | resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} 414 | cpu: [riscv64] 415 | os: [linux] 416 | 417 | '@rollup/rollup-linux-s390x-gnu@4.41.0': 418 | resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} 419 | cpu: [s390x] 420 | os: [linux] 421 | 422 | '@rollup/rollup-linux-x64-gnu@4.41.0': 423 | resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} 424 | cpu: [x64] 425 | os: [linux] 426 | 427 | '@rollup/rollup-linux-x64-musl@4.41.0': 428 | resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} 429 | cpu: [x64] 430 | os: [linux] 431 | 432 | '@rollup/rollup-win32-arm64-msvc@4.41.0': 433 | resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} 434 | cpu: [arm64] 435 | os: [win32] 436 | 437 | '@rollup/rollup-win32-ia32-msvc@4.41.0': 438 | resolution: {integrity: sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==} 439 | cpu: [ia32] 440 | os: [win32] 441 | 442 | '@rollup/rollup-win32-x64-msvc@4.41.0': 443 | resolution: {integrity: sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==} 444 | cpu: [x64] 445 | os: [win32] 446 | 447 | '@scure/base@1.2.5': 448 | resolution: {integrity: sha512-9rE6EOVeIQzt5TSu4v+K523F8u6DhBsoZWPGKlnCshhlDhy0kJzUX4V+tr2dWmzF1GdekvThABoEQBGBQI7xZw==} 449 | 450 | '@tokenizer/token@0.3.0': 451 | resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} 452 | 453 | '@types/estree@1.0.7': 454 | resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} 455 | 456 | '@types/node@22.15.21': 457 | resolution: {integrity: sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==} 458 | 459 | '@vitest/coverage-v8@3.1.4': 460 | resolution: {integrity: sha512-G4p6OtioySL+hPV7Y6JHlhpsODbJzt1ndwHAFkyk6vVjpK03PFsKnauZIzcd0PrK4zAbc5lc+jeZ+eNGiMA+iw==} 461 | peerDependencies: 462 | '@vitest/browser': 3.1.4 463 | vitest: 3.1.4 464 | peerDependenciesMeta: 465 | '@vitest/browser': 466 | optional: true 467 | 468 | '@vitest/expect@3.1.4': 469 | resolution: {integrity: sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==} 470 | 471 | '@vitest/mocker@3.1.4': 472 | resolution: {integrity: sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==} 473 | peerDependencies: 474 | msw: ^2.4.9 475 | vite: ^5.0.0 || ^6.0.0 476 | peerDependenciesMeta: 477 | msw: 478 | optional: true 479 | vite: 480 | optional: true 481 | 482 | '@vitest/pretty-format@3.1.4': 483 | resolution: {integrity: sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==} 484 | 485 | '@vitest/runner@3.1.4': 486 | resolution: {integrity: sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==} 487 | 488 | '@vitest/snapshot@3.1.4': 489 | resolution: {integrity: sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==} 490 | 491 | '@vitest/spy@3.1.4': 492 | resolution: {integrity: sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==} 493 | 494 | '@vitest/utils@3.1.4': 495 | resolution: {integrity: sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==} 496 | 497 | abort-controller@3.0.0: 498 | resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} 499 | engines: {node: '>=6.5'} 500 | 501 | acorn@8.14.1: 502 | resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 503 | engines: {node: '>=0.4.0'} 504 | hasBin: true 505 | 506 | ansi-regex@5.0.1: 507 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 508 | engines: {node: '>=8'} 509 | 510 | ansi-regex@6.1.0: 511 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 512 | engines: {node: '>=12'} 513 | 514 | ansi-styles@4.3.0: 515 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 516 | engines: {node: '>=8'} 517 | 518 | ansi-styles@6.2.1: 519 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 520 | engines: {node: '>=12'} 521 | 522 | any-promise@1.3.0: 523 | resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 524 | 525 | assertion-error@2.0.1: 526 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 527 | engines: {node: '>=12'} 528 | 529 | balanced-match@1.0.2: 530 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 531 | 532 | base64-js@1.5.1: 533 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 534 | 535 | bip32-path@0.4.2: 536 | resolution: {integrity: sha512-ZBMCELjJfcNMkz5bDuJ1WrYvjlhEF5k6mQ8vUr4N7MbVRsXei7ZOg8VhhwMfNiW68NWmLkgkc6WvTickrLGprQ==} 537 | 538 | brace-expansion@2.0.1: 539 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 540 | 541 | buffer@6.0.3: 542 | resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 543 | 544 | bundle-name@4.1.0: 545 | resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} 546 | engines: {node: '>=18'} 547 | 548 | bundle-require@5.1.0: 549 | resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} 550 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 551 | peerDependencies: 552 | esbuild: '>=0.18' 553 | 554 | cac@6.7.14: 555 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 556 | engines: {node: '>=8'} 557 | 558 | chai@5.2.0: 559 | resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} 560 | engines: {node: '>=12'} 561 | 562 | check-error@2.1.1: 563 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 564 | engines: {node: '>= 16'} 565 | 566 | chokidar@4.0.3: 567 | resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} 568 | engines: {node: '>= 14.16.0'} 569 | 570 | color-convert@2.0.1: 571 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 572 | engines: {node: '>=7.0.0'} 573 | 574 | color-name@1.1.4: 575 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 576 | 577 | commander@4.1.1: 578 | resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 579 | engines: {node: '>= 6'} 580 | 581 | confbox@0.1.8: 582 | resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} 583 | 584 | consola@3.4.2: 585 | resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} 586 | engines: {node: ^14.18.0 || >=16.10.0} 587 | 588 | cross-spawn@7.0.6: 589 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 590 | engines: {node: '>= 8'} 591 | 592 | crypto-random-string@4.0.0: 593 | resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} 594 | engines: {node: '>=12'} 595 | 596 | debug@4.4.1: 597 | resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} 598 | engines: {node: '>=6.0'} 599 | peerDependencies: 600 | supports-color: '*' 601 | peerDependenciesMeta: 602 | supports-color: 603 | optional: true 604 | 605 | deep-eql@5.0.2: 606 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} 607 | engines: {node: '>=6'} 608 | 609 | default-browser-id@5.0.0: 610 | resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} 611 | engines: {node: '>=18'} 612 | 613 | default-browser@5.2.1: 614 | resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} 615 | engines: {node: '>=18'} 616 | 617 | define-lazy-prop@3.0.0: 618 | resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} 619 | engines: {node: '>=12'} 620 | 621 | eastasianwidth@0.2.0: 622 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 623 | 624 | emoji-regex@8.0.0: 625 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 626 | 627 | emoji-regex@9.2.2: 628 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 629 | 630 | es-module-lexer@1.7.0: 631 | resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} 632 | 633 | esbuild@0.25.4: 634 | resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} 635 | engines: {node: '>=18'} 636 | hasBin: true 637 | 638 | estree-walker@3.0.3: 639 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 640 | 641 | event-target-shim@5.0.1: 642 | resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} 643 | engines: {node: '>=6'} 644 | 645 | events@3.3.0: 646 | resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 647 | engines: {node: '>=0.8.x'} 648 | 649 | expect-type@1.2.1: 650 | resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} 651 | engines: {node: '>=12.0.0'} 652 | 653 | fdir@6.4.4: 654 | resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} 655 | peerDependencies: 656 | picomatch: ^3 || ^4 657 | peerDependenciesMeta: 658 | picomatch: 659 | optional: true 660 | 661 | file-type@18.7.0: 662 | resolution: {integrity: sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw==} 663 | engines: {node: '>=14.16'} 664 | 665 | fix-dts-default-cjs-exports@1.0.1: 666 | resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} 667 | 668 | foreground-child@3.3.1: 669 | resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} 670 | engines: {node: '>=14'} 671 | 672 | fsevents@2.3.3: 673 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 674 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 675 | os: [darwin] 676 | 677 | get-stdin@9.0.0: 678 | resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} 679 | engines: {node: '>=12'} 680 | 681 | glob@10.4.5: 682 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 683 | hasBin: true 684 | 685 | has-flag@4.0.0: 686 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 687 | engines: {node: '>=8'} 688 | 689 | html-escaper@2.0.2: 690 | resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} 691 | 692 | ieee754@1.2.1: 693 | resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 694 | 695 | is-docker@3.0.0: 696 | resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} 697 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 698 | hasBin: true 699 | 700 | is-fullwidth-code-point@3.0.0: 701 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 702 | engines: {node: '>=8'} 703 | 704 | is-inside-container@1.0.0: 705 | resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} 706 | engines: {node: '>=14.16'} 707 | hasBin: true 708 | 709 | is-stream@3.0.0: 710 | resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} 711 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 712 | 713 | is-wsl@3.1.0: 714 | resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} 715 | engines: {node: '>=16'} 716 | 717 | isexe@2.0.0: 718 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 719 | 720 | istanbul-lib-coverage@3.2.2: 721 | resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} 722 | engines: {node: '>=8'} 723 | 724 | istanbul-lib-report@3.0.1: 725 | resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} 726 | engines: {node: '>=10'} 727 | 728 | istanbul-lib-source-maps@5.0.6: 729 | resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} 730 | engines: {node: '>=10'} 731 | 732 | istanbul-reports@3.1.7: 733 | resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} 734 | engines: {node: '>=8'} 735 | 736 | jackspeak@3.4.3: 737 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 738 | 739 | joycon@3.1.1: 740 | resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} 741 | engines: {node: '>=10'} 742 | 743 | lilconfig@3.1.3: 744 | resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} 745 | engines: {node: '>=14'} 746 | 747 | lines-and-columns@1.2.4: 748 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 749 | 750 | load-tsconfig@0.2.5: 751 | resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} 752 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 753 | 754 | lodash.sortby@4.7.0: 755 | resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} 756 | 757 | loupe@3.1.3: 758 | resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} 759 | 760 | lru-cache@10.4.3: 761 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 762 | 763 | magic-string@0.30.17: 764 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} 765 | 766 | magicast@0.3.5: 767 | resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} 768 | 769 | make-dir@4.0.0: 770 | resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} 771 | engines: {node: '>=10'} 772 | 773 | meow@12.1.1: 774 | resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} 775 | engines: {node: '>=16.10'} 776 | 777 | minimatch@9.0.5: 778 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 779 | engines: {node: '>=16 || 14 >=14.17'} 780 | 781 | minipass@7.1.2: 782 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 783 | engines: {node: '>=16 || 14 >=14.17'} 784 | 785 | mlly@1.7.4: 786 | resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} 787 | 788 | ms@2.1.3: 789 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 790 | 791 | mz@2.7.0: 792 | resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 793 | 794 | nanoid@3.3.11: 795 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 796 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 797 | hasBin: true 798 | 799 | object-assign@4.1.1: 800 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 801 | engines: {node: '>=0.10.0'} 802 | 803 | open-cli@8.0.0: 804 | resolution: {integrity: sha512-3muD3BbfLyzl+aMVSEfn2FfOqGdPYR0O4KNnxXsLEPE2q9OSjBfJAaB6XKbrUzLgymoSMejvb5jpXJfru/Ko2A==} 805 | engines: {node: '>=18'} 806 | hasBin: true 807 | 808 | open@10.1.2: 809 | resolution: {integrity: sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==} 810 | engines: {node: '>=18'} 811 | 812 | package-json-from-dist@1.0.1: 813 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 814 | 815 | path-key@3.1.1: 816 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 817 | engines: {node: '>=8'} 818 | 819 | path-scurry@1.11.1: 820 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 821 | engines: {node: '>=16 || 14 >=14.18'} 822 | 823 | pathe@2.0.3: 824 | resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 825 | 826 | pathval@2.0.0: 827 | resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} 828 | engines: {node: '>= 14.16'} 829 | 830 | peek-readable@5.4.2: 831 | resolution: {integrity: sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==} 832 | engines: {node: '>=14.16'} 833 | 834 | picocolors@1.1.1: 835 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 836 | 837 | picomatch@4.0.2: 838 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 839 | engines: {node: '>=12'} 840 | 841 | pirates@4.0.7: 842 | resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} 843 | engines: {node: '>= 6'} 844 | 845 | pkg-types@1.3.1: 846 | resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} 847 | 848 | postcss-load-config@6.0.1: 849 | resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} 850 | engines: {node: '>= 18'} 851 | peerDependencies: 852 | jiti: '>=1.21.0' 853 | postcss: '>=8.0.9' 854 | tsx: ^4.8.1 855 | yaml: ^2.4.2 856 | peerDependenciesMeta: 857 | jiti: 858 | optional: true 859 | postcss: 860 | optional: true 861 | tsx: 862 | optional: true 863 | yaml: 864 | optional: true 865 | 866 | postcss@8.5.3: 867 | resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} 868 | engines: {node: ^10 || ^12 || >=14} 869 | 870 | process@0.11.10: 871 | resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} 872 | engines: {node: '>= 0.6.0'} 873 | 874 | punycode@2.3.1: 875 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 876 | engines: {node: '>=6'} 877 | 878 | readable-stream@4.7.0: 879 | resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} 880 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 881 | 882 | readable-web-to-node-stream@3.0.4: 883 | resolution: {integrity: sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==} 884 | engines: {node: '>=8'} 885 | 886 | readdirp@4.1.2: 887 | resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} 888 | engines: {node: '>= 14.18.0'} 889 | 890 | resolve-from@5.0.0: 891 | resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 892 | engines: {node: '>=8'} 893 | 894 | rollup@4.41.0: 895 | resolution: {integrity: sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==} 896 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 897 | hasBin: true 898 | 899 | run-applescript@7.0.0: 900 | resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} 901 | engines: {node: '>=18'} 902 | 903 | rxjs@7.8.2: 904 | resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} 905 | 906 | safe-buffer@5.2.1: 907 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 908 | 909 | semver@7.7.2: 910 | resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} 911 | engines: {node: '>=10'} 912 | hasBin: true 913 | 914 | shebang-command@2.0.0: 915 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 916 | engines: {node: '>=8'} 917 | 918 | shebang-regex@3.0.0: 919 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 920 | engines: {node: '>=8'} 921 | 922 | siginfo@2.0.0: 923 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 924 | 925 | signal-exit@4.1.0: 926 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 927 | engines: {node: '>=14'} 928 | 929 | source-map-js@1.2.1: 930 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 931 | engines: {node: '>=0.10.0'} 932 | 933 | source-map@0.8.0-beta.0: 934 | resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} 935 | engines: {node: '>= 8'} 936 | 937 | stackback@0.0.2: 938 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 939 | 940 | std-env@3.9.0: 941 | resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} 942 | 943 | string-width@4.2.3: 944 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 945 | engines: {node: '>=8'} 946 | 947 | string-width@5.1.2: 948 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 949 | engines: {node: '>=12'} 950 | 951 | string_decoder@1.3.0: 952 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 953 | 954 | strip-ansi@6.0.1: 955 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 956 | engines: {node: '>=8'} 957 | 958 | strip-ansi@7.1.0: 959 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 960 | engines: {node: '>=12'} 961 | 962 | strtok3@7.1.1: 963 | resolution: {integrity: sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==} 964 | engines: {node: '>=16'} 965 | 966 | sucrase@3.35.0: 967 | resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} 968 | engines: {node: '>=16 || 14 >=14.17'} 969 | hasBin: true 970 | 971 | supports-color@7.2.0: 972 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 973 | engines: {node: '>=8'} 974 | 975 | temp-dir@3.0.0: 976 | resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} 977 | engines: {node: '>=14.16'} 978 | 979 | tempy@3.1.0: 980 | resolution: {integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==} 981 | engines: {node: '>=14.16'} 982 | 983 | test-exclude@7.0.1: 984 | resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} 985 | engines: {node: '>=18'} 986 | 987 | thenify-all@1.6.0: 988 | resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} 989 | engines: {node: '>=0.8'} 990 | 991 | thenify@3.3.1: 992 | resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 993 | 994 | tinybench@2.9.0: 995 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 996 | 997 | tinyexec@0.3.2: 998 | resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} 999 | 1000 | tinyglobby@0.2.13: 1001 | resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} 1002 | engines: {node: '>=12.0.0'} 1003 | 1004 | tinypool@1.0.2: 1005 | resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} 1006 | engines: {node: ^18.0.0 || >=20.0.0} 1007 | 1008 | tinyrainbow@2.0.0: 1009 | resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} 1010 | engines: {node: '>=14.0.0'} 1011 | 1012 | tinyspy@3.0.2: 1013 | resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} 1014 | engines: {node: '>=14.0.0'} 1015 | 1016 | token-types@5.0.1: 1017 | resolution: {integrity: sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==} 1018 | engines: {node: '>=14.16'} 1019 | 1020 | tr46@1.0.1: 1021 | resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} 1022 | 1023 | tree-kill@1.2.2: 1024 | resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 1025 | hasBin: true 1026 | 1027 | ts-interface-checker@0.1.13: 1028 | resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 1029 | 1030 | tslib@2.8.1: 1031 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1032 | 1033 | tsup@8.5.0: 1034 | resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} 1035 | engines: {node: '>=18'} 1036 | hasBin: true 1037 | peerDependencies: 1038 | '@microsoft/api-extractor': ^7.36.0 1039 | '@swc/core': ^1 1040 | postcss: ^8.4.12 1041 | typescript: '>=4.5.0' 1042 | peerDependenciesMeta: 1043 | '@microsoft/api-extractor': 1044 | optional: true 1045 | '@swc/core': 1046 | optional: true 1047 | postcss: 1048 | optional: true 1049 | typescript: 1050 | optional: true 1051 | 1052 | type-fest@1.4.0: 1053 | resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} 1054 | engines: {node: '>=10'} 1055 | 1056 | type-fest@2.19.0: 1057 | resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} 1058 | engines: {node: '>=12.20'} 1059 | 1060 | typescript@5.8.3: 1061 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 1062 | engines: {node: '>=14.17'} 1063 | hasBin: true 1064 | 1065 | ufo@1.6.1: 1066 | resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} 1067 | 1068 | undici-types@6.21.0: 1069 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 1070 | 1071 | unique-string@3.0.0: 1072 | resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} 1073 | engines: {node: '>=12'} 1074 | 1075 | vite-node@3.1.4: 1076 | resolution: {integrity: sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==} 1077 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 1078 | hasBin: true 1079 | 1080 | vite@6.3.5: 1081 | resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} 1082 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 1083 | hasBin: true 1084 | peerDependencies: 1085 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 1086 | jiti: '>=1.21.0' 1087 | less: '*' 1088 | lightningcss: ^1.21.0 1089 | sass: '*' 1090 | sass-embedded: '*' 1091 | stylus: '*' 1092 | sugarss: '*' 1093 | terser: ^5.16.0 1094 | tsx: ^4.8.1 1095 | yaml: ^2.4.2 1096 | peerDependenciesMeta: 1097 | '@types/node': 1098 | optional: true 1099 | jiti: 1100 | optional: true 1101 | less: 1102 | optional: true 1103 | lightningcss: 1104 | optional: true 1105 | sass: 1106 | optional: true 1107 | sass-embedded: 1108 | optional: true 1109 | stylus: 1110 | optional: true 1111 | sugarss: 1112 | optional: true 1113 | terser: 1114 | optional: true 1115 | tsx: 1116 | optional: true 1117 | yaml: 1118 | optional: true 1119 | 1120 | vitest@3.1.4: 1121 | resolution: {integrity: sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==} 1122 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 1123 | hasBin: true 1124 | peerDependencies: 1125 | '@edge-runtime/vm': '*' 1126 | '@types/debug': ^4.1.12 1127 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 1128 | '@vitest/browser': 3.1.4 1129 | '@vitest/ui': 3.1.4 1130 | happy-dom: '*' 1131 | jsdom: '*' 1132 | peerDependenciesMeta: 1133 | '@edge-runtime/vm': 1134 | optional: true 1135 | '@types/debug': 1136 | optional: true 1137 | '@types/node': 1138 | optional: true 1139 | '@vitest/browser': 1140 | optional: true 1141 | '@vitest/ui': 1142 | optional: true 1143 | happy-dom: 1144 | optional: true 1145 | jsdom: 1146 | optional: true 1147 | 1148 | webidl-conversions@4.0.2: 1149 | resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} 1150 | 1151 | whatwg-url@7.1.0: 1152 | resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} 1153 | 1154 | which@2.0.2: 1155 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1156 | engines: {node: '>= 8'} 1157 | hasBin: true 1158 | 1159 | why-is-node-running@2.3.0: 1160 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 1161 | engines: {node: '>=8'} 1162 | hasBin: true 1163 | 1164 | wrap-ansi@7.0.0: 1165 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1166 | engines: {node: '>=10'} 1167 | 1168 | wrap-ansi@8.1.0: 1169 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 1170 | engines: {node: '>=12'} 1171 | 1172 | yaml@2.4.5: 1173 | resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} 1174 | engines: {node: '>= 14'} 1175 | hasBin: true 1176 | 1177 | snapshots: 1178 | 1179 | '@ampproject/remapping@2.3.0': 1180 | dependencies: 1181 | '@jridgewell/gen-mapping': 0.3.8 1182 | '@jridgewell/trace-mapping': 0.3.25 1183 | 1184 | '@babel/helper-string-parser@7.27.1': {} 1185 | 1186 | '@babel/helper-validator-identifier@7.27.1': {} 1187 | 1188 | '@babel/parser@7.27.2': 1189 | dependencies: 1190 | '@babel/types': 7.27.1 1191 | 1192 | '@babel/types@7.27.1': 1193 | dependencies: 1194 | '@babel/helper-string-parser': 7.27.1 1195 | '@babel/helper-validator-identifier': 7.27.1 1196 | 1197 | '@bcoe/v8-coverage@1.0.2': {} 1198 | 1199 | '@biomejs/biome@1.9.4': 1200 | optionalDependencies: 1201 | '@biomejs/cli-darwin-arm64': 1.9.4 1202 | '@biomejs/cli-darwin-x64': 1.9.4 1203 | '@biomejs/cli-linux-arm64': 1.9.4 1204 | '@biomejs/cli-linux-arm64-musl': 1.9.4 1205 | '@biomejs/cli-linux-x64': 1.9.4 1206 | '@biomejs/cli-linux-x64-musl': 1.9.4 1207 | '@biomejs/cli-win32-arm64': 1.9.4 1208 | '@biomejs/cli-win32-x64': 1.9.4 1209 | 1210 | '@biomejs/cli-darwin-arm64@1.9.4': 1211 | optional: true 1212 | 1213 | '@biomejs/cli-darwin-x64@1.9.4': 1214 | optional: true 1215 | 1216 | '@biomejs/cli-linux-arm64-musl@1.9.4': 1217 | optional: true 1218 | 1219 | '@biomejs/cli-linux-arm64@1.9.4': 1220 | optional: true 1221 | 1222 | '@biomejs/cli-linux-x64-musl@1.9.4': 1223 | optional: true 1224 | 1225 | '@biomejs/cli-linux-x64@1.9.4': 1226 | optional: true 1227 | 1228 | '@biomejs/cli-win32-arm64@1.9.4': 1229 | optional: true 1230 | 1231 | '@biomejs/cli-win32-x64@1.9.4': 1232 | optional: true 1233 | 1234 | '@esbuild/aix-ppc64@0.25.4': 1235 | optional: true 1236 | 1237 | '@esbuild/android-arm64@0.25.4': 1238 | optional: true 1239 | 1240 | '@esbuild/android-arm@0.25.4': 1241 | optional: true 1242 | 1243 | '@esbuild/android-x64@0.25.4': 1244 | optional: true 1245 | 1246 | '@esbuild/darwin-arm64@0.25.4': 1247 | optional: true 1248 | 1249 | '@esbuild/darwin-x64@0.25.4': 1250 | optional: true 1251 | 1252 | '@esbuild/freebsd-arm64@0.25.4': 1253 | optional: true 1254 | 1255 | '@esbuild/freebsd-x64@0.25.4': 1256 | optional: true 1257 | 1258 | '@esbuild/linux-arm64@0.25.4': 1259 | optional: true 1260 | 1261 | '@esbuild/linux-arm@0.25.4': 1262 | optional: true 1263 | 1264 | '@esbuild/linux-ia32@0.25.4': 1265 | optional: true 1266 | 1267 | '@esbuild/linux-loong64@0.25.4': 1268 | optional: true 1269 | 1270 | '@esbuild/linux-mips64el@0.25.4': 1271 | optional: true 1272 | 1273 | '@esbuild/linux-ppc64@0.25.4': 1274 | optional: true 1275 | 1276 | '@esbuild/linux-riscv64@0.25.4': 1277 | optional: true 1278 | 1279 | '@esbuild/linux-s390x@0.25.4': 1280 | optional: true 1281 | 1282 | '@esbuild/linux-x64@0.25.4': 1283 | optional: true 1284 | 1285 | '@esbuild/netbsd-arm64@0.25.4': 1286 | optional: true 1287 | 1288 | '@esbuild/netbsd-x64@0.25.4': 1289 | optional: true 1290 | 1291 | '@esbuild/openbsd-arm64@0.25.4': 1292 | optional: true 1293 | 1294 | '@esbuild/openbsd-x64@0.25.4': 1295 | optional: true 1296 | 1297 | '@esbuild/sunos-x64@0.25.4': 1298 | optional: true 1299 | 1300 | '@esbuild/win32-arm64@0.25.4': 1301 | optional: true 1302 | 1303 | '@esbuild/win32-ia32@0.25.4': 1304 | optional: true 1305 | 1306 | '@esbuild/win32-x64@0.25.4': 1307 | optional: true 1308 | 1309 | '@fleet-sdk/common@0.8.5': {} 1310 | 1311 | '@fleet-sdk/core@0.8.5': 1312 | dependencies: 1313 | '@fleet-sdk/common': 0.8.5 1314 | '@fleet-sdk/crypto': 0.8.5 1315 | '@fleet-sdk/serializer': 0.8.5 1316 | 1317 | '@fleet-sdk/crypto@0.8.5': 1318 | dependencies: 1319 | '@fleet-sdk/common': 0.8.5 1320 | '@noble/hashes': 1.8.0 1321 | '@scure/base': 1.2.5 1322 | 1323 | '@fleet-sdk/serializer@0.8.5': 1324 | dependencies: 1325 | '@fleet-sdk/common': 0.8.5 1326 | '@fleet-sdk/crypto': 0.8.5 1327 | 1328 | '@isaacs/cliui@8.0.2': 1329 | dependencies: 1330 | string-width: 5.1.2 1331 | string-width-cjs: string-width@4.2.3 1332 | strip-ansi: 7.1.0 1333 | strip-ansi-cjs: strip-ansi@6.0.1 1334 | wrap-ansi: 8.1.0 1335 | wrap-ansi-cjs: wrap-ansi@7.0.0 1336 | 1337 | '@istanbuljs/schema@0.1.3': {} 1338 | 1339 | '@jridgewell/gen-mapping@0.3.8': 1340 | dependencies: 1341 | '@jridgewell/set-array': 1.2.1 1342 | '@jridgewell/sourcemap-codec': 1.5.0 1343 | '@jridgewell/trace-mapping': 0.3.25 1344 | 1345 | '@jridgewell/resolve-uri@3.1.2': {} 1346 | 1347 | '@jridgewell/set-array@1.2.1': {} 1348 | 1349 | '@jridgewell/sourcemap-codec@1.5.0': {} 1350 | 1351 | '@jridgewell/trace-mapping@0.3.25': 1352 | dependencies: 1353 | '@jridgewell/resolve-uri': 3.1.2 1354 | '@jridgewell/sourcemap-codec': 1.5.0 1355 | 1356 | '@ledgerhq/devices@8.4.4': 1357 | dependencies: 1358 | '@ledgerhq/errors': 6.19.1 1359 | '@ledgerhq/logs': 6.12.0 1360 | rxjs: 7.8.2 1361 | semver: 7.7.2 1362 | 1363 | '@ledgerhq/errors@6.19.1': {} 1364 | 1365 | '@ledgerhq/hw-transport-mocker@6.29.4': 1366 | dependencies: 1367 | '@ledgerhq/hw-transport': 6.31.4 1368 | '@ledgerhq/logs': 6.12.0 1369 | rxjs: 7.8.2 1370 | 1371 | '@ledgerhq/hw-transport@6.31.4': 1372 | dependencies: 1373 | '@ledgerhq/devices': 8.4.4 1374 | '@ledgerhq/errors': 6.19.1 1375 | '@ledgerhq/logs': 6.12.0 1376 | events: 3.3.0 1377 | 1378 | '@ledgerhq/logs@6.12.0': {} 1379 | 1380 | '@noble/hashes@1.8.0': {} 1381 | 1382 | '@pkgjs/parseargs@0.11.0': 1383 | optional: true 1384 | 1385 | '@rollup/rollup-android-arm-eabi@4.41.0': 1386 | optional: true 1387 | 1388 | '@rollup/rollup-android-arm64@4.41.0': 1389 | optional: true 1390 | 1391 | '@rollup/rollup-darwin-arm64@4.41.0': 1392 | optional: true 1393 | 1394 | '@rollup/rollup-darwin-x64@4.41.0': 1395 | optional: true 1396 | 1397 | '@rollup/rollup-freebsd-arm64@4.41.0': 1398 | optional: true 1399 | 1400 | '@rollup/rollup-freebsd-x64@4.41.0': 1401 | optional: true 1402 | 1403 | '@rollup/rollup-linux-arm-gnueabihf@4.41.0': 1404 | optional: true 1405 | 1406 | '@rollup/rollup-linux-arm-musleabihf@4.41.0': 1407 | optional: true 1408 | 1409 | '@rollup/rollup-linux-arm64-gnu@4.41.0': 1410 | optional: true 1411 | 1412 | '@rollup/rollup-linux-arm64-musl@4.41.0': 1413 | optional: true 1414 | 1415 | '@rollup/rollup-linux-loongarch64-gnu@4.41.0': 1416 | optional: true 1417 | 1418 | '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': 1419 | optional: true 1420 | 1421 | '@rollup/rollup-linux-riscv64-gnu@4.41.0': 1422 | optional: true 1423 | 1424 | '@rollup/rollup-linux-riscv64-musl@4.41.0': 1425 | optional: true 1426 | 1427 | '@rollup/rollup-linux-s390x-gnu@4.41.0': 1428 | optional: true 1429 | 1430 | '@rollup/rollup-linux-x64-gnu@4.41.0': 1431 | optional: true 1432 | 1433 | '@rollup/rollup-linux-x64-musl@4.41.0': 1434 | optional: true 1435 | 1436 | '@rollup/rollup-win32-arm64-msvc@4.41.0': 1437 | optional: true 1438 | 1439 | '@rollup/rollup-win32-ia32-msvc@4.41.0': 1440 | optional: true 1441 | 1442 | '@rollup/rollup-win32-x64-msvc@4.41.0': 1443 | optional: true 1444 | 1445 | '@scure/base@1.2.5': {} 1446 | 1447 | '@tokenizer/token@0.3.0': {} 1448 | 1449 | '@types/estree@1.0.7': {} 1450 | 1451 | '@types/node@22.15.21': 1452 | dependencies: 1453 | undici-types: 6.21.0 1454 | 1455 | '@vitest/coverage-v8@3.1.4(vitest@3.1.4(@types/node@22.15.21)(yaml@2.4.5))': 1456 | dependencies: 1457 | '@ampproject/remapping': 2.3.0 1458 | '@bcoe/v8-coverage': 1.0.2 1459 | debug: 4.4.1 1460 | istanbul-lib-coverage: 3.2.2 1461 | istanbul-lib-report: 3.0.1 1462 | istanbul-lib-source-maps: 5.0.6 1463 | istanbul-reports: 3.1.7 1464 | magic-string: 0.30.17 1465 | magicast: 0.3.5 1466 | std-env: 3.9.0 1467 | test-exclude: 7.0.1 1468 | tinyrainbow: 2.0.0 1469 | vitest: 3.1.4(@types/node@22.15.21)(yaml@2.4.5) 1470 | transitivePeerDependencies: 1471 | - supports-color 1472 | 1473 | '@vitest/expect@3.1.4': 1474 | dependencies: 1475 | '@vitest/spy': 3.1.4 1476 | '@vitest/utils': 3.1.4 1477 | chai: 5.2.0 1478 | tinyrainbow: 2.0.0 1479 | 1480 | '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@22.15.21)(yaml@2.4.5))': 1481 | dependencies: 1482 | '@vitest/spy': 3.1.4 1483 | estree-walker: 3.0.3 1484 | magic-string: 0.30.17 1485 | optionalDependencies: 1486 | vite: 6.3.5(@types/node@22.15.21)(yaml@2.4.5) 1487 | 1488 | '@vitest/pretty-format@3.1.4': 1489 | dependencies: 1490 | tinyrainbow: 2.0.0 1491 | 1492 | '@vitest/runner@3.1.4': 1493 | dependencies: 1494 | '@vitest/utils': 3.1.4 1495 | pathe: 2.0.3 1496 | 1497 | '@vitest/snapshot@3.1.4': 1498 | dependencies: 1499 | '@vitest/pretty-format': 3.1.4 1500 | magic-string: 0.30.17 1501 | pathe: 2.0.3 1502 | 1503 | '@vitest/spy@3.1.4': 1504 | dependencies: 1505 | tinyspy: 3.0.2 1506 | 1507 | '@vitest/utils@3.1.4': 1508 | dependencies: 1509 | '@vitest/pretty-format': 3.1.4 1510 | loupe: 3.1.3 1511 | tinyrainbow: 2.0.0 1512 | 1513 | abort-controller@3.0.0: 1514 | dependencies: 1515 | event-target-shim: 5.0.1 1516 | 1517 | acorn@8.14.1: {} 1518 | 1519 | ansi-regex@5.0.1: {} 1520 | 1521 | ansi-regex@6.1.0: {} 1522 | 1523 | ansi-styles@4.3.0: 1524 | dependencies: 1525 | color-convert: 2.0.1 1526 | 1527 | ansi-styles@6.2.1: {} 1528 | 1529 | any-promise@1.3.0: {} 1530 | 1531 | assertion-error@2.0.1: {} 1532 | 1533 | balanced-match@1.0.2: {} 1534 | 1535 | base64-js@1.5.1: {} 1536 | 1537 | bip32-path@0.4.2: {} 1538 | 1539 | brace-expansion@2.0.1: 1540 | dependencies: 1541 | balanced-match: 1.0.2 1542 | 1543 | buffer@6.0.3: 1544 | dependencies: 1545 | base64-js: 1.5.1 1546 | ieee754: 1.2.1 1547 | 1548 | bundle-name@4.1.0: 1549 | dependencies: 1550 | run-applescript: 7.0.0 1551 | 1552 | bundle-require@5.1.0(esbuild@0.25.4): 1553 | dependencies: 1554 | esbuild: 0.25.4 1555 | load-tsconfig: 0.2.5 1556 | 1557 | cac@6.7.14: {} 1558 | 1559 | chai@5.2.0: 1560 | dependencies: 1561 | assertion-error: 2.0.1 1562 | check-error: 2.1.1 1563 | deep-eql: 5.0.2 1564 | loupe: 3.1.3 1565 | pathval: 2.0.0 1566 | 1567 | check-error@2.1.1: {} 1568 | 1569 | chokidar@4.0.3: 1570 | dependencies: 1571 | readdirp: 4.1.2 1572 | 1573 | color-convert@2.0.1: 1574 | dependencies: 1575 | color-name: 1.1.4 1576 | 1577 | color-name@1.1.4: {} 1578 | 1579 | commander@4.1.1: {} 1580 | 1581 | confbox@0.1.8: {} 1582 | 1583 | consola@3.4.2: {} 1584 | 1585 | cross-spawn@7.0.6: 1586 | dependencies: 1587 | path-key: 3.1.1 1588 | shebang-command: 2.0.0 1589 | which: 2.0.2 1590 | 1591 | crypto-random-string@4.0.0: 1592 | dependencies: 1593 | type-fest: 1.4.0 1594 | 1595 | debug@4.4.1: 1596 | dependencies: 1597 | ms: 2.1.3 1598 | 1599 | deep-eql@5.0.2: {} 1600 | 1601 | default-browser-id@5.0.0: {} 1602 | 1603 | default-browser@5.2.1: 1604 | dependencies: 1605 | bundle-name: 4.1.0 1606 | default-browser-id: 5.0.0 1607 | 1608 | define-lazy-prop@3.0.0: {} 1609 | 1610 | eastasianwidth@0.2.0: {} 1611 | 1612 | emoji-regex@8.0.0: {} 1613 | 1614 | emoji-regex@9.2.2: {} 1615 | 1616 | es-module-lexer@1.7.0: {} 1617 | 1618 | esbuild@0.25.4: 1619 | optionalDependencies: 1620 | '@esbuild/aix-ppc64': 0.25.4 1621 | '@esbuild/android-arm': 0.25.4 1622 | '@esbuild/android-arm64': 0.25.4 1623 | '@esbuild/android-x64': 0.25.4 1624 | '@esbuild/darwin-arm64': 0.25.4 1625 | '@esbuild/darwin-x64': 0.25.4 1626 | '@esbuild/freebsd-arm64': 0.25.4 1627 | '@esbuild/freebsd-x64': 0.25.4 1628 | '@esbuild/linux-arm': 0.25.4 1629 | '@esbuild/linux-arm64': 0.25.4 1630 | '@esbuild/linux-ia32': 0.25.4 1631 | '@esbuild/linux-loong64': 0.25.4 1632 | '@esbuild/linux-mips64el': 0.25.4 1633 | '@esbuild/linux-ppc64': 0.25.4 1634 | '@esbuild/linux-riscv64': 0.25.4 1635 | '@esbuild/linux-s390x': 0.25.4 1636 | '@esbuild/linux-x64': 0.25.4 1637 | '@esbuild/netbsd-arm64': 0.25.4 1638 | '@esbuild/netbsd-x64': 0.25.4 1639 | '@esbuild/openbsd-arm64': 0.25.4 1640 | '@esbuild/openbsd-x64': 0.25.4 1641 | '@esbuild/sunos-x64': 0.25.4 1642 | '@esbuild/win32-arm64': 0.25.4 1643 | '@esbuild/win32-ia32': 0.25.4 1644 | '@esbuild/win32-x64': 0.25.4 1645 | 1646 | estree-walker@3.0.3: 1647 | dependencies: 1648 | '@types/estree': 1.0.7 1649 | 1650 | event-target-shim@5.0.1: {} 1651 | 1652 | events@3.3.0: {} 1653 | 1654 | expect-type@1.2.1: {} 1655 | 1656 | fdir@6.4.4(picomatch@4.0.2): 1657 | optionalDependencies: 1658 | picomatch: 4.0.2 1659 | 1660 | file-type@18.7.0: 1661 | dependencies: 1662 | readable-web-to-node-stream: 3.0.4 1663 | strtok3: 7.1.1 1664 | token-types: 5.0.1 1665 | 1666 | fix-dts-default-cjs-exports@1.0.1: 1667 | dependencies: 1668 | magic-string: 0.30.17 1669 | mlly: 1.7.4 1670 | rollup: 4.41.0 1671 | 1672 | foreground-child@3.3.1: 1673 | dependencies: 1674 | cross-spawn: 7.0.6 1675 | signal-exit: 4.1.0 1676 | 1677 | fsevents@2.3.3: 1678 | optional: true 1679 | 1680 | get-stdin@9.0.0: {} 1681 | 1682 | glob@10.4.5: 1683 | dependencies: 1684 | foreground-child: 3.3.1 1685 | jackspeak: 3.4.3 1686 | minimatch: 9.0.5 1687 | minipass: 7.1.2 1688 | package-json-from-dist: 1.0.1 1689 | path-scurry: 1.11.1 1690 | 1691 | has-flag@4.0.0: {} 1692 | 1693 | html-escaper@2.0.2: {} 1694 | 1695 | ieee754@1.2.1: {} 1696 | 1697 | is-docker@3.0.0: {} 1698 | 1699 | is-fullwidth-code-point@3.0.0: {} 1700 | 1701 | is-inside-container@1.0.0: 1702 | dependencies: 1703 | is-docker: 3.0.0 1704 | 1705 | is-stream@3.0.0: {} 1706 | 1707 | is-wsl@3.1.0: 1708 | dependencies: 1709 | is-inside-container: 1.0.0 1710 | 1711 | isexe@2.0.0: {} 1712 | 1713 | istanbul-lib-coverage@3.2.2: {} 1714 | 1715 | istanbul-lib-report@3.0.1: 1716 | dependencies: 1717 | istanbul-lib-coverage: 3.2.2 1718 | make-dir: 4.0.0 1719 | supports-color: 7.2.0 1720 | 1721 | istanbul-lib-source-maps@5.0.6: 1722 | dependencies: 1723 | '@jridgewell/trace-mapping': 0.3.25 1724 | debug: 4.4.1 1725 | istanbul-lib-coverage: 3.2.2 1726 | transitivePeerDependencies: 1727 | - supports-color 1728 | 1729 | istanbul-reports@3.1.7: 1730 | dependencies: 1731 | html-escaper: 2.0.2 1732 | istanbul-lib-report: 3.0.1 1733 | 1734 | jackspeak@3.4.3: 1735 | dependencies: 1736 | '@isaacs/cliui': 8.0.2 1737 | optionalDependencies: 1738 | '@pkgjs/parseargs': 0.11.0 1739 | 1740 | joycon@3.1.1: {} 1741 | 1742 | lilconfig@3.1.3: {} 1743 | 1744 | lines-and-columns@1.2.4: {} 1745 | 1746 | load-tsconfig@0.2.5: {} 1747 | 1748 | lodash.sortby@4.7.0: {} 1749 | 1750 | loupe@3.1.3: {} 1751 | 1752 | lru-cache@10.4.3: {} 1753 | 1754 | magic-string@0.30.17: 1755 | dependencies: 1756 | '@jridgewell/sourcemap-codec': 1.5.0 1757 | 1758 | magicast@0.3.5: 1759 | dependencies: 1760 | '@babel/parser': 7.27.2 1761 | '@babel/types': 7.27.1 1762 | source-map-js: 1.2.1 1763 | 1764 | make-dir@4.0.0: 1765 | dependencies: 1766 | semver: 7.7.2 1767 | 1768 | meow@12.1.1: {} 1769 | 1770 | minimatch@9.0.5: 1771 | dependencies: 1772 | brace-expansion: 2.0.1 1773 | 1774 | minipass@7.1.2: {} 1775 | 1776 | mlly@1.7.4: 1777 | dependencies: 1778 | acorn: 8.14.1 1779 | pathe: 2.0.3 1780 | pkg-types: 1.3.1 1781 | ufo: 1.6.1 1782 | 1783 | ms@2.1.3: {} 1784 | 1785 | mz@2.7.0: 1786 | dependencies: 1787 | any-promise: 1.3.0 1788 | object-assign: 4.1.1 1789 | thenify-all: 1.6.0 1790 | 1791 | nanoid@3.3.11: {} 1792 | 1793 | object-assign@4.1.1: {} 1794 | 1795 | open-cli@8.0.0: 1796 | dependencies: 1797 | file-type: 18.7.0 1798 | get-stdin: 9.0.0 1799 | meow: 12.1.1 1800 | open: 10.1.2 1801 | tempy: 3.1.0 1802 | 1803 | open@10.1.2: 1804 | dependencies: 1805 | default-browser: 5.2.1 1806 | define-lazy-prop: 3.0.0 1807 | is-inside-container: 1.0.0 1808 | is-wsl: 3.1.0 1809 | 1810 | package-json-from-dist@1.0.1: {} 1811 | 1812 | path-key@3.1.1: {} 1813 | 1814 | path-scurry@1.11.1: 1815 | dependencies: 1816 | lru-cache: 10.4.3 1817 | minipass: 7.1.2 1818 | 1819 | pathe@2.0.3: {} 1820 | 1821 | pathval@2.0.0: {} 1822 | 1823 | peek-readable@5.4.2: {} 1824 | 1825 | picocolors@1.1.1: {} 1826 | 1827 | picomatch@4.0.2: {} 1828 | 1829 | pirates@4.0.7: {} 1830 | 1831 | pkg-types@1.3.1: 1832 | dependencies: 1833 | confbox: 0.1.8 1834 | mlly: 1.7.4 1835 | pathe: 2.0.3 1836 | 1837 | postcss-load-config@6.0.1(postcss@8.5.3)(yaml@2.4.5): 1838 | dependencies: 1839 | lilconfig: 3.1.3 1840 | optionalDependencies: 1841 | postcss: 8.5.3 1842 | yaml: 2.4.5 1843 | 1844 | postcss@8.5.3: 1845 | dependencies: 1846 | nanoid: 3.3.11 1847 | picocolors: 1.1.1 1848 | source-map-js: 1.2.1 1849 | 1850 | process@0.11.10: {} 1851 | 1852 | punycode@2.3.1: {} 1853 | 1854 | readable-stream@4.7.0: 1855 | dependencies: 1856 | abort-controller: 3.0.0 1857 | buffer: 6.0.3 1858 | events: 3.3.0 1859 | process: 0.11.10 1860 | string_decoder: 1.3.0 1861 | 1862 | readable-web-to-node-stream@3.0.4: 1863 | dependencies: 1864 | readable-stream: 4.7.0 1865 | 1866 | readdirp@4.1.2: {} 1867 | 1868 | resolve-from@5.0.0: {} 1869 | 1870 | rollup@4.41.0: 1871 | dependencies: 1872 | '@types/estree': 1.0.7 1873 | optionalDependencies: 1874 | '@rollup/rollup-android-arm-eabi': 4.41.0 1875 | '@rollup/rollup-android-arm64': 4.41.0 1876 | '@rollup/rollup-darwin-arm64': 4.41.0 1877 | '@rollup/rollup-darwin-x64': 4.41.0 1878 | '@rollup/rollup-freebsd-arm64': 4.41.0 1879 | '@rollup/rollup-freebsd-x64': 4.41.0 1880 | '@rollup/rollup-linux-arm-gnueabihf': 4.41.0 1881 | '@rollup/rollup-linux-arm-musleabihf': 4.41.0 1882 | '@rollup/rollup-linux-arm64-gnu': 4.41.0 1883 | '@rollup/rollup-linux-arm64-musl': 4.41.0 1884 | '@rollup/rollup-linux-loongarch64-gnu': 4.41.0 1885 | '@rollup/rollup-linux-powerpc64le-gnu': 4.41.0 1886 | '@rollup/rollup-linux-riscv64-gnu': 4.41.0 1887 | '@rollup/rollup-linux-riscv64-musl': 4.41.0 1888 | '@rollup/rollup-linux-s390x-gnu': 4.41.0 1889 | '@rollup/rollup-linux-x64-gnu': 4.41.0 1890 | '@rollup/rollup-linux-x64-musl': 4.41.0 1891 | '@rollup/rollup-win32-arm64-msvc': 4.41.0 1892 | '@rollup/rollup-win32-ia32-msvc': 4.41.0 1893 | '@rollup/rollup-win32-x64-msvc': 4.41.0 1894 | fsevents: 2.3.3 1895 | 1896 | run-applescript@7.0.0: {} 1897 | 1898 | rxjs@7.8.2: 1899 | dependencies: 1900 | tslib: 2.8.1 1901 | 1902 | safe-buffer@5.2.1: {} 1903 | 1904 | semver@7.7.2: {} 1905 | 1906 | shebang-command@2.0.0: 1907 | dependencies: 1908 | shebang-regex: 3.0.0 1909 | 1910 | shebang-regex@3.0.0: {} 1911 | 1912 | siginfo@2.0.0: {} 1913 | 1914 | signal-exit@4.1.0: {} 1915 | 1916 | source-map-js@1.2.1: {} 1917 | 1918 | source-map@0.8.0-beta.0: 1919 | dependencies: 1920 | whatwg-url: 7.1.0 1921 | 1922 | stackback@0.0.2: {} 1923 | 1924 | std-env@3.9.0: {} 1925 | 1926 | string-width@4.2.3: 1927 | dependencies: 1928 | emoji-regex: 8.0.0 1929 | is-fullwidth-code-point: 3.0.0 1930 | strip-ansi: 6.0.1 1931 | 1932 | string-width@5.1.2: 1933 | dependencies: 1934 | eastasianwidth: 0.2.0 1935 | emoji-regex: 9.2.2 1936 | strip-ansi: 7.1.0 1937 | 1938 | string_decoder@1.3.0: 1939 | dependencies: 1940 | safe-buffer: 5.2.1 1941 | 1942 | strip-ansi@6.0.1: 1943 | dependencies: 1944 | ansi-regex: 5.0.1 1945 | 1946 | strip-ansi@7.1.0: 1947 | dependencies: 1948 | ansi-regex: 6.1.0 1949 | 1950 | strtok3@7.1.1: 1951 | dependencies: 1952 | '@tokenizer/token': 0.3.0 1953 | peek-readable: 5.4.2 1954 | 1955 | sucrase@3.35.0: 1956 | dependencies: 1957 | '@jridgewell/gen-mapping': 0.3.8 1958 | commander: 4.1.1 1959 | glob: 10.4.5 1960 | lines-and-columns: 1.2.4 1961 | mz: 2.7.0 1962 | pirates: 4.0.7 1963 | ts-interface-checker: 0.1.13 1964 | 1965 | supports-color@7.2.0: 1966 | dependencies: 1967 | has-flag: 4.0.0 1968 | 1969 | temp-dir@3.0.0: {} 1970 | 1971 | tempy@3.1.0: 1972 | dependencies: 1973 | is-stream: 3.0.0 1974 | temp-dir: 3.0.0 1975 | type-fest: 2.19.0 1976 | unique-string: 3.0.0 1977 | 1978 | test-exclude@7.0.1: 1979 | dependencies: 1980 | '@istanbuljs/schema': 0.1.3 1981 | glob: 10.4.5 1982 | minimatch: 9.0.5 1983 | 1984 | thenify-all@1.6.0: 1985 | dependencies: 1986 | thenify: 3.3.1 1987 | 1988 | thenify@3.3.1: 1989 | dependencies: 1990 | any-promise: 1.3.0 1991 | 1992 | tinybench@2.9.0: {} 1993 | 1994 | tinyexec@0.3.2: {} 1995 | 1996 | tinyglobby@0.2.13: 1997 | dependencies: 1998 | fdir: 6.4.4(picomatch@4.0.2) 1999 | picomatch: 4.0.2 2000 | 2001 | tinypool@1.0.2: {} 2002 | 2003 | tinyrainbow@2.0.0: {} 2004 | 2005 | tinyspy@3.0.2: {} 2006 | 2007 | token-types@5.0.1: 2008 | dependencies: 2009 | '@tokenizer/token': 0.3.0 2010 | ieee754: 1.2.1 2011 | 2012 | tr46@1.0.1: 2013 | dependencies: 2014 | punycode: 2.3.1 2015 | 2016 | tree-kill@1.2.2: {} 2017 | 2018 | ts-interface-checker@0.1.13: {} 2019 | 2020 | tslib@2.8.1: {} 2021 | 2022 | tsup@8.5.0(postcss@8.5.3)(typescript@5.8.3)(yaml@2.4.5): 2023 | dependencies: 2024 | bundle-require: 5.1.0(esbuild@0.25.4) 2025 | cac: 6.7.14 2026 | chokidar: 4.0.3 2027 | consola: 3.4.2 2028 | debug: 4.4.1 2029 | esbuild: 0.25.4 2030 | fix-dts-default-cjs-exports: 1.0.1 2031 | joycon: 3.1.1 2032 | picocolors: 1.1.1 2033 | postcss-load-config: 6.0.1(postcss@8.5.3)(yaml@2.4.5) 2034 | resolve-from: 5.0.0 2035 | rollup: 4.41.0 2036 | source-map: 0.8.0-beta.0 2037 | sucrase: 3.35.0 2038 | tinyexec: 0.3.2 2039 | tinyglobby: 0.2.13 2040 | tree-kill: 1.2.2 2041 | optionalDependencies: 2042 | postcss: 8.5.3 2043 | typescript: 5.8.3 2044 | transitivePeerDependencies: 2045 | - jiti 2046 | - supports-color 2047 | - tsx 2048 | - yaml 2049 | 2050 | type-fest@1.4.0: {} 2051 | 2052 | type-fest@2.19.0: {} 2053 | 2054 | typescript@5.8.3: {} 2055 | 2056 | ufo@1.6.1: {} 2057 | 2058 | undici-types@6.21.0: {} 2059 | 2060 | unique-string@3.0.0: 2061 | dependencies: 2062 | crypto-random-string: 4.0.0 2063 | 2064 | vite-node@3.1.4(@types/node@22.15.21)(yaml@2.4.5): 2065 | dependencies: 2066 | cac: 6.7.14 2067 | debug: 4.4.1 2068 | es-module-lexer: 1.7.0 2069 | pathe: 2.0.3 2070 | vite: 6.3.5(@types/node@22.15.21)(yaml@2.4.5) 2071 | transitivePeerDependencies: 2072 | - '@types/node' 2073 | - jiti 2074 | - less 2075 | - lightningcss 2076 | - sass 2077 | - sass-embedded 2078 | - stylus 2079 | - sugarss 2080 | - supports-color 2081 | - terser 2082 | - tsx 2083 | - yaml 2084 | 2085 | vite@6.3.5(@types/node@22.15.21)(yaml@2.4.5): 2086 | dependencies: 2087 | esbuild: 0.25.4 2088 | fdir: 6.4.4(picomatch@4.0.2) 2089 | picomatch: 4.0.2 2090 | postcss: 8.5.3 2091 | rollup: 4.41.0 2092 | tinyglobby: 0.2.13 2093 | optionalDependencies: 2094 | '@types/node': 22.15.21 2095 | fsevents: 2.3.3 2096 | yaml: 2.4.5 2097 | 2098 | vitest@3.1.4(@types/node@22.15.21)(yaml@2.4.5): 2099 | dependencies: 2100 | '@vitest/expect': 3.1.4 2101 | '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@22.15.21)(yaml@2.4.5)) 2102 | '@vitest/pretty-format': 3.1.4 2103 | '@vitest/runner': 3.1.4 2104 | '@vitest/snapshot': 3.1.4 2105 | '@vitest/spy': 3.1.4 2106 | '@vitest/utils': 3.1.4 2107 | chai: 5.2.0 2108 | debug: 4.4.1 2109 | expect-type: 1.2.1 2110 | magic-string: 0.30.17 2111 | pathe: 2.0.3 2112 | std-env: 3.9.0 2113 | tinybench: 2.9.0 2114 | tinyexec: 0.3.2 2115 | tinyglobby: 0.2.13 2116 | tinypool: 1.0.2 2117 | tinyrainbow: 2.0.0 2118 | vite: 6.3.5(@types/node@22.15.21)(yaml@2.4.5) 2119 | vite-node: 3.1.4(@types/node@22.15.21)(yaml@2.4.5) 2120 | why-is-node-running: 2.3.0 2121 | optionalDependencies: 2122 | '@types/node': 22.15.21 2123 | transitivePeerDependencies: 2124 | - jiti 2125 | - less 2126 | - lightningcss 2127 | - msw 2128 | - sass 2129 | - sass-embedded 2130 | - stylus 2131 | - sugarss 2132 | - supports-color 2133 | - terser 2134 | - tsx 2135 | - yaml 2136 | 2137 | webidl-conversions@4.0.2: {} 2138 | 2139 | whatwg-url@7.1.0: 2140 | dependencies: 2141 | lodash.sortby: 4.7.0 2142 | tr46: 1.0.1 2143 | webidl-conversions: 4.0.2 2144 | 2145 | which@2.0.2: 2146 | dependencies: 2147 | isexe: 2.0.0 2148 | 2149 | why-is-node-running@2.3.0: 2150 | dependencies: 2151 | siginfo: 2.0.0 2152 | stackback: 0.0.2 2153 | 2154 | wrap-ansi@7.0.0: 2155 | dependencies: 2156 | ansi-styles: 4.3.0 2157 | string-width: 4.2.3 2158 | strip-ansi: 6.0.1 2159 | 2160 | wrap-ansi@8.1.0: 2161 | dependencies: 2162 | ansi-styles: 6.2.1 2163 | string-width: 5.1.2 2164 | strip-ansi: 7.1.0 2165 | 2166 | yaml@2.4.5: 2167 | optional: true 2168 | -------------------------------------------------------------------------------- /src/commands/app/attestInput.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { decodeAttestedFrameResponse } from "./attestInput"; 3 | 4 | describe("attestInput test", () => { 5 | const frameHex = 6 | "7cbe85a5f2d2154538eb883bbbee10dd414ec24b6e52b43495da906bce2c5e8a010000000000ab6a1fde032d554219a80c011cc51509e34fa4950965bb8e01de4d012536e766c9ca08bc2c000000174876e7febcd5db3a2872f279ef89edaa51a9344a6095ea1f03396874b695b5ba95ff602e00000017483412969f90c012e03bf99397e363fb1571b7999941e0862a217307e3467ee80cf53af700000000000000012f5151af1796a5827de6df5339ddca7a"; 7 | 8 | it("should parse frame response", () => { 9 | const parsedFrame = decodeAttestedFrameResponse(Buffer.from(frameHex, "hex")); 10 | 11 | expect(parsedFrame).toMatchObject({ 12 | boxId: "7cbe85a5f2d2154538eb883bbbee10dd414ec24b6e52b43495da906bce2c5e8a", 13 | count: 1, 14 | index: 0, 15 | amount: "2875858910", 16 | tokens: [ 17 | { 18 | id: "2d554219a80c011cc51509e34fa4950965bb8e01de4d012536e766c9ca08bc2c", 19 | amount: "99999999998" 20 | }, 21 | { 22 | id: "bcd5db3a2872f279ef89edaa51a9344a6095ea1f03396874b695b5ba95ff602e", 23 | amount: "99995619990" 24 | }, 25 | { 26 | id: "9f90c012e03bf99397e363fb1571b7999941e0862a217307e3467ee80cf53af7", 27 | amount: "1" 28 | } 29 | ], 30 | attestation: "2f5151af1796a5827de6df5339ddca7a" 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/commands/app/attestInput.ts: -------------------------------------------------------------------------------- 1 | import { chunk } from "@fleet-sdk/common"; 2 | import { hex } from "@fleet-sdk/crypto"; 3 | import { COMMAND, type Device, MAX_DATA_LENGTH } from "../../device"; 4 | import { ByteWriter } from "../../serialization/byteWriter"; 5 | import { AttestedBox } from "../../types/attestedBox"; 6 | import type { DeviceResponse } from "../../types/internal"; 7 | import type { AttestedBoxFrame, Token, UnsignedBox } from "../../types/public"; 8 | 9 | const enum P1 { 10 | BOX_START = 0x01, 11 | ADD_ERGO_TREE_CHUNK = 0x02, 12 | ADD_TOKENS = 0x03, 13 | ADD_REGISTERS_CHUNK = 0x04, 14 | GET_ATTESTED_BOX_FRAME = 0x05 15 | } 16 | 17 | const enum P2 { 18 | WITHOUT_TOKEN = 0x01, 19 | WITH_TOKEN = 0x02 20 | } 21 | 22 | const CLA = 0xe0; 23 | const MAX_HEADER_SIZE = 59; // https://github.com/tesseract-one/ledger-app-ergo/blob/main/doc/INS-20-ATTEST-BOX.md#data 24 | const TOKEN_ENTRY_SIZE = 40; // https://github.com/tesseract-one/ledger-app-ergo/blob/main/doc/INS-20-ATTEST-BOX.md#data-2 25 | 26 | export async function attestInput( 27 | device: Device, 28 | box: UnsignedBox, 29 | authToken?: number 30 | ): Promise { 31 | const sessionId = await sendHeader(device, box, authToken); 32 | let frameCount = await sendErgoTree(device, box.ergoTree, sessionId); 33 | if (box.tokens.length > 0) { 34 | frameCount = await sendTokens(device, box.tokens, sessionId); 35 | } 36 | if (box.additionalRegisters.length > 0) { 37 | frameCount = await sendRegisters(device, box.additionalRegisters, sessionId); 38 | } 39 | 40 | return new AttestedBox(box, await getAttestedFrames(device, frameCount, sessionId)); 41 | } 42 | 43 | async function sendHeader( 44 | device: Device, 45 | box: UnsignedBox, 46 | authToken?: number 47 | ): Promise { 48 | const header = new ByteWriter(MAX_HEADER_SIZE) 49 | .writeHex(box.txId) 50 | .writeUInt16(box.index) 51 | .writeUInt64(box.value) 52 | .writeUInt32(box.ergoTree.length) 53 | .writeUInt32(box.creationHeight) 54 | .writeUInt8(box.tokens.length) 55 | .writeUInt32(box.additionalRegisters.length) 56 | .writeAuthToken(authToken) 57 | .toBytes(); 58 | 59 | const response = await device.send( 60 | CLA, 61 | COMMAND.ATTEST_INPUT, 62 | P1.BOX_START, 63 | authToken ? P2.WITH_TOKEN : P2.WITHOUT_TOKEN, 64 | header 65 | ); 66 | 67 | return response.data[0]; 68 | } 69 | 70 | async function sendErgoTree( 71 | device: Device, 72 | data: Uint8Array, 73 | sessionId: number 74 | ): Promise { 75 | const results = await device.sendData( 76 | CLA, 77 | COMMAND.ATTEST_INPUT, 78 | P1.ADD_ERGO_TREE_CHUNK, 79 | sessionId, 80 | data 81 | ); 82 | 83 | return results.pop()?.data[0] || 0; 84 | } 85 | 86 | async function sendTokens( 87 | device: Device, 88 | tokens: Token[], 89 | sessionId: number 90 | ): Promise { 91 | const chunks = chunk(tokens, Math.floor(MAX_DATA_LENGTH / TOKEN_ENTRY_SIZE)); 92 | const results: DeviceResponse[] = []; 93 | 94 | for (const chunk of chunks) { 95 | const data = new ByteWriter(chunk.length * TOKEN_ENTRY_SIZE); 96 | for (const token of chunk) data.writeHex(token.id).writeUInt64(token.amount); 97 | 98 | results.push( 99 | await device.send( 100 | CLA, 101 | COMMAND.ATTEST_INPUT, 102 | P1.ADD_TOKENS, 103 | sessionId, 104 | data.toBytes() 105 | ) 106 | ); 107 | } 108 | 109 | /* v8 ignore next */ 110 | return results.pop()?.data[0] || 0; 111 | } 112 | 113 | async function sendRegisters( 114 | device: Device, 115 | data: Uint8Array, 116 | sessionId: number 117 | ): Promise { 118 | const results = await device.sendData( 119 | CLA, 120 | COMMAND.ATTEST_INPUT, 121 | P1.ADD_REGISTERS_CHUNK, 122 | sessionId, 123 | data 124 | ); 125 | 126 | /* v8 ignore next */ 127 | return results.pop()?.data[0] || 0; 128 | } 129 | 130 | async function getAttestedFrames( 131 | device: Device, 132 | count: number, 133 | sessionId: number 134 | ): Promise { 135 | const responses: AttestedBoxFrame[] = []; 136 | for (let i = 0; i < count; i++) { 137 | const response = await device.send( 138 | CLA, 139 | COMMAND.ATTEST_INPUT, 140 | P1.GET_ATTESTED_BOX_FRAME, 141 | sessionId, 142 | Uint8Array.from([i]) 143 | ); 144 | 145 | responses.push(decodeAttestedFrameResponse(response.data)); 146 | } 147 | 148 | return responses; 149 | } 150 | 151 | export function decodeAttestedFrameResponse(bytes: Buffer): AttestedBoxFrame { 152 | let offset = 0; 153 | const boxId = hex.encode(bytes.subarray(0, (offset += 32))); 154 | const count = bytes.readUint8(offset++); 155 | const index = bytes.readUint8(offset++); 156 | const amount = bytes.readBigUInt64BE(offset).toString(); 157 | offset += 8; 158 | const tokenCount = bytes.readUint8(offset++); 159 | const tokens: Token[] = []; 160 | for (let i = 0; i < tokenCount; i++) { 161 | tokens.push({ 162 | id: hex.encode(bytes.subarray(offset, (offset += 32))), 163 | amount: bytes.readBigUInt64BE(offset).toString() 164 | }); 165 | offset += 8; 166 | } 167 | 168 | const attestation = hex.encode(bytes.subarray(offset, offset + 16)); 169 | 170 | return { 171 | boxId, 172 | count, 173 | index, 174 | amount, 175 | tokens, 176 | attestation, 177 | bytes 178 | }; 179 | } 180 | -------------------------------------------------------------------------------- /src/commands/app/deriveAddress.ts: -------------------------------------------------------------------------------- 1 | import type { Network } from "@fleet-sdk/common"; 2 | import { hex } from "@fleet-sdk/crypto"; 3 | import { COMMAND, type Device, RETURN_CODE } from "../../device"; 4 | import { ByteWriter } from "../../serialization/byteWriter"; 5 | import { pathToArray } from "../../serialization/utils"; 6 | import type { DeviceResponse } from "../../types/internal"; 7 | import type { DerivedAddress } from "../../types/public"; 8 | 9 | const enum ReturnType { 10 | Return = 0x01, 11 | Display = 0x02 12 | } 13 | 14 | const enum P1 { 15 | RETURN = 0x01, 16 | DISPLAY = 0x02 17 | } 18 | 19 | const enum P2 { 20 | WITHOUT_TOKEN = 0x01, 21 | WITH_TOKEN = 0x02 22 | } 23 | 24 | const CLA = 0xe0; 25 | const CHANGE_PATH_INDEX = 3; 26 | const ALLOWED_CHANGE_PATHS = [0, 1]; 27 | const MAX_APDU_SIZE = 46; // https://github.com/tesseract-one/ledger-app-ergo/blob/main/doc/INS-11-DERIVE-ADDR.md#data 28 | 29 | function sendDeriveAddress( 30 | device: Device, 31 | network: Network, 32 | path: string, 33 | returnType: ReturnType, 34 | authToken?: number 35 | ): Promise { 36 | const pathArray = pathToArray(path); 37 | if (pathArray.length < 5) { 38 | throw new Error(`Invalid path length. ${pathArray.length}`); 39 | } 40 | 41 | const change = pathArray[CHANGE_PATH_INDEX]; 42 | if (!ALLOWED_CHANGE_PATHS.includes(change)) { 43 | throw new Error(`Invalid change path: ${change}`); 44 | } 45 | 46 | return device.send( 47 | CLA, 48 | COMMAND.DERIVE_ADDRESS, 49 | returnType === ReturnType.Return ? P1.RETURN : P1.DISPLAY, 50 | authToken ? P2.WITH_TOKEN : P2.WITHOUT_TOKEN, 51 | new ByteWriter(MAX_APDU_SIZE) 52 | .write(network) 53 | .writePath(path) 54 | .writeAuthToken(authToken) 55 | .toBytes() 56 | ); 57 | } 58 | 59 | export async function deriveAddress( 60 | device: Device, 61 | network: Network, 62 | path: string, 63 | authToken?: number 64 | ): Promise { 65 | const response = await sendDeriveAddress( 66 | device, 67 | network, 68 | path, 69 | ReturnType.Return, 70 | authToken 71 | ); 72 | 73 | return { addressHex: hex.encode(response.data) }; 74 | } 75 | 76 | export async function showAddress( 77 | device: Device, 78 | network: Network, 79 | path: string, 80 | authToken?: number 81 | ): Promise { 82 | const response = await sendDeriveAddress( 83 | device, 84 | network, 85 | path, 86 | ReturnType.Display, 87 | authToken 88 | ); 89 | 90 | return response.returnCode === RETURN_CODE.OK; 91 | } 92 | -------------------------------------------------------------------------------- /src/commands/app/getAppName.ts: -------------------------------------------------------------------------------- 1 | import { COMMAND, type Device } from "../../device"; 2 | import { asciiCodec, EMPTY_BYTES, NO_VALUE } from "../../serialization/utils"; 3 | import type { AppName } from "../../types/public"; 4 | 5 | const CLA = 0xe0; 6 | 7 | export async function getAppName(device: Device): Promise { 8 | const response = await device.send( 9 | CLA, 10 | COMMAND.GET_APP_NAME, 11 | NO_VALUE, 12 | NO_VALUE, 13 | EMPTY_BYTES 14 | ); 15 | return { name: asciiCodec.decode(response.data) }; 16 | } 17 | -------------------------------------------------------------------------------- /src/commands/app/getExtendedPublicKey.ts: -------------------------------------------------------------------------------- 1 | import { hex } from "@fleet-sdk/crypto"; 2 | import { COMMAND, type Device } from "../../device"; 3 | import { ByteWriter } from "../../serialization/byteWriter"; 4 | import type { ExtendedPublicKey } from "../../types/public"; 5 | import { NO_VALUE } from "../../serialization/utils"; 6 | 7 | const enum P1 { 8 | WITHOUT_TOKEN = 0x01, 9 | WITH_TOKEN = 0x02 10 | } 11 | 12 | const CLA = 0xe0; 13 | const MAX_APDU_SIZE = 45; // https://github.com/tesseract-one/ledger-app-ergo/blob/main/doc/INS-10-EXT-PUB-KEY.md#data 14 | 15 | export async function getExtendedPublicKey( 16 | device: Device, 17 | path: string, 18 | authToken?: number 19 | ): Promise { 20 | const response = await device.send( 21 | CLA, 22 | COMMAND.GET_EXTENDED_PUB_KEY, 23 | authToken ? P1.WITH_TOKEN : P1.WITHOUT_TOKEN, 24 | NO_VALUE, 25 | new ByteWriter(MAX_APDU_SIZE).writePath(path).writeAuthToken(authToken).toBytes() 26 | ); 27 | 28 | return { 29 | publicKey: hex.encode(response.data.subarray(0, 33)), 30 | chainCode: hex.encode(response.data.subarray(33, 65)) 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/app/getVersion.ts: -------------------------------------------------------------------------------- 1 | import { COMMAND, type Device } from "../../device"; 2 | import { EMPTY_BYTES, NO_VALUE } from "../../serialization/utils"; 3 | import type { Version } from "../../types/public"; 4 | 5 | const IS_DEBUG_FLAG = 0x01; 6 | const CLA = 0xe0; 7 | 8 | export async function getAppVersion(device: Device): Promise { 9 | const response = await device.send( 10 | CLA, 11 | COMMAND.GET_APP_VERSION, 12 | NO_VALUE, 13 | NO_VALUE, 14 | EMPTY_BYTES 15 | ); 16 | 17 | return { 18 | major: response.data[0], 19 | minor: response.data[1], 20 | patch: response.data[2], 21 | flags: { isDebug: response.data[3] === IS_DEBUG_FLAG } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/commands/app/index.ts: -------------------------------------------------------------------------------- 1 | export { attestInput } from "./attestInput"; 2 | export { deriveAddress, showAddress } from "./deriveAddress"; 3 | export { getAppName } from "./getAppName"; 4 | export { getExtendedPublicKey } from "./getExtendedPublicKey"; 5 | export { getAppVersion } from "./getVersion"; 6 | export { signTx } from "./signTx"; 7 | -------------------------------------------------------------------------------- /src/commands/app/signTx.ts: -------------------------------------------------------------------------------- 1 | import { chunk } from "@fleet-sdk/common"; 2 | import { ErgoAddress, type Network } from "@fleet-sdk/core"; 3 | import { hex } from "@fleet-sdk/crypto"; 4 | import { COMMAND, type Device, MAX_DATA_LENGTH } from "../../device"; 5 | import { ByteWriter } from "../../serialization/byteWriter"; 6 | import { EMPTY_BYTES } from "../../serialization/utils"; 7 | import type { AttestedBox } from "../../types/attestedBox"; 8 | import type { AttestedTransaction } from "../../types/internal"; 9 | import type { BoxCandidate, ChangeMap, Token } from "../../types/public"; 10 | 11 | const MINER_FEE_TREE = 12 | "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304"; 13 | 14 | const enum P1 { 15 | START_SIGNING = 0x01, 16 | START_TRANSACTION = 0x10, 17 | ADD_TOKEN_IDS = 0x11, 18 | ADD_INPUT_BOX_FRAME = 0x12, 19 | ADD_INPUT_BOX_CONTEXT_EXTENSION_CHUNK = 0x13, 20 | ADD_DATA_INPUTS = 0x14, 21 | ADD_OUTPUT_BOX_START = 0x15, 22 | ADD_OUTPUT_BOX_ERGO_TREE_CHUNK = 0x16, 23 | ADD_OUTPUT_BOX_MINERS_FEE_TREE = 0x17, 24 | ADD_OUTPUT_BOX_CHANGE_TREE = 0x18, 25 | ADD_OUTPUT_BOX_TOKENS = 0x19, 26 | ADD_OUTPUT_BOX_REGISTERS_CHUNK = 0x1a, 27 | CONFIRM_AND_SIGN = 0x20 28 | } 29 | 30 | const enum P2 { 31 | WITHOUT_TOKEN = 0x01, 32 | WITH_TOKEN = 0x02 33 | } 34 | 35 | const CLA = 0xe0; 36 | const HASH_SIZE = 32; 37 | const HEADER_SIZE = 46; // https://github.com/ergoplatform/ledger-app-ergo/blob/main/doc/INS-21-SIGN-TRANSACTION.md#data 38 | const START_TX_SIZE = 7; // https://github.com/ergoplatform/ledger-app-ergo/blob/main/doc/INS-21-SIGN-TRANSACTION.md#0x10---start-transaction-data 39 | const ADD_OUTPUT_HEADER_SIZE = 21; // https://github.com/ergoplatform/ledger-app-ergo/blob/main/doc/INS-21-SIGN-TRANSACTION.md#data-5 40 | const ADD_OUTPUT_CHANGE_PATH_SIZE = 51; // https://github.com/ergoplatform/ledger-app-ergo/blob/main/doc/INS-21-SIGN-TRANSACTION.md#data-6 41 | const ADD_OUTPUT_TOKEN_SIZE = 12; // https://github.com/ergoplatform/ledger-app-ergo/blob/main/doc/INS-21-SIGN-TRANSACTION.md#0x19---add-output-box-tokens 42 | 43 | export async function signTx( 44 | device: Device, 45 | tx: AttestedTransaction, 46 | signPath: string, 47 | network: Network, 48 | authToken?: number 49 | ): Promise { 50 | const sessionId = await sendHeader(device, network, signPath, authToken); 51 | await sendStartTx(device, sessionId, tx, tx.distinctTokenIds.length); 52 | await sendDistinctTokensIds(device, sessionId, tx.distinctTokenIds); 53 | await sendInputs(device, sessionId, tx.inputs); 54 | await sendDataInputs(device, sessionId, tx.dataInputs); 55 | await sendOutputs(device, sessionId, tx.outputs, tx.changeMap, tx.distinctTokenIds); 56 | const proof = await sendConfirmAndSign(device, sessionId); 57 | 58 | return proof; 59 | } 60 | 61 | async function sendHeader( 62 | device: Device, 63 | network: Network, 64 | path: string, 65 | authToken?: number 66 | ): Promise { 67 | const response = await device.send( 68 | CLA, 69 | COMMAND.SIGN_TX, 70 | P1.START_SIGNING, 71 | authToken ? P2.WITH_TOKEN : P2.WITHOUT_TOKEN, 72 | new ByteWriter(HEADER_SIZE) 73 | .writeUInt8(network) 74 | .writePath(path) 75 | .writeAuthToken(authToken) 76 | .toBytes() 77 | ); 78 | 79 | return response.data[0]; 80 | } 81 | 82 | async function sendStartTx( 83 | device: Device, 84 | sessionId: number, 85 | tx: AttestedTransaction, 86 | uniqueTokenIdsCount: number 87 | ): Promise { 88 | const response = await device.send( 89 | CLA, 90 | COMMAND.SIGN_TX, 91 | P1.START_TRANSACTION, 92 | sessionId, 93 | new ByteWriter(START_TX_SIZE) 94 | .writeUInt16(tx.inputs.length) 95 | .writeUInt16(tx.dataInputs.length) 96 | .writeUInt8(uniqueTokenIdsCount) 97 | .writeUInt16(tx.outputs.length) 98 | .toBytes() 99 | ); 100 | 101 | return response.data[0]; 102 | } 103 | 104 | async function sendDistinctTokensIds( 105 | device: Device, 106 | sessionId: number, 107 | tokenIds: Uint8Array[] 108 | ) { 109 | const chunks = chunk(tokenIds, Math.floor(MAX_DATA_LENGTH / HASH_SIZE)); 110 | for (const chunk of chunks) { 111 | const data = new ByteWriter(chunk.length * HASH_SIZE); 112 | for (const id of chunk) data.writeBytes(id); 113 | 114 | await device.send(CLA, COMMAND.SIGN_TX, P1.ADD_TOKEN_IDS, sessionId, data.toBytes()); 115 | } 116 | } 117 | 118 | async function sendInputs(device: Device, sessionId: number, inputs: AttestedBox[]) { 119 | for (const input of inputs) { 120 | for (const frame of input.frames) { 121 | await device.send( 122 | CLA, 123 | COMMAND.SIGN_TX, 124 | P1.ADD_INPUT_BOX_FRAME, 125 | sessionId, 126 | frame.bytes 127 | ); 128 | } 129 | 130 | if (input.extension !== undefined && input.extension.length > 0) { 131 | await sendBoxContextExtension(device, sessionId, input.extension); 132 | } 133 | } 134 | } 135 | 136 | async function sendBoxContextExtension( 137 | device: Device, 138 | sessionId: number, 139 | extension: Uint8Array 140 | ) { 141 | await device.sendData( 142 | CLA, 143 | COMMAND.SIGN_TX, 144 | P1.ADD_INPUT_BOX_CONTEXT_EXTENSION_CHUNK, 145 | sessionId, 146 | extension 147 | ); 148 | } 149 | 150 | async function sendDataInputs(device: Device, sessionId: number, boxIds: string[]) { 151 | const chunks = chunk(boxIds, Math.floor(MAX_DATA_LENGTH / HASH_SIZE)); 152 | for (const chunk of chunks) { 153 | const data = new ByteWriter(chunk.length * HASH_SIZE); 154 | for (const id of chunk) data.writeHex(id); 155 | 156 | await device.send( 157 | CLA, 158 | COMMAND.SIGN_TX, 159 | P1.ADD_DATA_INPUTS, 160 | sessionId, 161 | data.toBytes() 162 | ); 163 | } 164 | } 165 | 166 | async function sendOutputs( 167 | device: Device, 168 | sessionId: number, 169 | boxes: BoxCandidate[], 170 | changeMap: ChangeMap, 171 | distinctTokenIds: Uint8Array[] 172 | ) { 173 | const distinctTokenIdsStr = distinctTokenIds.map((t) => Buffer.from(t).toString("hex")); 174 | 175 | for (const box of boxes) { 176 | await device.send( 177 | CLA, 178 | COMMAND.SIGN_TX, 179 | P1.ADD_OUTPUT_BOX_START, 180 | sessionId, 181 | new ByteWriter(ADD_OUTPUT_HEADER_SIZE) 182 | .writeUInt64(box.value) 183 | .writeUInt32(box.ergoTree.length) 184 | .writeUInt32(box.creationHeight) 185 | .writeUInt8(box.tokens.length) 186 | .writeUInt32(box.registers.length) 187 | .toBytes() 188 | ); 189 | 190 | const tree = hex.encode(box.ergoTree); 191 | if (tree === MINER_FEE_TREE) { 192 | await addOutputBoxMinersFeeTree(device, sessionId); 193 | } else if (ErgoAddress.fromErgoTree(tree).toString() === changeMap.address) { 194 | await addOutputBoxChangePath(device, sessionId, changeMap.path); 195 | } else { 196 | await addOutputBoxErgoTree(device, sessionId, box.ergoTree); 197 | } 198 | 199 | if (box.tokens && box.tokens.length > 0) { 200 | await addOutputBoxTokens(device, sessionId, box.tokens, distinctTokenIdsStr); 201 | } 202 | 203 | if (box.registers.length > 0) { 204 | await addOutputBoxRegisters(device, sessionId, box.registers); 205 | } 206 | } 207 | } 208 | 209 | async function addOutputBoxErgoTree( 210 | device: Device, 211 | sessionId: number, 212 | ergoTree: Uint8Array 213 | ) { 214 | await device.sendData( 215 | CLA, 216 | COMMAND.SIGN_TX, 217 | P1.ADD_OUTPUT_BOX_ERGO_TREE_CHUNK, 218 | sessionId, 219 | ergoTree 220 | ); 221 | } 222 | 223 | async function addOutputBoxMinersFeeTree(device: Device, sessionId: number) { 224 | await device.send( 225 | CLA, 226 | COMMAND.SIGN_TX, 227 | P1.ADD_OUTPUT_BOX_MINERS_FEE_TREE, 228 | sessionId, 229 | EMPTY_BYTES 230 | ); 231 | } 232 | 233 | async function addOutputBoxChangePath(device: Device, sessionId: number, path: string) { 234 | await device.send( 235 | CLA, 236 | COMMAND.SIGN_TX, 237 | P1.ADD_OUTPUT_BOX_CHANGE_TREE, 238 | sessionId, 239 | new ByteWriter(ADD_OUTPUT_CHANGE_PATH_SIZE).writePath(path).toBytes() 240 | ); 241 | } 242 | 243 | async function addOutputBoxTokens( 244 | device: Device, 245 | sessionId: number, 246 | tokens: Token[], 247 | distinctTokenIds: string[] 248 | ) { 249 | const chunks = chunk(tokens, Math.floor(MAX_DATA_LENGTH / ADD_OUTPUT_TOKEN_SIZE)); 250 | for (const chunk of chunks) { 251 | const data = new ByteWriter(chunk.length * ADD_OUTPUT_TOKEN_SIZE); 252 | for (const token of chunk) { 253 | data.writeUInt32(distinctTokenIds.indexOf(token.id)).writeUInt64(token.amount); 254 | } 255 | 256 | await device.send( 257 | CLA, 258 | COMMAND.SIGN_TX, 259 | P1.ADD_OUTPUT_BOX_TOKENS, 260 | sessionId, 261 | data.toBytes() 262 | ); 263 | } 264 | } 265 | 266 | async function addOutputBoxRegisters( 267 | device: Device, 268 | sessionId: number, 269 | registers: Uint8Array 270 | ) { 271 | await device.sendData( 272 | CLA, 273 | COMMAND.SIGN_TX, 274 | P1.ADD_OUTPUT_BOX_REGISTERS_CHUNK, 275 | sessionId, 276 | registers 277 | ); 278 | } 279 | 280 | async function sendConfirmAndSign( 281 | device: Device, 282 | sessionId: number 283 | ): Promise { 284 | const response = await device.send( 285 | CLA, 286 | COMMAND.SIGN_TX, 287 | P1.CONFIRM_AND_SIGN, 288 | sessionId, 289 | EMPTY_BYTES 290 | ); 291 | 292 | return response.data; 293 | } 294 | -------------------------------------------------------------------------------- /src/commands/os/closeApp.ts: -------------------------------------------------------------------------------- 1 | import { RETURN_CODE, type Device } from "../../device"; 2 | import { EMPTY_BYTES, NO_VALUE } from "../../serialization/utils"; 3 | 4 | const CLA = 0xb0; 5 | const CLOSE_APP = 0xa7; 6 | 7 | export async function closeApp(device: Device): Promise { 8 | const response = await device.send(CLA, CLOSE_APP, NO_VALUE, NO_VALUE, EMPTY_BYTES); 9 | 10 | return response.returnCode === RETURN_CODE.OK; 11 | } 12 | -------------------------------------------------------------------------------- /src/commands/os/getCurrentAppInfo.ts: -------------------------------------------------------------------------------- 1 | import type { Device } from "../../device"; 2 | import { asciiCodec, EMPTY_BYTES, NO_VALUE } from "../../serialization/utils"; 3 | 4 | const CLA = 0xb0; 5 | const GET_CURRENT_APP_INFO = 0x01; 6 | 7 | export type AppInfo = { 8 | name: string; 9 | version: string; 10 | }; 11 | 12 | function reader(bytes: Uint8Array) { 13 | let offset = 0; 14 | 15 | return { 16 | readByte(): number { 17 | return bytes[offset++]; 18 | }, 19 | readBytes(number: number): Uint8Array { 20 | const result = bytes.subarray(offset, offset + number); 21 | offset += number; 22 | 23 | return result; 24 | }, 25 | readLV(): Uint8Array { 26 | const length = this.readByte(); 27 | return this.readBytes(length); 28 | } 29 | }; 30 | } 31 | 32 | export async function getCurrentAppInfo(device: Device): Promise { 33 | const { data } = await device.send( 34 | CLA, 35 | GET_CURRENT_APP_INFO, 36 | NO_VALUE, 37 | NO_VALUE, 38 | EMPTY_BYTES 39 | ); 40 | 41 | const r = reader(data); 42 | if (r.readByte() !== 0x01) throw new Error("Response format is not recognized"); 43 | 44 | return { 45 | name: asciiCodec.decode(r.readLV()), 46 | version: asciiCodec.decode(r.readLV()) 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /src/commands/os/index.ts: -------------------------------------------------------------------------------- 1 | export { openApp } from "./openApp"; 2 | export { closeApp } from "./closeApp"; 3 | export * from "./getCurrentAppInfo"; 4 | -------------------------------------------------------------------------------- /src/commands/os/openApp.ts: -------------------------------------------------------------------------------- 1 | import { RETURN_CODE, type Device } from "../../device"; 2 | import { asciiCodec, NO_VALUE } from "../../serialization/utils"; 3 | 4 | const CLA = 0xe0; 5 | const OPEN_APP = 0xd8; 6 | 7 | export async function openApp(device: Device, appName: string): Promise { 8 | const response = await device.send( 9 | CLA, 10 | OPEN_APP, 11 | NO_VALUE, 12 | NO_VALUE, 13 | asciiCodec.encode(appName) 14 | ); 15 | 16 | return response.returnCode === RETURN_CODE.OK; 17 | } 18 | -------------------------------------------------------------------------------- /src/device.spec.ts: -------------------------------------------------------------------------------- 1 | import { RecordStore, openTransportReplayer } from "@ledgerhq/hw-transport-mocker"; 2 | import { describe, expect, it, test } from "vitest"; 3 | import { Device, DeviceError, RETURN_CODE } from "./device"; 4 | 5 | describe("DeviceError construction", () => { 6 | it("should create a new DeviceError instance from a know RETURN_CODE", () => { 7 | const error = new DeviceError(RETURN_CODE.TOO_MUCH_DATA); 8 | expect(error.code).toBe(RETURN_CODE.TOO_MUCH_DATA); 9 | expect(error.message).toBe("Too much data"); 10 | expect(error.name).to.be.equal("DeviceError"); 11 | }); 12 | 13 | it("should create a new DeviceError instance from a unknown RETURN_CODE", () => { 14 | const error = new DeviceError(0 as RETURN_CODE); 15 | expect(error.code).toBe(0); 16 | expect(error.message).toBe("Unknown error"); 17 | }); 18 | }); 19 | 20 | describe("OS interactions", () => { 21 | test.each([ 22 | { 23 | record: RecordStore.fromString(` 24 | => b001000000 25 | <= 01044572676f05302e302e3601029000 26 | `), 27 | name: "Ergo", 28 | version: "0.0.6" 29 | }, 30 | { 31 | record: RecordStore.fromString(` 32 | => b001000000 33 | <= 0105424f4c4f5305312e312e319000 34 | `), 35 | name: "BOLOS", 36 | version: "1.1.1" 37 | } 38 | ])("should get current app name and version", async (tv) => { 39 | const transport = await openTransportReplayer(tv.record); 40 | const device = new Device(transport); 41 | 42 | const { name, version } = await device.getCurrentAppInfo(); 43 | 44 | expect(name).to.be.equal(tv.name); 45 | expect(version).to.be.equal(tv.version); 46 | }); 47 | 48 | it("should throw an error if the response format is not recognized", async () => { 49 | const transport = await openTransportReplayer( 50 | RecordStore.fromString(` 51 | => b001000000 52 | <= 02044572676f05302e302e3601029000 53 | `) //^^ -- should be 01 54 | ); 55 | 56 | const device = new Device(transport); 57 | 58 | await expect(() => device.getCurrentAppInfo()).rejects.toThrow( 59 | "Response format is not recognized" 60 | ); 61 | }); 62 | 63 | it("should open ergo app", async () => { 64 | const transport = await openTransportReplayer( 65 | RecordStore.fromString(` 66 | => e0d80000044572676f 67 | <= 9000 68 | `) 69 | ); 70 | 71 | const device = new Device(transport); 72 | const result = await device.openApp(); 73 | 74 | expect(result).to.be.true; 75 | }); 76 | 77 | it("should close current app", async () => { 78 | const transport = await openTransportReplayer( 79 | RecordStore.fromString(` 80 | => b0a7000000 81 | <= 9000 82 | `) 83 | ); 84 | 85 | const device = new Device(transport); 86 | const result = await device.closeApp(); 87 | 88 | expect(result).to.be.true; 89 | }); 90 | }); 91 | 92 | describe("Negative tests", () => { 93 | it("Should throw an error if the response length is less than the minimum", async () => { 94 | const transport = await openTransportReplayer( 95 | RecordStore.fromString(` 96 | => e00102030104 97 | <= 69 98 | `) 99 | ); 100 | 101 | const device = new Device(transport); 102 | 103 | await expect(() => 104 | device.send(0xe0, 0x1, 0x2, 0x3, Buffer.from([0x4])) 105 | ).rejects.toThrow("Wrong response length"); 106 | }); 107 | 108 | it("Should throw if data is too long", async () => { 109 | const transport = await openTransportReplayer( 110 | RecordStore.fromString(` 111 | => e00102030104 112 | <= 9000 113 | `) 114 | ); 115 | 116 | const device = new Device(transport); 117 | 118 | await expect(() => 119 | device.send(0xe0, 0x1, 0x2, 0x3, Buffer.alloc(260)) 120 | ).rejects.toThrow("Too much data"); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /src/device.ts: -------------------------------------------------------------------------------- 1 | import type Transport from "@ledgerhq/hw-transport"; 2 | import { ByteWriter } from "./serialization/byteWriter"; 3 | import type { DeviceResponse } from "./types/internal"; 4 | import { hex } from "@fleet-sdk/crypto"; 5 | import { type AppInfo, closeApp, getCurrentAppInfo, openApp } from "./commands/os"; 6 | 7 | export const enum COMMAND { 8 | GET_APP_VERSION = 0x01, 9 | GET_APP_NAME = 0x02, 10 | 11 | GET_EXTENDED_PUB_KEY = 0x10, 12 | DERIVE_ADDRESS = 0x11, 13 | ATTEST_INPUT = 0x20, 14 | SIGN_TX = 0x21 15 | } 16 | 17 | export const MAX_DATA_LENGTH = 255; 18 | const MIN_RESPONSE_LENGTH = 2; 19 | const MIN_APDU_LENGTH = 5; 20 | 21 | export class Device { 22 | #transport: Transport; 23 | #log: boolean; 24 | 25 | get transport(): Transport { 26 | return this.#transport; 27 | } 28 | 29 | constructor(transport: Transport) { 30 | this.#transport = transport; 31 | this.#log = false; 32 | } 33 | 34 | enableLogging(enabled = true): Device { 35 | this.#log = enabled; 36 | return this; 37 | } 38 | 39 | /** 40 | * Retrieves information about the currently running application on the device. 41 | * 42 | * @returns {Promise} A promise that resolves to an object containing information about the current application 43 | */ 44 | async getCurrentAppInfo(): Promise { 45 | return getCurrentAppInfo(this); 46 | } 47 | 48 | /** 49 | * Opens the Ergo application on the Ledger device. 50 | * @returns Promise that resolves to true if the application was opened successfully 51 | */ 52 | async openApp(name = "Ergo"): Promise { 53 | return openApp(this, name); 54 | } 55 | 56 | /** 57 | * Closes the currently open Ergo app on the Ledger device. 58 | * @returns A promise that resolves to true if the app was successfully closed. 59 | */ 60 | async closeApp(): Promise { 61 | return closeApp(this); 62 | } 63 | 64 | async sendData( 65 | cla: number, 66 | ins: COMMAND, 67 | p1: number, 68 | p2: number, 69 | data: Uint8Array 70 | ): Promise { 71 | const responses: DeviceResponse[] = []; 72 | for (let i = 0; i < Math.ceil(data.length / MAX_DATA_LENGTH); i++) { 73 | const chunk = data.subarray( 74 | i * MAX_DATA_LENGTH, 75 | Math.min((i + 1) * MAX_DATA_LENGTH, data.length) 76 | ); 77 | 78 | responses.push(await this.send(cla, ins, p1, p2, chunk)); 79 | } 80 | 81 | return responses; 82 | } 83 | 84 | async send( 85 | cla: number, 86 | ins: number, 87 | p1: number, 88 | p2: number, 89 | data: Uint8Array 90 | ): Promise { 91 | if (data.length > MAX_DATA_LENGTH) { 92 | throw new DeviceError(RETURN_CODE.TOO_MUCH_DATA); 93 | } 94 | 95 | const apdu = buildApdu(cla, ins, p1, p2, data); 96 | const response = await this.#transport.exchange(apdu); 97 | 98 | if (this.#log) { 99 | console.debug(`=> ${hex.encode(apdu)}\n<= ${hex.encode(response)}`); 100 | } 101 | 102 | if (response.length < MIN_RESPONSE_LENGTH) { 103 | throw new DeviceError(RETURN_CODE.WRONG_RESPONSE_LENGTH); 104 | } 105 | const returnCode = response.readUInt16BE(response.length - 2); 106 | if (returnCode !== RETURN_CODE.OK) throw new DeviceError(returnCode); 107 | 108 | const responseData = response.subarray(0, response.length - 2); 109 | return { returnCode, data: responseData }; 110 | } 111 | } 112 | 113 | function buildApdu( 114 | cla: number, 115 | ins: COMMAND, 116 | p1: number, 117 | p2: number, 118 | data: Uint8Array 119 | ): Buffer { 120 | return new ByteWriter(MIN_APDU_LENGTH + data.length) 121 | .write(cla) 122 | .write(ins) 123 | .write(p1) 124 | .write(p2) 125 | .write(data.length) 126 | .writeBytes(data) 127 | .toBuffer(); 128 | } 129 | 130 | export class DeviceError extends Error { 131 | #code; 132 | 133 | get code() { 134 | return this.#code; 135 | } 136 | 137 | constructor(code: RETURN_CODE, options?: ErrorOptions) { 138 | super(RETURN_MESSAGES[code] || "Unknown error", options); 139 | this.#code = code; 140 | 141 | Object.setPrototypeOf(this, new.target.prototype); 142 | this.name = new.target.name; 143 | } 144 | } 145 | 146 | export enum RETURN_CODE { 147 | DENIED = 0x6985, 148 | WRONG_P1P2 = 0x6a86, 149 | WRONG_APDU_DATA_LENGTH = 0x6a87, 150 | INS_NOT_SUPPORTED = 0x6d00, 151 | CLA_NOT_SUPPORTED = 0x6e00, 152 | BUSY = 0xb000, 153 | WRONG_RESPONSE_LENGTH = 0xb001, 154 | BAD_SESSION_ID = 0xb002, 155 | WRONG_SUBCOMMAND = 0xb003, 156 | BAD_STATE = 0xb0ff, 157 | BAD_TOKEN_ID = 0xe001, 158 | BAD_TOKEN_VALUE = 0xe002, 159 | BAD_CONTEXT_EXTENSION_SIZE = 0xe003, 160 | BAD_DATA_INPUT = 0xe004, 161 | BAD_BOX_ID = 0xe005, 162 | BAD_TOKEN_INDEX = 0xe006, 163 | BAD_FRAME_INDEX = 0xe007, 164 | BAD_INPUT_COUNT = 0xe008, 165 | BAD_OUTPUT_COUNT = 0xe009, 166 | TOO_MANY_TOKENS = 0xe00a, 167 | TOO_MANY_INPUTS = 0xe00b, 168 | TOO_MANY_DATA_INPUTS = 0xe00c, 169 | TOO_MANY_INPUT_FRAMES = 0xe00d, 170 | TOO_MANY_OUTPUTS = 0xe00e, 171 | HASHER_ERROR = 0xe00f, 172 | BUFFER_ERROR = 0xe010, 173 | U64_OVERFLOW = 0xe011, 174 | BIP32_BAD_PATH = 0xe012, 175 | INTERNAL_CRYPTO_ERROR = 0xe013, 176 | NOT_ENOUGH_DATA = 0xe014, 177 | TOO_MUCH_DATA = 0xe015, 178 | ADDRESS_GENERATION_FAILED = 0xe016, 179 | SCHNORR_SIGNING_FAILED = 0xe017, 180 | BAD_FRAME_SIGNATURE = 0xe018, 181 | BAD_NET_TYPE_VALUE = 0xe019, 182 | SW_SMALL_CHUNK = 0xe01a, 183 | BIP32_FORMATTING_FAILED = 0xe101, 184 | ADDRESS_FORMATTING_FAILED = 0xe102, 185 | STACK_OVERFLOW = 0xffff, 186 | 187 | OK = 0x9000, 188 | GLOBAL_LOCKED_DEVICE = 0x5515, 189 | GLOBAL_ACTION_REFUSED = 0x5501, 190 | GLOBAL_PIN_NOT_SET = 0x5502, 191 | GLOBAL_DEVICE_INTERNAL_ERROR = 0x5223, 192 | NO_APP_NAME_PROVIDED = 0x670a, 193 | UNKNOWN_APPLICATION_NAME = 0x6807 194 | } 195 | 196 | export const RETURN_MESSAGES = { 197 | [RETURN_CODE.DENIED]: "Operation denied by user", 198 | [RETURN_CODE.WRONG_P1P2]: "Incorrect P1 or P2", 199 | [RETURN_CODE.WRONG_APDU_DATA_LENGTH]: "Bad APDU length", 200 | [RETURN_CODE.INS_NOT_SUPPORTED]: "Instruction isn't supported", 201 | [RETURN_CODE.CLA_NOT_SUPPORTED]: "CLA is not supported", 202 | [RETURN_CODE.BUSY]: "Device is busy", 203 | [RETURN_CODE.WRONG_RESPONSE_LENGTH]: "Wrong response length", 204 | [RETURN_CODE.BAD_SESSION_ID]: "Bad session id", 205 | [RETURN_CODE.WRONG_SUBCOMMAND]: "Unknown subcommand", 206 | [RETURN_CODE.BAD_STATE]: "Bad state (check order of calls and errors)", 207 | [RETURN_CODE.BAD_TOKEN_ID]: "Bad token ID", 208 | [RETURN_CODE.BAD_TOKEN_VALUE]: "Bad token value", 209 | [RETURN_CODE.BAD_CONTEXT_EXTENSION_SIZE]: "Bad context extension size", 210 | [RETURN_CODE.BAD_DATA_INPUT]: "Bad data input ID", 211 | [RETURN_CODE.BAD_BOX_ID]: "Bad box ID", 212 | [RETURN_CODE.BAD_TOKEN_INDEX]: "Bad token index", 213 | [RETURN_CODE.BAD_FRAME_INDEX]: "Bad frame index", 214 | [RETURN_CODE.BAD_INPUT_COUNT]: "Bad input count", 215 | [RETURN_CODE.BAD_OUTPUT_COUNT]: "Bad output count", 216 | [RETURN_CODE.TOO_MANY_TOKENS]: "Too many tokens", 217 | [RETURN_CODE.TOO_MANY_INPUTS]: "Too many inputs", 218 | [RETURN_CODE.TOO_MANY_DATA_INPUTS]: "Too many data inputs", 219 | [RETURN_CODE.TOO_MANY_INPUT_FRAMES]: "Too many input frames", 220 | [RETURN_CODE.TOO_MANY_OUTPUTS]: "Too many outputs", 221 | [RETURN_CODE.HASHER_ERROR]: "Hasher internal error", 222 | [RETURN_CODE.BUFFER_ERROR]: "Buffer internal error", 223 | [RETURN_CODE.U64_OVERFLOW]: "UInt64 overflow", 224 | [RETURN_CODE.BIP32_BAD_PATH]: "Bad Bip32 path", 225 | [RETURN_CODE.INTERNAL_CRYPTO_ERROR]: "Internal crypto engine error", 226 | [RETURN_CODE.NOT_ENOUGH_DATA]: "Not enough data", 227 | [RETURN_CODE.TOO_MUCH_DATA]: "Too much data", 228 | [RETURN_CODE.ADDRESS_GENERATION_FAILED]: "Address generation failed", 229 | [RETURN_CODE.SCHNORR_SIGNING_FAILED]: "Schnorr signing failed", 230 | [RETURN_CODE.BAD_FRAME_SIGNATURE]: "Bad frame signature", 231 | [RETURN_CODE.BAD_NET_TYPE_VALUE]: "Bad network type value", 232 | [RETURN_CODE.SW_SMALL_CHUNK]: "Bad chunk size", 233 | [RETURN_CODE.BIP32_FORMATTING_FAILED]: "Can't display Bip32 path", 234 | [RETURN_CODE.ADDRESS_FORMATTING_FAILED]: "Can't display address", 235 | [RETURN_CODE.STACK_OVERFLOW]: "Stack overflow", 236 | [RETURN_CODE.OK]: "Ok", 237 | [RETURN_CODE.GLOBAL_LOCKED_DEVICE]: "Device is locked", 238 | [RETURN_CODE.GLOBAL_ACTION_REFUSED]: "Action refused on device", 239 | [RETURN_CODE.GLOBAL_PIN_NOT_SET]: "Pin is not set", 240 | [RETURN_CODE.GLOBAL_DEVICE_INTERNAL_ERROR]: "Device internal error", 241 | [RETURN_CODE.NO_APP_NAME_PROVIDED]: "No app name provided", 242 | [RETURN_CODE.UNKNOWN_APPLICATION_NAME]: "Unknown application name" 243 | }; 244 | -------------------------------------------------------------------------------- /src/erg.spec.ts: -------------------------------------------------------------------------------- 1 | import { RecordStore, openTransportReplayer } from "@ledgerhq/hw-transport-mocker"; 2 | import { describe, expect, it, test, vi } from "vitest"; 3 | import { Device, ErgoLedgerApp } from "./erg"; 4 | 5 | describe("construction", () => { 6 | it("should construct app with transport", async () => { 7 | const transport = await openTransportReplayer( 8 | RecordStore.fromString(` 9 | => e001000000 10 | <= 000004019000 11 | `) 12 | ); 13 | 14 | const app = new ErgoLedgerApp(transport); 15 | 16 | expect(app.device.transport).to.be.equal(transport); 17 | expect(app.authToken).to.be.greaterThan(0); 18 | expect(app.device).to.be.instanceOf(Device); 19 | }); 20 | 21 | it("should enable debug mode", async () => { 22 | const consoleMock = vi.spyOn(console, "debug").mockImplementation(() => { 23 | return; 24 | }); 25 | 26 | const transport = await openTransportReplayer( 27 | RecordStore.fromString(` 28 | => e001000000 29 | <= 000004019000 30 | 31 | => e010020011038000002c800001ad800000007ee523ef 32 | <= 025381e95e132a4b7a6fc66844a81657a07da1ef5041eaefb7fce03f71c06a11a99cc4eb9abc8d3f55afeff7bcb8fe2d0a8d100fa35f6fcbac74deded867633eba9000 33 | `) 34 | ); 35 | 36 | const authToken = Number("0x7ee523ef"); 37 | const app = new ErgoLedgerApp(transport, authToken).useLogging(); 38 | 39 | await app.getAppVersion(); // without arguments call 40 | await app.getExtendedPublicKey("m/44'/429'/0'"); // with arguments call 41 | 42 | expect(consoleMock).to.be.toHaveBeenCalledTimes(2); 43 | }); 44 | }); 45 | 46 | describe("Get app name and version", () => { 47 | it("should get app version", async () => { 48 | const transport = await openTransportReplayer( 49 | RecordStore.fromString(` 50 | => e001000000 51 | <= 000004019000 52 | `) 53 | ); 54 | 55 | const app = new ErgoLedgerApp(transport); 56 | const version = await app.getAppVersion(); 57 | 58 | expect(app.device.transport).to.be.equal(transport); 59 | expect(version).to.be.deep.equal({ 60 | flags: { isDebug: true }, 61 | major: 0, 62 | minor: 0, 63 | patch: 4 64 | }); 65 | }); 66 | 67 | it("should get app name", async () => { 68 | const transport = await openTransportReplayer( 69 | RecordStore.fromString(` 70 | => e002000000 71 | <= 4572676f9000 72 | `) 73 | ); 74 | 75 | const app = new ErgoLedgerApp(transport); 76 | const appName = await app.getAppName(); 77 | 78 | expect(appName).toEqual({ name: "Ergo" }); 79 | }); 80 | }); 81 | 82 | describe("public key management with auth token", () => { 83 | it("should get extended public key", async () => { 84 | const transport = await openTransportReplayer( 85 | RecordStore.fromString(` 86 | => e010020011038000002c800001ad800000007ee523ef 87 | <= 025381e95e132a4b7a6fc66844a81657a07da1ef5041eaefb7fce03f71c06a11a99cc4eb9abc8d3f55afeff7bcb8fe2d0a8d100fa35f6fcbac74deded867633eba9000 88 | `) 89 | ); 90 | 91 | const authToken = Number("0x7ee523ef"); 92 | const app = new ErgoLedgerApp(transport, authToken); 93 | const extendedPublicKey = await app.getExtendedPublicKey("m/44'/429'/0'"); 94 | 95 | expect(app.authToken).toEqual(authToken); 96 | expect(extendedPublicKey).toEqual({ 97 | publicKey: "025381e95e132a4b7a6fc66844a81657a07da1ef5041eaefb7fce03f71c06a11a9", 98 | chainCode: "9cc4eb9abc8d3f55afeff7bcb8fe2d0a8d100fa35f6fcbac74deded867633eba" 99 | }); 100 | }); 101 | 102 | it("should show address", async () => { 103 | const transport = await openTransportReplayer( 104 | RecordStore.fromString(` 105 | => e01102021a00058000002c800001ad8000000000000000000000007ee523ef 106 | <= 9000 107 | `) 108 | ); 109 | 110 | const authToken = Number("0x7ee523ef"); 111 | const app = new ErgoLedgerApp(transport, authToken); 112 | const accepted = await app.showAddress("m/44'/429'/0'/0/0"); 113 | 114 | expect(accepted).to.be.true; 115 | }); 116 | 117 | it("should derive address", async () => { 118 | const transport = await openTransportReplayer( 119 | RecordStore.fromString(` 120 | => e01101021a00058000002c800001ad8000000000000000000000007de17fdd 121 | <= 0102a51a0c5e6b456c2c8e71f238dc02f5345aad9a7c5b8c655dd24bc5e419c4212d731952549000 122 | `) 123 | ); 124 | 125 | const authToken = Number("0x7de17fdd"); 126 | const app = new ErgoLedgerApp(transport, authToken); 127 | const accepted = await app.deriveAddress("m/44'/429'/0'/0/0"); 128 | 129 | expect(accepted).to.be.deep.equal({ 130 | addressHex: 131 | "0102a51a0c5e6b456c2c8e71f238dc02f5345aad9a7c5b8c655dd24bc5e419c4212d73195254" 132 | }); 133 | }); 134 | }); 135 | 136 | describe("public key management without auth token", () => { 137 | it("should get extended public key", async () => { 138 | const transport = await openTransportReplayer( 139 | RecordStore.fromString(` 140 | => e01001000d038000002c800001ad80000000 141 | <= 025381e95e132a4b7a6fc66844a81657a07da1ef5041eaefb7fce03f71c06a11a99cc4eb9abc8d3f55afeff7bcb8fe2d0a8d100fa35f6fcbac74deded867633eba9000 142 | `) 143 | ); 144 | 145 | const app = new ErgoLedgerApp(transport).useAuthToken(false); 146 | const extendedPublicKey = await app.getExtendedPublicKey("m/44'/429'/0'"); 147 | 148 | expect(extendedPublicKey).toEqual({ 149 | publicKey: "025381e95e132a4b7a6fc66844a81657a07da1ef5041eaefb7fce03f71c06a11a9", 150 | chainCode: "9cc4eb9abc8d3f55afeff7bcb8fe2d0a8d100fa35f6fcbac74deded867633eba" 151 | }); 152 | }); 153 | 154 | it("should handle user rejection on extended public key fetching", async () => { 155 | const transport = await openTransportReplayer( 156 | RecordStore.fromString(` 157 | => e01001000d038000002c800001ad80000000 158 | <= 6985 159 | `) 160 | ); 161 | 162 | const app = new ErgoLedgerApp(transport).useAuthToken(false); 163 | await expect(() => app.getExtendedPublicKey("m/44'/429'/0'")).rejects.toThrow( 164 | "Operation denied by user" 165 | ); 166 | }); 167 | 168 | it("should show address", async () => { 169 | const transport = await openTransportReplayer( 170 | RecordStore.fromString(` 171 | => e01102011600058000002c800001ad800000000000000000000000 172 | <= 9000 173 | `) 174 | ); 175 | 176 | const app = new ErgoLedgerApp(transport).useAuthToken(false); 177 | const accepted = await app.showAddress("m/44'/429'/0'/0/0"); 178 | 179 | expect(accepted).to.be.true; 180 | }); 181 | 182 | it("should handle user rejection on address displaying", async () => { 183 | const transport = await openTransportReplayer( 184 | RecordStore.fromString(` 185 | => e01102011600058000002c800001ad800000000000000000000000 186 | <= 6985 187 | `) 188 | ); 189 | 190 | const app = new ErgoLedgerApp(transport).useAuthToken(false); 191 | await expect(() => app.showAddress("m/44'/429'/0'/0/0")).rejects.toThrow( 192 | "Operation denied by user" 193 | ); 194 | }); 195 | 196 | it("should fail when deriving address with invalid path", async () => { 197 | const transport = await openTransportReplayer( 198 | RecordStore.fromString(` 199 | => e01102011600058000002c800001ad800000000000000000000000 200 | <= 6985 201 | `) 202 | ); 203 | 204 | const app = new ErgoLedgerApp(transport).useAuthToken(false); 205 | await expect(() => app.showAddress("m/44'/429'/0'/3/0")).rejects.toThrow( 206 | "Invalid change path: 3" 207 | ); 208 | await expect(() => app.showAddress("m/44'/429'")).rejects.toThrow( 209 | "Invalid path length. 2" 210 | ); 211 | 212 | await expect(() => app.deriveAddress("m/44'/429'/0'/10/0")).rejects.toThrow( 213 | "Invalid change path: 10" 214 | ); 215 | await expect(() => app.deriveAddress("m/44'/429'/0'/1")).rejects.toThrow( 216 | "Invalid path length. 4" 217 | ); 218 | }); 219 | }); 220 | 221 | describe("transaction signing", () => { 222 | test.each(txTestVectors)( 223 | "should sign $name", 224 | async ({ apduQueue, authToken, proofs, tx }) => { 225 | const transport = await openTransportReplayer(RecordStore.fromString(apduQueue)); 226 | 227 | const app = new ErgoLedgerApp(transport, authToken).useAuthToken(!!authToken); 228 | 229 | const result = await app.signTx(tx); 230 | expect(result).to.deep.equal(proofs); 231 | } 232 | ); 233 | 234 | it("Should throw with empty inputs", async () => { 235 | const { apduQueue, tx } = txTestVectors[0]; 236 | const transport = await openTransportReplayer(RecordStore.fromString(apduQueue)); 237 | 238 | const app = new ErgoLedgerApp(transport); 239 | await expect(() => app.signTx({ ...tx, inputs: [] })).rejects.toThrow( 240 | "Bad input count" 241 | ); 242 | }); 243 | 244 | it("Should attest input", async () => { 245 | const transport = await openTransportReplayer( 246 | RecordStore.fromString(` 247 | => e02001023b7eb901e0e7a3b2c2a4795851fc40c58b71a31ba114dedacf467eda3d3902f09e00000000000005f5e100000000240013d0e80000000001670dc113 248 | <= e09000 249 | 250 | => e02002e0240008cd02a51a0c5e6b456c2c8e71f238dc02f5345aad9a7c5b8c655dd24bc5e419c4212d 251 | <= 9000 252 | 253 | => e02004e00100 254 | <= 019000 255 | 256 | => e02005e00100 257 | <= 2bb2f3111e33ad9d9ab1fa3ae184fc1f06a6ac5a71d40a825997f6637b44784f01000000000005f5e100008c2d9e1dcd467155df32bf1ded8007109000 258 | `) 259 | ); 260 | 261 | const authToken = Number("0x670dc113"); 262 | const app = new ErgoLedgerApp(transport, authToken).useAuthToken(); 263 | 264 | const box = { 265 | txId: "7eb901e0e7a3b2c2a4795851fc40c58b71a31ba114dedacf467eda3d3902f09e", 266 | index: 0, 267 | value: "100000000", 268 | ergoTree: Buffer.from([ 269 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 245, 270 | 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 45 271 | ]), 272 | creationHeight: 1298664, 273 | tokens: [], 274 | additionalRegisters: Buffer.from([0]), 275 | extension: Buffer.from([0]), 276 | signPath: "m/44'/429'/0'/0/0" 277 | }; 278 | 279 | const attestedBox = await app.attestInput(box); 280 | 281 | expect(attestedBox.box).to.be.equal(box); 282 | expect(attestedBox.frames).to.deep.equal([ 283 | { 284 | amount: "100000000", 285 | attestation: "8c2d9e1dcd467155df32bf1ded800710", 286 | boxId: "2bb2f3111e33ad9d9ab1fa3ae184fc1f06a6ac5a71d40a825997f6637b44784f", 287 | bytes: Buffer.from([ 288 | 43, 178, 243, 17, 30, 51, 173, 157, 154, 177, 250, 58, 225, 132, 252, 31, 6, 289 | 166, 172, 90, 113, 212, 10, 130, 89, 151, 246, 99, 123, 68, 120, 79, 1, 0, 0, 0, 290 | 0, 0, 5, 245, 225, 0, 0, 140, 45, 158, 29, 205, 70, 113, 85, 223, 50, 191, 29, 291 | 237, 128, 7, 16 292 | ]), 293 | index: 0, 294 | count: 1, 295 | tokens: [] 296 | } 297 | ]); 298 | }); 299 | }); 300 | 301 | const txTestVectors = [ 302 | { 303 | name: "simple erg-only p2p transaction", 304 | authToken: 0, 305 | apduQueue: ` 306 | => e0200101378e73d69748e76867f9e71351481137bc6d5e671979d050f61eabfdccee28a513000100000000067f2af0000000240013d0a10000000001 307 | <= 469000 308 | 309 | => e0200246240008cd02a51a0c5e6b456c2c8e71f238dc02f5345aad9a7c5b8c655dd24bc5e419c4212d 310 | <= 9000 311 | 312 | => e02004460100 313 | <= 019000 314 | 315 | => e02005460100 316 | <= 6ce8899226740d75f585f2f9523e72b8ea15ac74ff1ae0cc27285a1c9441671c010000000000067f2af0002bed1821ecfbb415788b74a5e36587199000 317 | 318 | => e02101011600058000002c800001ad800000000000000000000000 319 | <= 879000 320 | 321 | => e02110870700010000000003 322 | <= 9000 323 | 324 | => e02112873f6ce8899226740d75f585f2f9523e72b8ea15ac74ff1ae0cc27285a1c9441671c010000000000067f2af0002bed1821ecfbb415788b74a5e365871900000000 325 | <= 9000 326 | 327 | => e0211587150000000005f5e100000000240013d0e80000000001 328 | <= 9000 329 | 330 | => e021188715058000002c800001ad800000000000000000000000 331 | <= 9000 332 | 333 | => e0211a870100 334 | <= 9000 335 | 336 | => e021158715000000000010c8e0000000690013d0e80000000001 337 | <= 9000 338 | 339 | => e021178700 340 | <= 9000 341 | 342 | => e0211a870100 343 | <= 9000 344 | 345 | => e0211587150000000000788110000000240013d0e80000000001 346 | <= 9000 347 | 348 | => e021188715058000002c800001ad800000000000000000000000 349 | <= 9000 350 | 351 | => e0211a870100 352 | <= 9000 353 | 354 | => e021208700 355 | <= 445a29676b4d463b9a4266ae42d5d046e6610e936347d6ae0a22c8f539f6ed6077777e9478f501e657eabc6b8cefd406c00a50b80840a5fe9000 356 | `, 357 | tx: { 358 | inputs: [ 359 | { 360 | txId: "8e73d69748e76867f9e71351481137bc6d5e671979d050f61eabfdccee28a513", 361 | index: 1, 362 | value: "108997360", 363 | ergoTree: Buffer.from([ 364 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 365 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 366 | 45 367 | ]), 368 | creationHeight: 1298593, 369 | tokens: [], 370 | additionalRegisters: Buffer.from([0]), 371 | extension: Buffer.from([0]), 372 | signPath: "m/44'/429'/0'/0/0" 373 | } 374 | ], 375 | dataInputs: [], 376 | outputs: [ 377 | { 378 | value: "100000000", 379 | ergoTree: Buffer.from([ 380 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 381 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 382 | 45 383 | ]), 384 | creationHeight: 1298664, 385 | tokens: [], 386 | registers: Buffer.from([0]) 387 | }, 388 | { 389 | value: "1100000", 390 | ergoTree: Buffer.from([ 391 | 16, 5, 4, 0, 4, 0, 14, 54, 16, 2, 4, 160, 11, 8, 205, 2, 121, 190, 102, 126, 392 | 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 393 | 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 234, 2, 209, 146, 163, 154, 394 | 140, 199, 167, 1, 115, 0, 115, 1, 16, 1, 2, 4, 2, 209, 150, 131, 3, 1, 147, 395 | 163, 140, 199, 178, 165, 115, 0, 0, 1, 147, 194, 178, 165, 115, 1, 0, 116, 396 | 115, 2, 115, 3, 131, 1, 8, 205, 238, 172, 147, 177, 165, 115, 4 397 | ]), 398 | creationHeight: 1298664, 399 | tokens: [], 400 | registers: Buffer.from([0]) 401 | }, 402 | { 403 | value: "7897360", 404 | ergoTree: Buffer.from([ 405 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 406 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 407 | 45 408 | ]), 409 | creationHeight: 1298664, 410 | tokens: [], 411 | registers: Buffer.from([0]) 412 | } 413 | ], 414 | distinctTokenIds: [], 415 | changeMap: { 416 | address: "9fmmpNtxpYe5rFB5MAb4v86FFvTXby5jykoLM6XtGyqwEmFK4io", 417 | path: "m/44'/429'/0'/0/0" 418 | } 419 | }, 420 | proofs: [ 421 | Uint8Array.from([ 422 | 68, 90, 41, 103, 107, 77, 70, 59, 154, 66, 102, 174, 66, 213, 208, 70, 230, 97, 423 | 14, 147, 99, 71, 214, 174, 10, 34, 200, 245, 57, 246, 237, 96, 119, 119, 126, 148, 424 | 120, 245, 1, 230, 87, 234, 188, 107, 140, 239, 212, 6, 192, 10, 80, 184, 8, 64, 425 | 165, 254 426 | ]) 427 | ] 428 | }, 429 | { 430 | name: "simple p2p transaction with tokens", 431 | authToken: Number("0x68771637"), 432 | apduQueue: ` 433 | => e02001023b27c4d6a5a5e883282a5a1246975a1b42df78aa270638c8d843d63c14fae7a31f000b0000000000009ee8000000240013d08c010000000168771637 434 | <= aa9000 435 | 436 | => e02002aa240008cd02a51a0c5e6b456c2c8e71f238dc02f5345aad9a7c5b8c655dd24bc5e419c4212d 437 | <= 9000 438 | 439 | => e02003aa2800b1e236b60b95c2c6f8007a9d89bc460fc9e78f98b09faec9449007b40bccf30000000000002710 440 | <= 9000 441 | 442 | => e02004aa0100 443 | <= 019000 444 | 445 | => e02005aa0100 446 | <= 8038d412c4a62b7bb868ea5b1736a942613cb054f7ab5486885d157585c84ea001000000000000009ee80100b1e236b60b95c2c6f8007a9d89bc460fc9e78f98b09faec9449007b40bccf300000000000027100d7c4914d25595506ca5c2721d78350c9000 447 | 448 | => e02001023b27c4d6a5a5e883282a5a1246975a1b42df78aa270638c8d843d63c14fae7a31f00010000000006a01dc8000000240013d08c000000000168771637 449 | <= 179000 450 | 451 | => e0200217240008cd02a51a0c5e6b456c2c8e71f238dc02f5345aad9a7c5b8c655dd24bc5e419c4212d 452 | <= 9000 453 | 454 | => e02004170100 455 | <= 019000 456 | 457 | => e02005170100 458 | <= a377165e1cee26898a9baf470fc523ebf150f1936ab1a6033578a750ad5d6b8d01000000000006a01dc800fafe902998c7baee95696bffb6f3e5439000 459 | 460 | => e02101021a00058000002c800001ad80000000000000000000000068771637 461 | <= f19000 462 | 463 | => e02110f10700020000010003 464 | <= 9000 465 | 466 | => e02111f12000b1e236b60b95c2c6f8007a9d89bc460fc9e78f98b09faec9449007b40bccf3 467 | <= 9000 468 | 469 | => e02112f1678038d412c4a62b7bb868ea5b1736a942613cb054f7ab5486885d157585c84ea001000000000000009ee80100b1e236b60b95c2c6f8007a9d89bc460fc9e78f98b09faec9449007b40bccf300000000000027100d7c4914d25595506ca5c2721d78350c00000000 470 | <= 9000 471 | 472 | => e02112f13fa377165e1cee26898a9baf470fc523ebf150f1936ab1a6033578a750ad5d6b8d01000000000006a01dc800fafe902998c7baee95696bffb6f3e54300000000 473 | <= 9000 474 | 475 | => e02115f11500000000000f4240000000240013d0920100000001 476 | <= 9000 477 | 478 | => e02116f1240008cd0353daf95a91956229972514e2f491fdf1f9e2ed4793f916829ee427d023b51b01 479 | <= 9000 480 | 481 | => e02119f10c000000000000000000002710 482 | <= 9000 483 | 484 | => e0211af10100 485 | <= 9000 486 | 487 | => e02115f115000000000010c8e0000000690013d0920000000001 488 | <= 9000 489 | 490 | => e02117f100 491 | <= 9000 492 | 493 | => e0211af10100 494 | <= 9000 495 | 496 | => e02115f115000000000680b190000000240013d0920000000001 497 | <= 9000 498 | 499 | => e02118f115058000002c800001ad800000000000000000000000 500 | <= 9000 501 | 502 | => e0211af10100 503 | <= 9000 504 | 505 | => e02120f100 506 | <= b34353bca479fe5aed04025ebd47deb3d2fc252b166f701207416420fb1a8800056615910b1cac1b56eaa70b6f3721a55b8f9c9d6d6a5ab09000 507 | `, 508 | tx: { 509 | inputs: [ 510 | { 511 | txId: "27c4d6a5a5e883282a5a1246975a1b42df78aa270638c8d843d63c14fae7a31f", 512 | index: 11, 513 | value: "40680", 514 | ergoTree: Buffer.from([ 515 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 516 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 517 | 45 518 | ]), 519 | creationHeight: 1298572, 520 | tokens: [ 521 | { 522 | id: "00b1e236b60b95c2c6f8007a9d89bc460fc9e78f98b09faec9449007b40bccf3", 523 | amount: "10000" 524 | } 525 | ], 526 | additionalRegisters: Buffer.from([0]), 527 | extension: Buffer.from([0]), 528 | signPath: "m/44'/429'/0'/0/0" 529 | }, 530 | { 531 | txId: "27c4d6a5a5e883282a5a1246975a1b42df78aa270638c8d843d63c14fae7a31f", 532 | index: 1, 533 | value: "111156680", 534 | ergoTree: Buffer.from([ 535 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 536 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 537 | 45 538 | ]), 539 | creationHeight: 1298572, 540 | tokens: [], 541 | additionalRegisters: Buffer.from([0]), 542 | extension: Buffer.from([0]), 543 | signPath: "m/44'/429'/0'/0/0" 544 | } 545 | ], 546 | dataInputs: [], 547 | outputs: [ 548 | { 549 | value: "1000000", 550 | ergoTree: Buffer.from([ 551 | 0, 8, 205, 3, 83, 218, 249, 90, 145, 149, 98, 41, 151, 37, 20, 226, 244, 145, 552 | 253, 241, 249, 226, 237, 71, 147, 249, 22, 130, 158, 228, 39, 208, 35, 181, 553 | 27, 1 554 | ]), 555 | creationHeight: 1298578, 556 | tokens: [ 557 | { 558 | id: "00b1e236b60b95c2c6f8007a9d89bc460fc9e78f98b09faec9449007b40bccf3", 559 | amount: "10000" 560 | } 561 | ], 562 | registers: Buffer.from([0]) 563 | }, 564 | { 565 | value: "1100000", 566 | ergoTree: Buffer.from([ 567 | 16, 5, 4, 0, 4, 0, 14, 54, 16, 2, 4, 160, 11, 8, 205, 2, 121, 190, 102, 126, 568 | 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 569 | 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 234, 2, 209, 146, 163, 154, 570 | 140, 199, 167, 1, 115, 0, 115, 1, 16, 1, 2, 4, 2, 209, 150, 131, 3, 1, 147, 571 | 163, 140, 199, 178, 165, 115, 0, 0, 1, 147, 194, 178, 165, 115, 1, 0, 116, 572 | 115, 2, 115, 3, 131, 1, 8, 205, 238, 172, 147, 177, 165, 115, 4 573 | ]), 574 | creationHeight: 1298578, 575 | tokens: [], 576 | registers: Buffer.from([0]) 577 | }, 578 | { 579 | value: "109097360", 580 | ergoTree: Buffer.from([ 581 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 582 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 583 | 45 584 | ]), 585 | creationHeight: 1298578, 586 | tokens: [], 587 | registers: Buffer.from([0]) 588 | } 589 | ], 590 | distinctTokenIds: [ 591 | Uint8Array.from([ 592 | 0, 177, 226, 54, 182, 11, 149, 194, 198, 248, 0, 122, 157, 137, 188, 70, 15, 593 | 201, 231, 143, 152, 176, 159, 174, 201, 68, 144, 7, 180, 11, 204, 243 594 | ]) 595 | ], 596 | changeMap: { 597 | address: "9fmmpNtxpYe5rFB5MAb4v86FFvTXby5jykoLM6XtGyqwEmFK4io", 598 | path: "m/44'/429'/0'/0/0" 599 | } 600 | }, 601 | proofs: [ 602 | Uint8Array.from([ 603 | 179, 67, 83, 188, 164, 121, 254, 90, 237, 4, 2, 94, 189, 71, 222, 179, 210, 252, 604 | 37, 43, 22, 111, 112, 18, 7, 65, 100, 32, 251, 26, 136, 0, 5, 102, 21, 145, 11, 605 | 28, 172, 27, 86, 234, 167, 11, 111, 55, 33, 165, 91, 143, 156, 157, 109, 106, 90, 606 | 176 607 | ]), 608 | Uint8Array.from([ 609 | 179, 67, 83, 188, 164, 121, 254, 90, 237, 4, 2, 94, 189, 71, 222, 179, 210, 252, 610 | 37, 43, 22, 111, 112, 18, 7, 65, 100, 32, 251, 26, 136, 0, 5, 102, 21, 145, 11, 611 | 28, 172, 27, 86, 234, 167, 11, 111, 55, 33, 165, 91, 143, 156, 157, 109, 106, 90, 612 | 176 613 | ]) 614 | ] 615 | }, 616 | { 617 | name: "transaction with input extension and data inputs", 618 | authToken: 0, 619 | apduQueue: ` 620 | => e0200101375f083bdf25ef9b915205e569c3c0623c5b7ae743a6d26112704cfd8ae1261555000100000001c235c8c0000000c200128d98010000004a 621 | <= f09000 622 | 623 | => e02002f0c2100604000e20d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de4130400040005000500d803d601e30004d602e4c6a70408d603e4c6a7050595e67201d804d604b2a5e4720100d605b2db63087204730000d606db6308a7d60799c1a7c17204d1968302019683050193c27204c2a7938c720501730193e4c672040408720293e4c672040505720393e4c67204060ec5a796830201929c998c7205029591b1720673028cb272067303000273047203720792720773057202 624 | <= 9000 625 | 626 | => e02003f028d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de41300000000000004e8 627 | <= 9000 628 | 629 | => e02004f04a0308cd020f45695a0b93a631884a2cd0716a72522323678528522b315c54d6cf9c604cd705e0d1040e20dbaafd3269dd0d332c990da519a48a75024e0c8bc881a019b1c13ffa5708e61f 630 | <= 019000 631 | 632 | => e02005f00100 633 | <= 8864306054980d41709f4cedd886f06c9edd2d0f59a14afa91e2656077d803f2010000000001c235c8c001d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de41300000000000004e89b12099035cea03108ef10fc55b6dd3d9000 634 | 635 | => e0200101378e73d69748e76867f9e71351481137bc6d5e671979d050f61eabfdccee28a5130012000000000000a320000000240013d0a10100000001 636 | <= dd9000 637 | 638 | => e02002dd240008cd02a51a0c5e6b456c2c8e71f238dc02f5345aad9a7c5b8c655dd24bc5e419c4212d 639 | <= 9000 640 | 641 | => e02003dd28ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12b000000003b9aca00 642 | <= 9000 643 | 644 | => e02004dd0100 645 | <= 019000 646 | 647 | => e02005dd0100 648 | <= 3032130427fbf960d8a656fe390812cd1892b2822c4908f85df2216ec0ca15a70100000000000000a32001ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12b000000003b9aca004e74bdc55599b11c10adbecf07f28bb49000 649 | 650 | => e0200101378e73d69748e76867f9e71351481137bc6d5e671979d050f61eabfdccee28a51300050000000000009d80000000240013d0a10100000001 651 | <= 979000 652 | 653 | => e0200297240008cd02a51a0c5e6b456c2c8e71f238dc02f5345aad9a7c5b8c655dd24bc5e419c4212d 654 | <= 9000 655 | 656 | => e020039728d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de4130000000000000064 657 | <= 9000 658 | 659 | => e02004970100 660 | <= 019000 661 | 662 | => e02005970100 663 | <= 6196ae3283e0939b97b11bcd3c4900902d476b2c244b9fbb79f27765dc2a803b01000000000000009d8001d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de41300000000000000642ff3a8da556b580c45216f09c0a823869000 664 | 665 | => e0200101377eb901e0e7a3b2c2a4795851fc40c58b71a31ba114dedacf467eda3d3902f09e00000000000005f5e100000000240013d0e80000000001 666 | <= b09000 667 | 668 | => e02002b0240008cd02a51a0c5e6b456c2c8e71f238dc02f5345aad9a7c5b8c655dd24bc5e419c4212d 669 | <= 9000 670 | 671 | => e02004b00100 672 | <= 019000 673 | 674 | => e02005b00100 675 | <= 2bb2f3111e33ad9d9ab1fa3ae184fc1f06a6ac5a71d40a825997f6637b44784f01000000000005f5e1000073b3067b4874e9c13f2c72135ab3c43a9000 676 | 677 | => e02101011600058000002c800001ad800000000000000000000000 678 | <= bf9000 679 | 680 | => e02110bf0700040004020005 681 | <= 9000 682 | 683 | => e02111bf40ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12bd71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de413 684 | <= 9000 685 | 686 | => e02112bf678864306054980d41709f4cedd886f06c9edd2d0f59a14afa91e2656077d803f2010000000001c235c8c001d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de41300000000000004e89b12099035cea03108ef10fc55b6dd3d00000004 687 | <= 9000 688 | 689 | => e02113bf0401000402 690 | <= 9000 691 | 692 | => e02112bf673032130427fbf960d8a656fe390812cd1892b2822c4908f85df2216ec0ca15a70100000000000000a32001ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12b000000003b9aca004e74bdc55599b11c10adbecf07f28bb400000000 693 | <= 9000 694 | 695 | => e02112bf676196ae3283e0939b97b11bcd3c4900902d476b2c244b9fbb79f27765dc2a803b01000000000000009d8001d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de41300000000000000642ff3a8da556b580c45216f09c0a8238600000000 696 | <= 9000 697 | 698 | => e02112bf3f2bb2f3111e33ad9d9ab1fa3ae184fc1f06a6ac5a71d40a825997f6637b44784f01000000000005f5e1000073b3067b4874e9c13f2c72135ab3c43a00000000 699 | <= 9000 700 | 701 | => e02114bf808864306054980d41709f4cedd886f06c9edd2d0f59a14afa91e2656077d803f23032130427fbf960d8a656fe390812cd1892b2822c4908f85df2216ec0ca15a76196ae3283e0939b97b11bcd3c4900902d476b2c244b9fbb79f27765dc2a803b2bb2f3111e33ad9d9ab1fa3ae184fc1f06a6ac5a71d40a825997f6637b44784f 702 | <= 9000 703 | 704 | => e02115bf150000000000989680000000240013d0f70100000001 705 | <= 9000 706 | 707 | => e02118bf15058000002c800001ad800000000000000000000000 708 | <= 9000 709 | 710 | => e02119bf0c00000000000000003b9aca00 711 | <= 9000 712 | 713 | => e0211abf0100 714 | <= 9000 715 | 716 | => e02115bf1500000001c2142760000000c20013d0f7010000004a 717 | <= 9000 718 | 719 | => e02116bfc2100604000e20d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de4130400040005000500d803d601e30004d602e4c6a70408d603e4c6a7050595e67201d804d604b2a5e4720100d605b2db63087204730000d606db6308a7d60799c1a7c17204d1968302019683050193c27204c2a7938c720501730193e4c672040408720293e4c672040505720393e4c67204060ec5a796830201929c998c7205029591b1720673028cb272067303000273047203720792720773057202 720 | <= 9000 721 | 722 | => e02119bf0c000000010000000000000522 723 | <= 9000 724 | 725 | => e0211abf4a0308cd020f45695a0b93a631884a2cd0716a72522323678528522b315c54d6cf9c604cd705e0d1040e208864306054980d41709f4cedd886f06c9edd2d0f59a14afa91e2656077d803f2 726 | <= 9000 727 | 728 | => e02115bf15000000000021a160000000690013d0f70000000001 729 | <= 9000 730 | 731 | => e02117bf00 732 | <= 9000 733 | 734 | => e0211abf0100 735 | <= 9000 736 | 737 | => e02115bf1500000000055deda0000000240013d0f70000000001 738 | <= 9000 739 | 740 | => e02118bf15058000002c800001ad800000000000000000000000 741 | <= 9000 742 | 743 | => e0211abf0100 744 | <= 9000 745 | 746 | => e02115bf150000000000009d80000000240013d0f70100000001 747 | <= 9000 748 | 749 | => e02118bf15058000002c800001ad800000000000000000000000 750 | <= 9000 751 | 752 | => e02119bf0c00000001000000000000002a 753 | <= 9000 754 | 755 | => e0211abf0100 756 | <= 9000 757 | 758 | => e02120bf00 759 | <= e400970489548477054e2d9dacaeccb2c145f9e319ebca3e5abbb4be73fa0402c5868d7dfa68adffe9b4c0863d4ace3c3ca1f790815aed339000 760 | `, 761 | tx: { 762 | inputs: [ 763 | { 764 | txId: "5f083bdf25ef9b915205e569c3c0623c5b7ae743a6d26112704cfd8ae1261555", 765 | index: 1, 766 | value: "7553272000", 767 | ergoTree: Buffer.from([ 768 | 16, 6, 4, 0, 14, 32, 215, 22, 147, 196, 154, 132, 251, 190, 205, 73, 8, 201, 769 | 72, 19, 180, 101, 20, 177, 139, 103, 169, 153, 82, 220, 30, 110, 71, 145, 85, 770 | 109, 228, 19, 4, 0, 4, 0, 5, 0, 5, 0, 216, 3, 214, 1, 227, 0, 4, 214, 2, 228, 771 | 198, 167, 4, 8, 214, 3, 228, 198, 167, 5, 5, 149, 230, 114, 1, 216, 4, 214, 4, 772 | 178, 165, 228, 114, 1, 0, 214, 5, 178, 219, 99, 8, 114, 4, 115, 0, 0, 214, 6, 773 | 219, 99, 8, 167, 214, 7, 153, 193, 167, 193, 114, 4, 209, 150, 131, 2, 1, 150, 774 | 131, 5, 1, 147, 194, 114, 4, 194, 167, 147, 140, 114, 5, 1, 115, 1, 147, 228, 775 | 198, 114, 4, 4, 8, 114, 2, 147, 228, 198, 114, 4, 5, 5, 114, 3, 147, 228, 198, 776 | 114, 4, 6, 14, 197, 167, 150, 131, 2, 1, 146, 156, 153, 140, 114, 5, 2, 149, 777 | 145, 177, 114, 6, 115, 2, 140, 178, 114, 6, 115, 3, 0, 2, 115, 4, 114, 3, 114, 778 | 7, 146, 114, 7, 115, 5, 114, 2 779 | ]), 780 | creationHeight: 1215896, 781 | tokens: [ 782 | { 783 | id: "d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de413", 784 | amount: "1256" 785 | } 786 | ], 787 | additionalRegisters: Buffer.from([ 788 | 3, 8, 205, 2, 15, 69, 105, 90, 11, 147, 166, 49, 136, 74, 44, 208, 113, 106, 789 | 114, 82, 35, 35, 103, 133, 40, 82, 43, 49, 92, 84, 214, 207, 156, 96, 76, 215, 790 | 5, 224, 209, 4, 14, 32, 219, 170, 253, 50, 105, 221, 13, 51, 44, 153, 13, 165, 791 | 25, 164, 138, 117, 2, 78, 12, 139, 200, 129, 160, 25, 177, 193, 63, 250, 87, 792 | 8, 230, 31 793 | ]), 794 | extension: Buffer.from([1, 0, 4, 2]), 795 | signPath: "m/44'/429'/0'/0/0" 796 | }, 797 | { 798 | txId: "8e73d69748e76867f9e71351481137bc6d5e671979d050f61eabfdccee28a513", 799 | index: 18, 800 | value: "41760", 801 | ergoTree: Buffer.from([ 802 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 803 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 804 | 45 805 | ]), 806 | creationHeight: 1298593, 807 | tokens: [ 808 | { 809 | id: "ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12b", 810 | amount: "1000000000" 811 | } 812 | ], 813 | additionalRegisters: Buffer.from([0]), 814 | extension: Buffer.from([0]), 815 | signPath: "m/44'/429'/0'/0/0" 816 | }, 817 | { 818 | txId: "8e73d69748e76867f9e71351481137bc6d5e671979d050f61eabfdccee28a513", 819 | index: 5, 820 | value: "40320", 821 | ergoTree: Buffer.from([ 822 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 823 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 824 | 45 825 | ]), 826 | creationHeight: 1298593, 827 | tokens: [ 828 | { 829 | id: "d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de413", 830 | amount: "100" 831 | } 832 | ], 833 | additionalRegisters: Buffer.from([0]), 834 | extension: Buffer.from([0]), 835 | signPath: "m/44'/429'/0'/0/0" 836 | }, 837 | { 838 | txId: "7eb901e0e7a3b2c2a4795851fc40c58b71a31ba114dedacf467eda3d3902f09e", 839 | index: 0, 840 | value: "100000000", 841 | ergoTree: Buffer.from([ 842 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 843 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 844 | 45 845 | ]), 846 | creationHeight: 1298664, 847 | tokens: [], 848 | additionalRegisters: Buffer.from([0]), 849 | extension: Buffer.from([0]), 850 | signPath: "m/44'/429'/0'/0/0" 851 | } 852 | ], 853 | dataInputs: [ 854 | "8864306054980d41709f4cedd886f06c9edd2d0f59a14afa91e2656077d803f2", 855 | "3032130427fbf960d8a656fe390812cd1892b2822c4908f85df2216ec0ca15a7", 856 | "6196ae3283e0939b97b11bcd3c4900902d476b2c244b9fbb79f27765dc2a803b", 857 | "2bb2f3111e33ad9d9ab1fa3ae184fc1f06a6ac5a71d40a825997f6637b44784f" 858 | ], 859 | outputs: [ 860 | { 861 | value: "10000000", 862 | ergoTree: Buffer.from([ 863 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 864 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 865 | 45 866 | ]), 867 | creationHeight: 1298679, 868 | tokens: [ 869 | { 870 | id: "ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12b", 871 | amount: "1000000000" 872 | } 873 | ], 874 | registers: Buffer.from([0]) 875 | }, 876 | { 877 | value: "7551068000", 878 | ergoTree: Buffer.from([ 879 | 16, 6, 4, 0, 14, 32, 215, 22, 147, 196, 154, 132, 251, 190, 205, 73, 8, 201, 880 | 72, 19, 180, 101, 20, 177, 139, 103, 169, 153, 82, 220, 30, 110, 71, 145, 85, 881 | 109, 228, 19, 4, 0, 4, 0, 5, 0, 5, 0, 216, 3, 214, 1, 227, 0, 4, 214, 2, 228, 882 | 198, 167, 4, 8, 214, 3, 228, 198, 167, 5, 5, 149, 230, 114, 1, 216, 4, 214, 4, 883 | 178, 165, 228, 114, 1, 0, 214, 5, 178, 219, 99, 8, 114, 4, 115, 0, 0, 214, 6, 884 | 219, 99, 8, 167, 214, 7, 153, 193, 167, 193, 114, 4, 209, 150, 131, 2, 1, 150, 885 | 131, 5, 1, 147, 194, 114, 4, 194, 167, 147, 140, 114, 5, 1, 115, 1, 147, 228, 886 | 198, 114, 4, 4, 8, 114, 2, 147, 228, 198, 114, 4, 5, 5, 114, 3, 147, 228, 198, 887 | 114, 4, 6, 14, 197, 167, 150, 131, 2, 1, 146, 156, 153, 140, 114, 5, 2, 149, 888 | 145, 177, 114, 6, 115, 2, 140, 178, 114, 6, 115, 3, 0, 2, 115, 4, 114, 3, 114, 889 | 7, 146, 114, 7, 115, 5, 114, 2 890 | ]), 891 | creationHeight: 1298679, 892 | tokens: [ 893 | { 894 | id: "d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de413", 895 | amount: "1314" 896 | } 897 | ], 898 | registers: Buffer.from([ 899 | 3, 8, 205, 2, 15, 69, 105, 90, 11, 147, 166, 49, 136, 74, 44, 208, 113, 106, 900 | 114, 82, 35, 35, 103, 133, 40, 82, 43, 49, 92, 84, 214, 207, 156, 96, 76, 215, 901 | 5, 224, 209, 4, 14, 32, 136, 100, 48, 96, 84, 152, 13, 65, 112, 159, 76, 237, 902 | 216, 134, 240, 108, 158, 221, 45, 15, 89, 161, 74, 250, 145, 226, 101, 96, 903 | 119, 216, 3, 242 904 | ]) 905 | }, 906 | { 907 | value: "2204000", 908 | ergoTree: Buffer.from([ 909 | 16, 5, 4, 0, 4, 0, 14, 54, 16, 2, 4, 160, 11, 8, 205, 2, 121, 190, 102, 126, 910 | 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 911 | 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 234, 2, 209, 146, 163, 154, 912 | 140, 199, 167, 1, 115, 0, 115, 1, 16, 1, 2, 4, 2, 209, 150, 131, 3, 1, 147, 913 | 163, 140, 199, 178, 165, 115, 0, 0, 1, 147, 194, 178, 165, 115, 1, 0, 116, 914 | 115, 2, 115, 3, 131, 1, 8, 205, 238, 172, 147, 177, 165, 115, 4 915 | ]), 916 | creationHeight: 1298679, 917 | tokens: [], 918 | registers: Buffer.from([0]) 919 | }, 920 | { 921 | value: "90041760", 922 | ergoTree: Buffer.from([ 923 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 924 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 925 | 45 926 | ]), 927 | creationHeight: 1298679, 928 | tokens: [], 929 | registers: Buffer.from([0]) 930 | }, 931 | { 932 | value: "40320", 933 | ergoTree: Buffer.from([ 934 | 0, 8, 205, 2, 165, 26, 12, 94, 107, 69, 108, 44, 142, 113, 242, 56, 220, 2, 935 | 245, 52, 90, 173, 154, 124, 91, 140, 101, 93, 210, 75, 197, 228, 25, 196, 33, 936 | 45 937 | ]), 938 | creationHeight: 1298679, 939 | tokens: [ 940 | { 941 | id: "d71693c49a84fbbecd4908c94813b46514b18b67a99952dc1e6e4791556de413", 942 | amount: "42" 943 | } 944 | ], 945 | registers: Buffer.from([0]) 946 | } 947 | ], 948 | distinctTokenIds: [ 949 | Uint8Array.from([ 950 | 239, 128, 43, 71, 92, 6, 24, 159, 219, 248, 68, 21, 60, 220, 29, 68, 154, 91, 951 | 168, 124, 206, 19, 209, 27, 180, 123, 90, 83, 159, 39, 241, 43 952 | ]), 953 | Uint8Array.from([ 954 | 215, 22, 147, 196, 154, 132, 251, 190, 205, 73, 8, 201, 72, 19, 180, 101, 20, 955 | 177, 139, 103, 169, 153, 82, 220, 30, 110, 71, 145, 85, 109, 228, 19 956 | ]) 957 | ], 958 | changeMap: { 959 | address: "9fmmpNtxpYe5rFB5MAb4v86FFvTXby5jykoLM6XtGyqwEmFK4io", 960 | path: "m/44'/429'/0'/0/0" 961 | } 962 | }, 963 | proofs: [ 964 | Uint8Array.from([ 965 | 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, 193, 69, 966 | 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, 197, 134, 141, 125, 967 | 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, 60, 60, 161, 247, 144, 129, 968 | 90, 237, 51 969 | ]), 970 | Uint8Array.from([ 971 | 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, 193, 69, 972 | 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, 197, 134, 141, 125, 973 | 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, 60, 60, 161, 247, 144, 129, 974 | 90, 237, 51 975 | ]), 976 | Uint8Array.from([ 977 | 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, 193, 69, 978 | 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, 197, 134, 141, 125, 979 | 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, 60, 60, 161, 247, 144, 129, 980 | 90, 237, 51 981 | ]), 982 | Uint8Array.from([ 983 | 228, 0, 151, 4, 137, 84, 132, 119, 5, 78, 45, 157, 172, 174, 204, 178, 193, 69, 984 | 249, 227, 25, 235, 202, 62, 90, 187, 180, 190, 115, 250, 4, 2, 197, 134, 141, 125, 985 | 250, 104, 173, 255, 233, 180, 192, 134, 61, 74, 206, 60, 60, 161, 247, 144, 129, 986 | 90, 237, 51 987 | ]) 988 | ] 989 | } 990 | ]; 991 | -------------------------------------------------------------------------------- /src/erg.ts: -------------------------------------------------------------------------------- 1 | import { Network, uniq } from "@fleet-sdk/common"; 2 | import type Transport from "@ledgerhq/hw-transport"; 3 | import { Device, DeviceError, RETURN_CODE } from "./device"; 4 | import { 5 | attestInput, 6 | deriveAddress, 7 | getAppName, 8 | getAppVersion, 9 | getExtendedPublicKey, 10 | showAddress, 11 | signTx 12 | } from "./commands/app"; 13 | import type { AttestedBox } from "./types/attestedBox"; 14 | import type { AttestedTransaction, SignTransactionResponse } from "./types/internal"; 15 | import type { 16 | AppName, 17 | DerivedAddress, 18 | ExtendedPublicKey, 19 | UnsignedBox, 20 | UnsignedTransaction, 21 | Version 22 | } from "./types/public"; 23 | 24 | export * from "./types/public"; 25 | export { DeviceError, Network, RETURN_CODE, Device }; 26 | 27 | /** 28 | * Ergo's Ledger hardware wallet API 29 | */ 30 | export class ErgoLedgerApp { 31 | #device: Device; 32 | #authToken: number; 33 | #useAuthToken: boolean; 34 | 35 | get authToken(): number | undefined { 36 | return this.#useAuthToken ? this.#authToken : undefined; 37 | } 38 | 39 | get device(): Device { 40 | return this.#device; 41 | } 42 | 43 | constructor(transport: Transport); 44 | constructor(transport: Transport, authToken: number); 45 | constructor(transport: Transport, authToken: number, scrambleKey: string); 46 | constructor(transport: Transport, authToken?: number, scrambleKey = "ERG") { 47 | transport.decorateAppAPIMethods( 48 | this, 49 | [ 50 | "getAppVersion", 51 | "getAppName", 52 | "getExtendedPublicKey", 53 | "deriveAddress", 54 | "showAddress", 55 | "attestInput", 56 | "signTx", 57 | "openApp", 58 | "closeApp", 59 | "getCurrentAppInfo" 60 | ], 61 | scrambleKey 62 | ); 63 | 64 | this.#device = new Device(transport); 65 | this.#authToken = !authToken ? this.#newAuthToken() : authToken; 66 | this.#useAuthToken = true; 67 | } 68 | 69 | /** 70 | * Use authorization token to keep session opened. 71 | * @param use 72 | * @returns 73 | */ 74 | useAuthToken(use = true): ErgoLedgerApp { 75 | this.#useAuthToken = use; 76 | return this; 77 | } 78 | 79 | /** 80 | * Enable or disable logging. 81 | * @param enable 82 | * @returns 83 | */ 84 | useLogging(enable = true): ErgoLedgerApp { 85 | this.#device.enableLogging(enable); 86 | return this; 87 | } 88 | 89 | #newAuthToken(): number { 90 | let newToken = 0; 91 | do { 92 | newToken = Math.floor(Math.random() * 0xffffffff) + 1; 93 | } while (newToken === this.#authToken); 94 | 95 | return newToken; 96 | } 97 | 98 | /** 99 | * Get application version. 100 | * @returns Promise with the Ledger Application version. 101 | */ 102 | async getAppVersion(): Promise { 103 | return getAppVersion(this.#device); 104 | } 105 | 106 | /** 107 | * Get application name. 108 | * @returns Promise with the Ledger Application name. 109 | */ 110 | async getAppName(): Promise { 111 | return getAppName(this.#device); 112 | } 113 | 114 | /** 115 | * Get the extended public key. 116 | * @param path BIP32 path. 117 | * @returns a Promise with the **chain code** and the **public key** for provided BIP32 path. 118 | */ 119 | async getExtendedPublicKey(path: string): Promise { 120 | return getExtendedPublicKey(this.#device, path, this.authToken); 121 | } 122 | 123 | /** 124 | * Derive the address for a given Bip44 path. 125 | * @param path Bip44 path. 126 | * @returns a Promise with the derived address in hex format. 127 | */ 128 | async deriveAddress(path: string, network = Network.Mainnet): Promise { 129 | return deriveAddress(this.#device, network, path, this.authToken); 130 | } 131 | 132 | /** 133 | * Derive and show the address on device screen for a given Bip44 path. 134 | * @param path Bip44 path. 135 | * @returns a Promise with true if the user accepts or throws an exception if it get rejected. 136 | */ 137 | async showAddress(path: string, network = Network.Mainnet): Promise { 138 | return showAddress(this.#device, network, path, this.authToken); 139 | } 140 | 141 | async attestInput(box: UnsignedBox): Promise { 142 | return this.#attestInput(box); 143 | } 144 | 145 | async #attestInput(box: UnsignedBox): Promise { 146 | return attestInput(this.#device, box, this.authToken); 147 | } 148 | 149 | async signTx( 150 | tx: UnsignedTransaction, 151 | network = Network.Mainnet 152 | ): Promise { 153 | if (!tx.inputs || tx.inputs.length === 0) { 154 | throw new DeviceError(RETURN_CODE.BAD_INPUT_COUNT); 155 | } 156 | 157 | const attestedInputs = await this.#attestInputs(tx.inputs); 158 | const signPaths = uniq(tx.inputs.map((i) => i.signPath)); 159 | const attestedTx: AttestedTransaction = { 160 | inputs: attestedInputs, 161 | dataInputs: tx.dataInputs, 162 | outputs: tx.outputs, 163 | distinctTokenIds: tx.distinctTokenIds, 164 | changeMap: tx.changeMap 165 | }; 166 | 167 | const signatures: SignTransactionResponse = {}; 168 | for (const path of signPaths) { 169 | signatures[path] = await signTx( 170 | this.#device, 171 | attestedTx, 172 | path, 173 | network, 174 | this.authToken 175 | ); 176 | } 177 | 178 | const signBytes: Uint8Array[] = []; 179 | for (const input of tx.inputs) signBytes.push(signatures[input.signPath]); 180 | 181 | return signBytes; 182 | } 183 | 184 | async #attestInputs(inputs: UnsignedBox[]): Promise { 185 | const attestedBoxes: AttestedBox[] = []; 186 | for (const box of inputs) { 187 | const attestedBox = await this.#attestInput(box); 188 | attestedBox.setExtension(box.extension); 189 | attestedBoxes.push(attestedBox); 190 | } 191 | 192 | return attestedBoxes; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/serialization/byteWriter.ts: -------------------------------------------------------------------------------- 1 | import { assert, ensureBigInt } from "@fleet-sdk/common"; 2 | import { hex } from "@fleet-sdk/crypto"; 3 | import { isErgoPath, pathToArray } from "./utils"; 4 | 5 | export class ByteWriter { 6 | readonly #buffer: Buffer; 7 | #offset: number; 8 | 9 | constructor(length: number) { 10 | this.#buffer = Buffer.alloc(length); 11 | this.#offset = 0; 12 | } 13 | 14 | write(byte: number): ByteWriter { 15 | this.#buffer[this.#offset++] = byte; 16 | return this; 17 | } 18 | 19 | writeUInt32(value: number): ByteWriter { 20 | this.#offset = this.#buffer.writeUInt32BE(value, this.#offset); 21 | return this; 22 | } 23 | 24 | writeUInt8(value: number): ByteWriter { 25 | this.#offset = this.#buffer.writeUInt8(value, this.#offset); 26 | return this; 27 | } 28 | 29 | writeUInt16(value: number): ByteWriter { 30 | this.#offset = this.#buffer.writeUInt16BE(value, this.#offset); 31 | return this; 32 | } 33 | 34 | writeUInt64(value: string | bigint): ByteWriter { 35 | const data = ensureBigInt(value); 36 | this.#offset = this.#buffer.writeBigUInt64BE(data, this.#offset); 37 | return this; 38 | } 39 | 40 | writeBytes(bytes: ArrayLike): ByteWriter { 41 | this.#buffer.set(bytes, this.#offset); 42 | this.#offset += bytes.length; 43 | return this; 44 | } 45 | 46 | writeHex(bytesHex: string): ByteWriter { 47 | return this.writeBytes(hex.decode(bytesHex)); 48 | } 49 | 50 | writePath(path: string): ByteWriter { 51 | const pathArray = pathToArray(path); 52 | assert(isErgoPath(pathArray), "Invalid Ergo path"); 53 | 54 | this.write(pathArray.length); 55 | for (const index of pathArray) this.writeUInt32(index); 56 | return this; 57 | } 58 | 59 | writeAuthToken(authToken?: number): ByteWriter { 60 | if (!authToken) return this; 61 | return this.writeUInt32(authToken); 62 | } 63 | 64 | toBytes(): Uint8Array { 65 | return this.#buffer.subarray(0, this.#offset); 66 | } 67 | 68 | toBuffer(): Buffer { 69 | return this.#buffer.slice(0, this.#offset); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/serialization/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { isErgoPath, pathToArray } from "./utils"; 3 | 4 | describe("assertions", () => { 5 | it("Ergo path", () => { 6 | expect(isErgoPath(pathToArray("m/44'/429'"))).to.be.true; 7 | expect(isErgoPath(pathToArray("m/44'/2'"))).to.be.false; 8 | expect(isErgoPath(pathToArray("m/44'"))).to.be.false; 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/serialization/utils.ts: -------------------------------------------------------------------------------- 1 | import bip32Path from "bip32-path"; 2 | 3 | export const EMPTY_BYTES = Uint8Array.from([]); 4 | export const NO_VALUE = 0x00; 5 | const [ERGO_PURPOSE, ERGO_COIN_TYPE] = bip32Path.fromString("m/44'/429'").toPathArray(); 6 | 7 | export function pathToArray(path: string): number[] { 8 | return bip32Path.fromString(path).toPathArray(); 9 | } 10 | 11 | export function isErgoPath(path: number[]): boolean { 12 | if (path.length < 2) return false; 13 | const [pathPurpose, pathCoinType] = path; 14 | return pathPurpose === ERGO_PURPOSE && pathCoinType === ERGO_COIN_TYPE; 15 | } 16 | 17 | export const asciiCodec = { 18 | encode: (str: string): Uint8Array => { 19 | const bytes = new Uint8Array(str.length); 20 | for (let i = 0; i < str.length; i++) { 21 | bytes[i] = str.charCodeAt(i); 22 | } 23 | 24 | return bytes; 25 | }, 26 | decode: (bytes: Uint8Array): string => String.fromCharCode(...bytes) 27 | }; 28 | -------------------------------------------------------------------------------- /src/types/attestedBox.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "@fleet-sdk/common"; 2 | import { ByteWriter } from "../serialization/byteWriter"; 3 | import type { AttestedBoxFrame, UnsignedBox } from "./public"; 4 | 5 | export class AttestedBox { 6 | #box: UnsignedBox; 7 | #frames: AttestedBoxFrame[]; 8 | #extension?: Uint8Array; 9 | 10 | constructor(box: UnsignedBox, frames: AttestedBoxFrame[]) { 11 | this.#box = box; 12 | this.#frames = frames; 13 | } 14 | 15 | public get box(): UnsignedBox { 16 | return this.#box; 17 | } 18 | 19 | public get frames(): AttestedBoxFrame[] { 20 | return this.#frames; 21 | } 22 | 23 | public get extension(): Uint8Array | undefined { 24 | return this.#extension; 25 | } 26 | 27 | public setExtension(extension: Uint8Array): AttestedBox { 28 | assert(!this.#extension, "The extension is already inserted"); 29 | 30 | const firstFrame = this.#frames[0]; 31 | const length = firstFrame.bytes.length + 4; 32 | const newFrame = new ByteWriter(length).writeBytes(firstFrame.bytes); 33 | 34 | if (extension.length === 1 && extension[0] === 0) { 35 | newFrame.writeUInt32(0); 36 | } else { 37 | this.#extension = extension; 38 | firstFrame.extensionLength = extension.length; 39 | newFrame.writeUInt32(extension.length); 40 | } 41 | 42 | firstFrame.bytes = newFrame.toBytes(); 43 | 44 | return this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/types/bip32-path.d.ts: -------------------------------------------------------------------------------- 1 | declare module "bip32-path"; 2 | -------------------------------------------------------------------------------- /src/types/internal.ts: -------------------------------------------------------------------------------- 1 | import type { RETURN_CODE } from "../device"; 2 | import type { AttestedBox } from "./attestedBox"; 3 | import type { BoxCandidate, ChangeMap } from "./public"; 4 | 5 | export type DeviceResponse = { 6 | data: Buffer; 7 | returnCode: RETURN_CODE; 8 | }; 9 | 10 | export type AttestedTransaction = { 11 | inputs: AttestedBox[]; 12 | dataInputs: string[]; 13 | outputs: BoxCandidate[]; 14 | distinctTokenIds: Uint8Array[]; 15 | changeMap: ChangeMap; 16 | }; 17 | 18 | export type SignTransactionResponse = { 19 | [path: string]: Uint8Array; 20 | }; 21 | -------------------------------------------------------------------------------- /src/types/public.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Device app flags 3 | */ 4 | export type Flags = { 5 | isDebug: boolean; 6 | }; 7 | 8 | /** 9 | * Device app version 10 | */ 11 | export type Version = { 12 | major: number; 13 | minor: number; 14 | patch: number; 15 | flags: Flags; 16 | }; 17 | 18 | /** 19 | * Device app name 20 | */ 21 | export type AppName = { 22 | name: string; 23 | }; 24 | 25 | /** 26 | * Extended public key in hex format 27 | */ 28 | export type ExtendedPublicKey = { 29 | publicKey: string; 30 | chainCode: string; 31 | }; 32 | 33 | /** 34 | * Response to [[ErgoApp.deriveAddress]] call in hex format 35 | */ 36 | export type DerivedAddress = { 37 | addressHex: string; 38 | }; 39 | 40 | export type Token = { 41 | id: string; 42 | amount: string; 43 | }; 44 | 45 | export type UnsignedBox = { 46 | txId: string; 47 | index: number; 48 | value: string; 49 | ergoTree: Uint8Array; 50 | creationHeight: number; 51 | tokens: Token[]; 52 | additionalRegisters: Uint8Array; 53 | extension: Uint8Array; 54 | signPath: string; 55 | }; 56 | 57 | export type BoxCandidate = { 58 | value: string; 59 | ergoTree: Uint8Array; 60 | creationHeight: number; 61 | tokens: Token[]; 62 | registers: Uint8Array; 63 | }; 64 | 65 | export type AttestedBoxFrame = { 66 | boxId: string; 67 | count: number; 68 | index: number; 69 | amount: string; 70 | tokens: Token[]; 71 | attestation: string; 72 | extensionLength?: number; 73 | bytes: Uint8Array; 74 | }; 75 | 76 | export type UnsignedTransaction = { 77 | inputs: UnsignedBox[]; 78 | dataInputs: string[]; 79 | outputs: BoxCandidate[]; 80 | distinctTokenIds: Uint8Array[]; 81 | changeMap: ChangeMap; 82 | }; 83 | 84 | export type ChangeMap = { 85 | address: string; 86 | path: string; 87 | }; 88 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "moduleResolution": "Node", 5 | "module": "ESNext", 6 | "strict": true, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "esModuleInterop": true, 10 | "importHelpers": true, 11 | "baseUrl": "." 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["src/erg.ts"], 5 | outDir: "./dist", 6 | splitting: false, 7 | treeshake: true, 8 | sourcemap: true, 9 | clean: true, 10 | dts: { resolve: false }, 11 | format: ["esm", "cjs"] 12 | }); 13 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | coverage: { 6 | thresholds: { 7 | "100": true 8 | }, 9 | all: true, 10 | provider: "v8", 11 | reporter: ["text", "json", "html"], 12 | exclude: [ 13 | "**/*.spec.ts", 14 | "**/*.d.ts", 15 | "**/*.bench.ts", 16 | "**/*.test-d.ts", 17 | "**/*.test.ts", 18 | "*.config.ts", 19 | "dist/**", 20 | "coverage/**", 21 | "node_modules/**" 22 | ] 23 | } 24 | } 25 | }); 26 | --------------------------------------------------------------------------------