From c514632113e3aa66e436fdf1b97f172603a38fd2 Mon Sep 17 00:00:00 2001 From: janniks Date: Mon, 2 Oct 2023 11:02:37 +0200 Subject: [PATCH] feat: wip --- lerna.json | 2 +- package-lock.json | 1023 ++++++++++++++++++++- packages/sbtc/README.md | 668 +------------- packages/sbtc/jest.config.js | 3 - packages/sbtc/package.json | 10 +- packages/sbtc/src/index.ts | 40 +- packages/sbtc/src/transactions/api.ts | 130 +++ packages/sbtc/src/transactions/deposit.ts | 484 +++++----- packages/sbtc/src/transactions/index.ts | 3 + packages/sbtc/tests/deposit.test.ts | 61 ++ packages/sbtc/tsconfig.build.json | 7 +- packages/sbtc/webpack.config.js | 2 +- vitest.config.ts | 7 + 13 files changed, 1434 insertions(+), 1006 deletions(-) delete mode 100644 packages/sbtc/jest.config.js create mode 100644 packages/sbtc/src/transactions/api.ts create mode 100644 packages/sbtc/src/transactions/index.ts create mode 100644 packages/sbtc/tests/deposit.test.ts create mode 100644 vitest.config.ts diff --git a/lerna.json b/lerna.json index 7b1ffa8ff..cf46a9187 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "6.7.0", + "version": "independent", "registry": "https://registry.npmjs.org/", "publishConfig": { "access": "public" diff --git a/package-lock.json b/package-lock.json index ba51c4ab6..bc6a3a0c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4449,7 +4449,7 @@ "resolved": "packages/profile", "link": true }, - "node_modules/@stacks/sbtc": { + "node_modules/@stacks/sbtc-developer-release": { "resolved": "packages/sbtc", "link": true }, @@ -4621,6 +4621,21 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", + "dev": true + }, + "node_modules/@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, "node_modules/@types/common-tags": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.1.tgz", @@ -5189,6 +5204,101 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vitest/expect": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", + "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", + "dev": true, + "dependencies": { + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", + "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "0.34.6", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", + "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", + "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", + "dev": true, + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", + "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -5910,6 +6020,15 @@ "node": ">=0.8" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/ast-module-types": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-4.0.0.tgz", @@ -6775,6 +6894,15 @@ "node": ">=8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/cacache": { "version": "17.1.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.3.tgz", @@ -6937,6 +7065,24 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, + "node_modules/chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -6964,6 +7110,18 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/cheerio": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", @@ -8484,6 +8642,18 @@ } } }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -10913,6 +11083,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -15861,6 +16040,18 @@ "node": ">=8.9.0" } }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -16111,6 +16302,15 @@ "triple-beam": "^1.3.0" } }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -16244,6 +16444,18 @@ "node": ">=8" } }, + "node_modules/magic-string": { + "version": "0.30.4", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", + "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -16924,6 +17136,18 @@ "node": ">=10" } }, + "node_modules/mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -18674,6 +18898,21 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -18783,6 +19022,17 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -20291,6 +20541,22 @@ "node": ">=8" } }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/run-applescript": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", @@ -20668,6 +20934,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -21219,6 +21491,12 @@ "node": ">=8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -21227,6 +21505,12 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.4.3.tgz", + "integrity": "sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==", + "dev": true + }, "node_modules/stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -21458,6 +21742,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/strong-log-transformer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", @@ -21810,6 +22106,30 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, + "node_modules/tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.1.1.tgz", + "integrity": "sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -22420,6 +22740,12 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.1.tgz", + "integrity": "sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==", + "dev": true + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -22683,57 +23009,601 @@ "extsprintf": "^1.2.0" } }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", - "dev": true - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "node_modules/vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", "dev": true, "dependencies": { - "browser-process-hrtime": "^1.0.0" + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } } }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "node_modules/vite-node": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", + "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", "dev": true, "dependencies": { - "xml-name-validator": "^3.0.0" + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" }, "engines": { - "node": ">=10" + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/walkdir": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", - "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.0.0" + "node": ">=12" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/vitest": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", + "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } }, "node_modules/watchpack": { "version": "2.4.0", @@ -23122,6 +23992,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -23954,21 +24840,37 @@ } }, "packages/sbtc": { - "name": "@stacks/sbtc", - "version": "6.7.0", + "name": "@stacks/sbtc-developer-release", + "version": "0.1.0", "license": "MIT", "dependencies": { "@noble/secp256k1": "^2.0.0", "@scure/base": "^1.1.3", "@scure/btc-signer": "^1.1.0", + "@stacks/common": "^6.7.0", "@stacks/encryption": "^6.7.0", "@stacks/transactions": "^6.7.0", "c32check": "^2.0.0", "micro-packed": "^0.3.2" }, "devDependencies": { + "@scure/bip32": "^1.3.2", + "@scure/bip39": "^1.2.1", "jest-fetch-mock": "^3.0.3", - "rimraf": "^3.0.2" + "rimraf": "^3.0.2", + "vitest": "^0.34.6" + } + }, + "packages/sbtc/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "dev": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "packages/sbtc/node_modules/@noble/secp256k1": { @@ -23990,6 +24892,47 @@ "url": "https://paulmillr.com/funding/" } }, + "packages/sbtc/node_modules/@scure/bip32": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", + "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", + "dev": true, + "dependencies": { + "@noble/curves": "~1.2.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/sbtc/node_modules/@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dev": true, + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "packages/sbtc/node_modules/@stacks/common": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@stacks/common/-/common-6.8.1.tgz", + "integrity": "sha512-ewL9GLZNQYa5a/3K4xSHlHIgHkD4rwWW/QEaPId8zQIaL+1O9qCaF4LX9orNQeOmEk8kvG0x2xGV54fXKCZeWQ==", + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/node": "^18.0.4" + } + }, + "packages/sbtc/node_modules/@types/node": { + "version": "18.18.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.1.tgz", + "integrity": "sha512-3G42sxmm0fF2+Vtb9TJQpnjmP+uKlWvFa8KoEGquh4gqRmoUG/N0ufuhikw6HEsdG2G2oIKhog1GCTfz9v5NdQ==" + }, "packages/sbtc/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", diff --git a/packages/sbtc/README.md b/packages/sbtc/README.md index 112d912fe..e50464f39 100644 --- a/packages/sbtc/README.md +++ b/packages/sbtc/README.md @@ -1,667 +1 @@ -# @stacks/stacking [![npm](https://img.shields.io/npm/v/@stacks/stacking?color=red)](https://www.npmjs.com/package/@stacks/stacking) - -Library for PoX Stacking. - -> **Note**: [Not all methods](https://stacks.js.org/classes/_stacks_stacking.StackingClient#:~:text=methods%20-%20pox-2) are available before the 2.1 fork. These will throw if used on a <2.1 chain. - -## Installation - -```shell -npm install @stacks/stacking -``` - -- [Initialization](#initialization) -- [Stack STX](#stack-stx) - - [Check stacking eligibility](#check-stacking-eligibility) - - [Broadcast the stacking transaction](#broadcast-the-stacking-transaction) - - [Extend stacking](#extend-stacking) - - [Increase amount stacked](#increase-amount-stacked) -- [Client helpers](#client-helpers) - - [Will Stacking be executed in the next cycle?](#will-stacking-be-executed-in-the-next-cycle) - - [How long (in seconds) is a Stacking cycle?](#how-long-in-seconds-is-a-stacking-cycle) - - [How much estimated time is left (in seconds) to submit a stacking transaction for the upcoming reward cycle?](#how-much-estimated-time-is-left-in-seconds-to-submit-a-stacking-transaction-for-the-upcoming-reward-cycle) - - [Does account have sufficient STX to meet minimum threshold?](#does-account-have-sufficient-stx-to-meet-minimum-threshold) - - [Get PoX info](#get-pox-info) - - [Get Stacks node info](#get-stacks-node-info) - - [Get account balance](#get-account-balance) - - [Get account balance locked](#get-account-balance-locked) - - [Get account balances (from API)](#get-account-balances-from-api) - - [Get account stacking status](#get-account-stacking-status) - - [Get PoX operation info (current period and PoX contract versions)](#get-pox-operation-info-current-period-and-pox-contract-versions) -- [Delegated stacking](#delegated-stacking) - - [Stacking in a pool](#stacking-in-a-pool) - - [Delegate STX](#delegate-stx) - - [Revoke delegation](#revoke-delegation) - - [Operating a pool / Stacking for others](#operating-a-pool--stacking-for-others) - - [Stack delegated STX](#stack-delegated-stx) - - [Extend delegated STX](#extend-delegated-stx) - - [Increase delegated STX](#increase-delegated-stx) - - [Commit to stacking](#commit-to-stacking) - - [Increase existing commitment](#increase-existing-commitment) - - [Pool helpers](#pool-helpers) - - [Get burnchain rewards](#get-burnchain-rewards) - - [Get burnchain rewards total](#get-burnchain-rewards-total) - - [Get burnchain reward holders](#get-burnchain-reward-holders) - - [Get reward set by index](#get-reward-set-by-index) - -## Initialization - -Initialize a `StackingClient` to interact with the Stacking contract. - -> **Note:** The `StackingClient` sets its transactions `AnchorMode` to `Any` by default. - -```typescript -import { StacksTestnet, StacksMainnet } from '@stacks/network'; -import { StackingClient } from '@stacks/stacking'; - -// for mainnet: const network = new StacksMainnet(); -const network = new StacksTestnet(); -// the stacks STX address -const address = 'ST3XKKN4RPV69NN1PHFDNX3TYKXT7XPC4N8KC1ARH'; -const client = new StackingClient(address, network); -``` - -## Stack STX - -### Check stacking eligibility - -```typescript -// a BTC address for reward payouts -const poxAddress = 'mvuYDknzDtPgGqm2GnbAbmGMLwiyW3AwFP'; -// number cycles to stack -const cycles = 3; - -// Refer to initialization section to create client instance -const stackingEligibility = await client.canStack({ poxAddress, cycles }); - -// { -// eligible: false, -// reason: 'ERR_STACKING_INVALID_LOCK_PERIOD', -// } -``` - -### Broadcast the stacking transaction - -```typescript -// a BTC address for reward payouts -const poxAddress = 'mvuYDknzDtPgGqm2GnbAbmGMLwiyW3AwFP'; -// number cycles to stack -const cycles = 3; -// how much to stack, in microSTX -const amountMicroStx = 100000000000n; -// private key for transaction signing -const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; -// block height at which to stack -const burnBlockHeight = 2000; - -// Refer to initialization section to create client instance -const stackingResults = await client.stack({ - amountMicroStx, - poxAddress, - cycles, - privateKey, - burnBlockHeight, -}); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -### Extend stacking - -Extends previously stacked funds without cooldown. - -```typescript -// number cycles to extend stacking by -const extendCycles = 3; -// a BTC address for reward payouts -const poxAddress = 'mvuYDknzDtPgGqm2GnbAbmGMLwiyW3AwFP'; -// private key for transaction signing -const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; - -const extendResults = await client.stackExtend({ - extendCycles, - poxAddress, - privateKey, -}); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -### Increase amount stacked - -Increases the amount of funds stacked/locked after previously stacking. - -```typescript -// how much to increase by, in microSTX -const increaseBy = 3000000; -// private key for transaction signing -const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; - -const increaseResults = await client.stackIncrease({ - increaseBy, - privateKey, -}); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -## Client helpers - -### Will Stacking be executed in the next cycle? - -```typescript -const stackingEnabledNextCycle = await client.isStackingEnabledNextCycle(); - -// true / false -``` - -### How long (in seconds) is a Stacking cycle? - -```typescript -const cycleDuration = await client.getCycleDuration(); - -// 120 -``` - -### How much estimated time is left (in seconds) to submit a stacking transaction for the upcoming reward cycle? - -```typescript -const seconds = await client.getSecondsUntilStackingDeadline(); - -// 600000 -``` - -### Does account have sufficient STX to meet minimum threshold? - -```typescript -const hasMinStxAmount = await client.hasMinimumStx(); - -// true / false -``` - -### Get PoX info - -```typescript -const poxInfo = await client.getPoxInfo(); - -// { -// contract_id: 'ST000000000000000000002AMW42H.pox', -// first_burnchain_block_height: 0, -// min_amount_ustx: 83335083333333, -// prepare_cycle_length: 30, -// rejection_fraction: 3333333333333333, -// reward_cycle_id: 17, -// reward_cycle_length: 120, -// rejection_votes_left_required: 0, -// total_liquid_supply_ustx: 40000840000000000 -// } -``` - -### Get Stacks node info - -```typescript -const coreInfo = await client.getCoreInfo(); - -// { -// peer_version: 385875968, -// pox_consensus: 'bb88a6e6e65fa7c974d3f6e91a941d05cc3dff8e', -// burn_block_height: 2133, -// stable_pox_consensus: '2284451c3e623237def1f8caed1c11fa46b6f0cc', -// stable_burn_block_height: 2132, -// server_version: 'blockstack-core 0.0.1 => 23.0.0.0 (HEAD:a4deb7a+, release build, linux [x86_64])', -// network_id: 2147483648, -// parent_network_id: 3669344250, -// stacks_tip_height: 1797, -// stacks_tip: '016df36c6a154cb6114c469a28cc0ce8b415a7af0527f13f15e66e27aa480f94', -// stacks_tip_consensus_hash: 'bb88a6e6e65fa7c974d3f6e91a941d05cc3dff8e', -// unanchored_tip: '6b93d2c62fc07cf44302d4928211944d2debf476e5c71fb725fb298a037323cc', -// exit_at_block_height: null -// } -``` - -### Get account balance - -```typescript -const responseBalanceInfo = await client.getAccountBalance(); - -// 800000000000 -``` - -### Get account balance locked - -```typescript -const responseBalanceLockedInfo = await client.getAccountBalanceLocked(); - -// 40000000000 -``` - -### Get account balances (from API) - -```typescript -const responseBalancesInfo = await client.getAccountExtendedBalances(); - -// { -// stx: { -// balance: '1000000', -// total_sent: '0', -// total_received: '1000000', -// lock_tx_id: '0xec94e7d20af8979b44d17a0520c126bf742b999a0fc7ddbcbe0ab21b228ecc8c', -// locked: '50000', -// lock_height: 100, -// burnchain_lock_height: 100, -// burnchain_unlock_height: 200, -// }, -// fungible_tokens: {}, -// non_fungible_tokens: {}, -// } -``` - -### Get account stacking status - -```typescript -const stackingStatus = await client.getStatus(); - -// { -// stacked: true, -// details: { -// first_reward_cycle: 18, -// lock_period: 10, -// unlock_height: 3020, -// pox_address: { -// version: '00', -// hashbytes: '05cf52a44bf3e6829b4f8c221cc675355bf83b7d' -// } -// } -// } -``` - -### Get PoX operation info (current period and PoX contract versions) - -```typescript -const poxOperationInfo = await client.getPoxOperationInfo(); - -// { -// period: 'Period3', -// pox1: { -// contract_id: 'ST000000000000000000002AMW42H.pox', -// activation_burnchain_block_height: 0, -// first_reward_cycle_id: 0, -// }, -// pox2: { -// contract_id: 'ST000000000000000000002AMW42H.pox-2', -// activation_burnchain_block_height: 120, -// first_reward_cycle_id: 25, -// }, -// } -``` - -## Delegated stacking - -These are the methods for creating the required transactions for delegated stacking: - -- [Stacking in a pool](#stacking-in-a-pool) - - [`.delegateStx` Delegate STX](#delegate-stx) - - [`.revokeDelegateStx` Revoke delegation](#revoke-delegation) -- [Operating a pool](#operating-a-pool) - - [`.delegateStackStx` Stack delegated STX](#stack-delegated-stx) - - [`.stackAggregationCommit` Commit to stacking](#commit-to-stacking) - -```mermaid -sequenceDiagram - User->>Stacks Blockchain: tx: `delegateStx`
Delegate funds to pool,
by pool's STX address - Stacks Blockchain-->Pool Operator: Monitored by - Pool Operator->>Stacks Blockchain: tx: `delegateStackStx`
Lock delegated funds - Pool Operator->>Stacks Blockchain: tx: `stackAggregationCommit`
Commit stacking for each cycle -``` - -### Stacking in a pool - -If you are the account owner ("stacker"), you can delegate or revoke delegation rights. - -#### Delegate STX - -```typescript -// STX address of the pool/pool -const delegateTo = 'ST2MCYPWTFMD2MGR5YY695EJG0G1R4J2BTJPRGM7H'; -// burn height at which the delegation relationship should be revoked (optional) -const untilBurnBlockHeight = 5000; -// how much to stack, in microSTX -const amountMicroStx = 100000000000n; -// private key for transaction signing -const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; - -const delegetateResponse = await client.delegateStx({ - amountMicroStx, - delegateTo, - untilBurnBlockHeight, // optional - privateKey, -}); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -#### Revoke delegation - -```typescript -// private key for transaction signing -const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; - -const revokeResponse = await client.revokeDelegateStx(privateKey); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -### Operating a pool / Stacking for others - -If you are a pool operator (or wish to stack with someone else's funds), you can stack ("lock up") tokens for your users and commit to stacking participation for upcoming reward cycles. -These users need to first "delegate" some or all of their funds to you (the "delegator"). -The following examples refer to the "delegator" as pool, but in practice a delegator can also stack for only single or few individuals. -Even a group of friends could stack together and share a multi-sig BTC wallet for payouts. - -#### Stack delegated STX - -Stack STX, which have been previously delegated to the pool. -This step only locks the funds (partial stacking). -The pool operator will also need to ["commit"](#commit-to-stacking) to a reward cycle. - -```typescript -import { getNonce } from '@stacks/transactions'; -import { StacksTestnet, StacksMainnet } from '@stacks/network'; -import { StackingClient } from '@stacks/stacking'; - -// for mainnet: const network = new StacksMainnet(); -const network = new StacksTestnet(); -// the stacks STX address -const address = 'ST3XKKN4RPV69NN1PHFDNX3TYKXT7XPC4N8KC1ARH'; -// pools would initiate a different client -const poolAddress = 'ST22X605P0QX2BJC3NXEENXDPFCNJPHE02DTX5V74'; -// pool private key for transaction signing -const poolPrivateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; -// the BTC address for reward payouts -const poolBtcAddress = 'msiYwJCvXEzjgq6hDwD9ueBka6MTfN962Z'; -// how much to stack, in microSTX -const amountMicroStx = 100000000000n; -// block height at which to stack -const burnBlockHeight = 2000; -// number cycles to stack -const cycles = 3; -// if you call this method multiple times in the same block, you need to increase the nonce manually -let nonce = await getNonce(poolAddress, network); -nonce = nonce + 1n; - -const poolClient = new StackingClient(poolAddress, network); - -const delegetateStackResponses = await poolClient.delegateStackStx({ - stacker: address, - amountMicroStx, - poxAddress: poolBtcAddress, - burnBlockHeight, - cycles, - privateKey: poolPrivateKey, - nonce, // optional -}); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -#### Extend delegated STX - -Extend stacking of STX previously delegated to the pool. - -```typescript -import { getNonce } from '@stacks/transactions'; -import { StacksTestnet, StacksMainnet } from '@stacks/network'; -import { StackingClient } from '@stacks/stacking'; - -// for mainnet: const network = new StacksMainnet(); -const network = new StacksTestnet(); -// the stacks STX address -const address = 'ST3XKKN4RPV69NN1PHFDNX3TYKXT7XPC4N8KC1ARH'; -// pools would initiate a different client -const poolAddress = 'ST22X605P0QX2BJC3NXEENXDPFCNJPHE02DTX5V74'; -// pool private key for transaction signing -const poolPrivateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; -// the BTC address for reward payouts -const poolBtcAddress = 'msiYwJCvXEzjgq6hDwD9ueBka6MTfN962Z'; -// number of cycles to extend by -const extendCount = 3; -// if you call this method multiple times in the same block, you need to increase the nonce manually -let nonce = await getNonce(poolAddress, network); -nonce = nonce + 1n; - -const poolClient = new StackingClient(poolAddress, network); - -const delegetateExtendResponses = await poolClient.delegateStackExtend({ - extendCount, - stacker: address, - poxAddress: poolBtcAddress, - privateKey: poolPrivateKey, - nonce, // optional -}); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -#### Increase delegated STX - -Increase the locked amount of delegated STX stacked. - -```typescript -import { getNonce } from '@stacks/transactions'; -import { StacksTestnet, StacksMainnet } from '@stacks/network'; -import { StackingClient } from '@stacks/stacking'; - -// for mainnet: const network = new StacksMainnet(); -const network = new StacksTestnet(); -// the stacks STX address -const address = 'ST3XKKN4RPV69NN1PHFDNX3TYKXT7XPC4N8KC1ARH'; -// pools would initiate a different client -const poolAddress = 'ST22X605P0QX2BJC3NXEENXDPFCNJPHE02DTX5V74'; -// pool private key for transaction signing -const poolPrivateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; -// the BTC address for reward payouts -const poolBtcAddress = 'msiYwJCvXEzjgq6hDwD9ueBka6MTfN962Z'; -// amount to increase by, in microSTX -const increaseBy = 3; -// if you call this method multiple times in the same block, you need to increase the nonce manually -let nonce = await getNonce(poolAddress, network); -nonce = nonce + 1n; - -const poolClient = new StackingClient(poolAddress, network); - -const delegetateIncreaseResponses = await poolClient.delegateStackIncrease({ - increaseBy, - stacker: address, - poxAddress: poolBtcAddress, - privateKey: poolPrivateKey, - nonce, // optional -}); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -#### Commit to stacking - -The result of this commit transaction will contain the index of the pools reward set entry. - -```typescript -// reward cycle id to commit to -const rewardCycle = 12; -// the BTC address for reward payouts -const poolBtcAddress = 'msiYwJCvXEzjgq6hDwD9ueBka6MTfN962Z'; -// Private key -const privateKeyDelegate = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; - -const delegetateCommitResponse = await poolClient.stackAggregationCommitIndexed({ - rewardCycle, - poxAddress: poolBtcAddress, - privateKey: privateKeyDelegate, -}); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -#### Increase existing commitment - -The result of this commit transaction will contain the index of the pools reward set entry. - -```typescript -// reward cycle id to commit to -const rewardCycle = 12; -// reward set entry index -const rewardIndex = 3; -// the BTC address for reward payouts -const poolBtcAddress = 'msiYwJCvXEzjgq6hDwD9ueBka6MTfN962Z'; -// Private key -const privateKeyDelegate = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; - -const delegetateIncreaseResponse = await poolClient.stackAggregationIncrease({ - rewardCycle, - rewardIndex, - poxAddress: poolBtcAddress, - privateKey: privateKeyDelegate, -}); - -// { -// txid: '0xf6e9dbf6a26c1b73a14738606cb2232375d1b440246e6bbc14a45b3a66618481', -// } -``` - -#### Pool helpers - -##### Get burnchain rewards - -```typescript -import { StacksTestnet, StacksMainnet } from '@stacks/network'; -import { StackingClient } from '@stacks/stacking'; - -const address = 'myfTfju9XSMRusaY2qTitSEMSchsWRA441'; -// for mainnet: const network = new StacksMainnet(); -const network = new StacksTestnet(); -const client = new StackingClient(address, network); -const options = { limit: 2, offset: 0 }; - -const rewards = await client.getRewardsForBtcAddress(options); - -// { -// limit: 2, -// offset: 0, -// results: [ -// { -// canonical: true, -// burn_block_hash: '0x000000000000002083ca8303a2262d09a824cecb34b78f13a04787e4f05441d3', -// burn_block_height: 2004622, -// burn_amount: '0', -// reward_recipient: 'myfTfju9XSMRusaY2qTitSEMSchsWRA441', -// reward_amount: '20000', -// reward_index: 0 -// }, -// { -// canonical: true, -// burn_block_hash: '0x000000000000002f72213de621f9daf60d76aed3902a811561d06373b2fa6123', -// burn_block_height: 2004621, -// burn_amount: '0', -// reward_recipient: 'myfTfju9XSMRusaY2qTitSEMSchsWRA441', -// reward_amount: '20000', -// reward_index: 0 -// } -// ] -// }; -``` - -##### Get burnchain rewards total - -```typescript -import { StacksTestnet, StacksMainnet } from '@stacks/network'; -import { StackingClient } from '@stacks/stacking'; - -const address = 'myfTfju9XSMRusaY2qTitSEMSchsWRA441'; -// for mainnet: const network = new StacksMainnet(); -const network = new StacksTestnet(); -const client = new StackingClient(address, network); - -const total = await client.getRewardsTotalForBtcAddress(); -// { -// reward_recipient: 'myfTfju9XSMRusaY2qTitSEMSchsWRA441', -// reward_amount: '0' -// } -``` - -##### Get burnchain reward holders - -```typescript -import { StacksTestnet, StacksMainnet } from '@stacks/network'; -import { StackingClient } from '@stacks/stacking'; - -const address = 'myfTfju9XSMRusaY2qTitSEMSchsWRA441'; -// for mainnet: const network = new StacksMainnet(); -const network = new StacksTestnet(); -const client = new StackingClient(address, network); -const options = { limit: 2, offset: 0 }; - -const rewardHolders = await client.getRewardHoldersForBtcAddress(options); -// { -// limit: 2, -// offset: 0, -// total: 46, -// results: [ -// { -// canonical: true, -// burn_block_hash: '0x000000000000002083ca8303a2262d09a824cecb34b78f13a04787e4f05441d3', -// burn_block_height: 2004622, -// address: 'myfTfju9XSMRusaY2qTitSEMSchsWRA441', -// slot_index: 1 -// }, -// { -// canonical: true, -// burn_block_hash: '0x000000000000002083ca8303a2262d09a824cecb34b78f13a04787e4f05441d3', -// burn_block_height: 2004622, -// address: 'myfTfju9XSMRusaY2qTitSEMSchsWRA441', -// slot_index: 0 -// } -// ] -// }; -``` - -##### Get reward set by index - -```typescript -import { StacksTestnet, StacksMainnet } from '@stacks/network'; -import { StackingClient } from '@stacks/stacking'; - -const address = 'myfTfju9XSMRusaY2qTitSEMSchsWRA441'; -// for mainnet: const network = new StacksMainnet(); -const network = new StacksTestnet(); -const client = new StackingClient(address, network); - -const rewardSetItem = await client.getRewardSet({ - rewardCyleId: 49, - rewardSetIndex: 3, -}); - -// { -// pox_address: { -// version: 0, -// hashbytes: [ 67, 89, 107, 83, 134, 244, 102, 134, 62, 37, 101, 141, 223, 148, 189, 15, 173, 171, 0, 72 ] -// }, -// total_ustx: 1875230000000000 -// } -``` +# @stacks/sbtc-developer-release diff --git a/packages/sbtc/jest.config.js b/packages/sbtc/jest.config.js deleted file mode 100644 index fe8e05183..000000000 --- a/packages/sbtc/jest.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const makeJestConfig = require('../../configs/jestConfig'); - -module.exports = makeJestConfig(__dirname); diff --git a/packages/sbtc/package.json b/packages/sbtc/package.json index 79b77acbd..543978784 100644 --- a/packages/sbtc/package.json +++ b/packages/sbtc/package.json @@ -1,6 +1,6 @@ { - "name": "@stacks/sbtc", - "version": "6.7.0", + "name": "@stacks/sbtc-developer-release", + "version": "0.1.0", "description": "Library for sBTC.", "license": "MIT", "author": "Hiro Systems PBC (https://hiro.so)", @@ -23,14 +23,18 @@ "@noble/secp256k1": "^2.0.0", "@scure/base": "^1.1.3", "@scure/btc-signer": "^1.1.0", + "@stacks/common": "^6.7.0", "@stacks/encryption": "^6.7.0", "@stacks/transactions": "^6.7.0", "c32check": "^2.0.0", "micro-packed": "^0.3.2" }, "devDependencies": { + "@scure/bip32": "^1.3.2", + "@scure/bip39": "^1.2.1", "jest-fetch-mock": "^3.0.3", - "rimraf": "^3.0.2" + "rimraf": "^3.0.2", + "vitest": "^0.34.6" }, "sideEffects": false, "typings": "dist/index.d.ts", diff --git a/packages/sbtc/src/index.ts b/packages/sbtc/src/index.ts index 350e99d94..af47bf885 100644 --- a/packages/sbtc/src/index.ts +++ b/packages/sbtc/src/index.ts @@ -1,39 +1 @@ -// PLACEHOLDER FILE - -interface MintOptions { - amount: number; // (amount uint) - destination: string; // (destination principal) - depositTxid: string; // (deposit-txid (buff 32)) - burnChainHeight: number; // (burn-chain-height uint) - merkleProof: string[]; // (merkle-proof (list 14 (buff 32))) - txIndex: number; // (tx-index uint) - treeDepth: number; // (tree-depth uint) - blockHeader: string; // (block-header (buff 80))) -} - -export class sBTCClient { - constructor(public network: StacksNetwork) {} - - /** - - */ - async mint(options: MintOptions): Promise { - const [contractAddress, contractName] = this.parseContractId(options?.contractId); - const result = await callReadOnlyFunction({ - network: this.network, - senderAddress: this.address, - contractAddress, - contractName, - functionArgs: [uintCV(options.rewardCyleId), uintCV(options.rewardSetIndex)], - functionName: 'get-reward-set-pox-address', - }); - - return unwrapMap(result as OptionalCV, tuple => ({ - pox_address: { - version: ((tuple.data['pox-addr'] as TupleCV).data['version'] as BufferCV).buffer, - hashbytes: ((tuple.data['pox-addr'] as TupleCV).data['hashbytes'] as BufferCV).buffer, - }, - total_ustx: (tuple.data['total-ustx'] as UIntCV).value, - })); - } -} +export * from './transactions'; diff --git a/packages/sbtc/src/transactions/api.ts b/packages/sbtc/src/transactions/api.ts new file mode 100644 index 000000000..a8ef55055 --- /dev/null +++ b/packages/sbtc/src/transactions/api.ts @@ -0,0 +1,130 @@ +import * as btc from '@scure/btc-signer'; +import { Cl } from '@stacks/transactions'; + +// https://blockstream.info/api/address/1KFHE7w8BhaENAswwryaoccDb6qcT6DbYY/utxo +// [{"txid":"033e44b535c5709d30234921608219ee5ca1e320fa9def44715eaeb2b7ad52d3","vout":0,"status":{"confirmed":false},"value":42200}] +export type BlockstreamUtxo = { + txid: string; + vout: number; + value: number; + status: + | { + confirmed: false; + } + | { + confirmed: true; + block_height: number; + block_hash: string; + block_time: number; + }; +}; + +export type BlockstreamUtxoWithTxHex = BlockstreamUtxo & { + hex: string; +}; + +export async function fetchUtxos(address: string): Promise { + // todo: error handling? + return (await fetch(`https://blockstream.info/testnet/api/address/${address}/utxo`).then(res => + res.json() + )) as BlockstreamUtxo[]; +} + +export async function fetchTxHex(txid: string): Promise { + // todo: error handling? + return await fetch(`https://blockstream.info/testnet/api/tx/${txid}/hex`).then(res => res.text()); +} + +type BlockstreamFeeEstimates = { + [K in + | '1' + | '2' + | '3' + | '4' + | '5' + | '6' + | '7' + | '8' + | '9' + | '10' + | '11' + | '12' + | '13' + | '14' + | '15' + | '16' + | '17' + | '18' + | '19' + | '20' + | '21' + | '22' + | '23' + | '24' + | '25' + | '144' + | '504' + | '1008']: number; +}; + +export async function estimateFeeRates(): Promise { + return await fetch(`https://blockstream.info/testnet/api/fee-estimates`).then(res => res.json()); +} + +export async function estimateFeeRate(target: 'low' | 'medium' | 'high' | number): Promise { + const feeEstimates = await estimateFeeRates(); + const t = + typeof target === 'number' + ? target.toString() + : target === 'high' + ? '1' + : target === 'medium' + ? '2' + : '3'; + if (t in feeEstimates) { + return feeEstimates[t as keyof BlockstreamFeeEstimates]; + } + + throw new Error(`Invalid fee target: ${target}`); +} + +export async function broadcastTx(tx: btc.Transaction): Promise { + return await fetch(`https://blockstream.info/testnet/api/tx`, { + method: 'POST', + body: tx.hex, + }).then(res => res.text()); +} + +export async function stacksCallReadOnly({ + contractAddress, + functionName, + sender = 'ST000000000000000000002AMW42H', + args = [], +}: { + contractAddress: string; + functionName: string; + sender?: string; + args?: string[]; +}) { + contractAddress = contractAddress.replace('.', '/'); + return await fetch( + `https://api.testnet.hiro.so/v2/contracts/call-read/${contractAddress}/${encodeURIComponent( + functionName + )}`, + { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ sender, arguments: args }), + } + ) + .then(res => res.json()) + .then(res => Cl.deserialize(res.result)); +} + +// export async function informBridgeApi(txid: string) { +// // todo +// return 'txid'; +// } diff --git a/packages/sbtc/src/transactions/deposit.ts b/packages/sbtc/src/transactions/deposit.ts index 0bd68ff5a..8bceccca6 100644 --- a/packages/sbtc/src/transactions/deposit.ts +++ b/packages/sbtc/src/transactions/deposit.ts @@ -1,15 +1,20 @@ import * as btc from '@scure/btc-signer'; -import * as secp from '@noble/secp256k1'; -import * as P from 'micro-packed'; -import { hex } from '@scure/base'; -import type { BridgeTransactionType, UTXO } from './types/sbtc_types.js'; -import { toStorable, buildDepositPayload, buildDepositPayloadOpReturn } from './payload_utils.js'; -import { addInputs, inputAmt } from './wallet_utils.js'; -import { MagicBytes as MagicBytes, MagicBytes, OPCodes, OpCode } from './constants.js'; +import { hexToBytes, intToHex, utf8ToBytes } from '@stacks/common'; import { c32addressDecode } from 'c32check'; +import * as P from 'micro-packed'; +import * as api from './api'; +import { BlockstreamUtxo, BlockstreamUtxoWithTxHex } from './api'; +import { MagicBytes, OpCode } from './constants'; + +// todo: move to constants? -export const REVEAL_PAYMENT = 10001; // todo: is this const? -export const DUST = 500; // todo: double-check what this has to be set to +// Estimates based on https://bitcoinops.org/en/tools/calc-size/ +const OVERHEAD_TX = 4 + 1 + 1 + 4; // new btc.Transaction().vsize +// const OVERHEAD_INPUT = 36 + 1 + 4 + 0.25; +// const OVERHEAD_OUTPUT = 8 + 1; +// const OVERHEAD_INPUT_P2PKH = 107; +const VSIZE_INPUT_P2WPKH = 68; +// const OVERHEAD_OUTPUT_P2PKH = 25; const concat = P.concatBytes; @@ -23,17 +28,6 @@ interface BitcoinNetwork { export const NETWORK: BitcoinNetwork = btc.NETWORK; export const TEST_NETWORK: BitcoinNetwork = btc.TEST_NETWORK; -interface DepositOpts { - network?: BitcoinNetwork; - amount: number; - btcFeeRates: any; - addressInfo: any; - stacksAddress: string; - sbtcWalletAddress: string; - cardinal: string; - userPaymentPubKey: string; -} - export function buildSBtcDepositBtcPayload({ network: net, address, @@ -42,269 +36,267 @@ export function buildSBtcDepositBtcPayload({ address: string; }): Uint8Array { const magicBytes = - net.bech32 === 'tb' ? hex.decode(MagicBytes.Testnet) : hex.decode(MagicBytes.Mainnet); - const opCodeBytes = hex.decode(OpCode.PegIn); + net.bech32 === 'tb' ? hexToBytes(MagicBytes.Testnet) : hexToBytes(MagicBytes.Mainnet); + const opCodeBytes = hexToBytes(OpCode.PegIn); return concat(magicBytes, opCodeBytes, stacksAddressBytes(address)); } function stacksAddressBytes(address: string): Uint8Array { const [addr, contractName] = address.split('.'); const [version, hash] = c32addressDecode(addr); - const versionBytes = hex.decode(version.toString(16)); - const hashBytes = hex.decode(hash); + const versionBytes = hexToBytes(version.toString(16)); + const hashBytes = hexToBytes(hash); + const contractNameBytes = lengthPrefixedString(contractName, utf8ToBytes); - return concat(versionBytes, hashBytes, lpContractNameBytes(contractName)); + return concat(versionBytes, hashBytes, contractNameBytes); } -function lpContractNameBytes(contractName?: string): Uint8Array { - if (!contractName) return Uint8Array.from([0]); // empty +// todo: move to utils somewhere +function lengthPrefixedString( + something: string | null | undefined, + map: (something: string) => Uint8Array = utf8ToBytes, + maxByteLength: number = 40, + prefixByteLength: number = 1 +): Uint8Array { + if (!something) return Uint8Array.from([0]); // empty or nullish (optional) + + const bytes = map(something); + if (maxByteLength >= 0 && bytes.byteLength > maxByteLength) + throw new RangeError(`Content byteLength exceeds maximum length of ${maxByteLength}`); - const cnameBuf = new TextEncoder().encode(contractName); - const cnameLen = cnameBuf.byteLength; - if (cnameBuf.length > 40) throw new Error('Contract name is too long - max 40 characters'); - return concat(cnameLen, cnameBuf); + const prefixBytes = hexToBytes(intToHex(bytes.byteLength, prefixByteLength)); + if (prefixBytes.byteLength > prefixByteLength) + throw new RangeError(`Prefix byteLength exceeds maximum length of ${prefixByteLength}`); + + return concat(prefixBytes, bytes); } -function optionalLengthPrefixed( - something: T | null | undefined, - fn: (something: T) => Uint8Array, - maxLength?: number = -1 -): Uint8Array { - if (!something) return Uint8Array.from([0]); // empty +// todo: add p2sh for xverse +async function defaultUtxoToSpendable( + utxo: BlockstreamUtxo | BlockstreamUtxoWithTxHex +): Promise<{ input: btc.TransactionInput; vsize?: number }> { + const utxoWithTx: BlockstreamUtxoWithTxHex = + 'hex' in utxo ? utxo : { ...utxo, hex: await api.fetchTxHex(utxo.txid) }; + + const tx = btc.Transaction.fromRaw(hexToBytes(utxoWithTx.hex), { + allowUnknownOutputs: true, + allowUnknownInputs: true, + }); + + const outputToSpend = tx.getOutput(utxo.vout); + if (!outputToSpend?.script) throw new Error('No script found on utxo tx'); + const spendScript = btc.OutScript.decode(outputToSpend.script); + + try { + if (spendScript.type !== 'wpkh') throw new Error('Non-p2wpkh utxo found'); - const bytes = fn(something); - const length = bytes.byteLength; - if (maxLength >= 0 && bytes.length > maxLength) { - throw new Error(`ByteLength exceeds maximum length of ${maxLength}`); + const spendableInput: btc.TransactionInput = { + txid: hexToBytes(utxo.txid), + index: utxo.vout, + ...outputToSpend, + witnessUtxo: { + script: outputToSpend.script, + amount: BigInt(utxo.value), + }, + }; + new btc.Transaction().addInput(spendableInput); // validate, throws if invalid + return { input: spendableInput, vsize: VSIZE_INPUT_P2WPKH }; + } catch (e) { + throw new Error(`Utxo doesn't match spendable type, ${JSON.stringify(utxo)}`); } - return concat(hex.length, bytes); } +// todo: after DR? +// async function tryAllToSpendable( +// utxo: BlockstreamUtxo | BlockstreamUtxoWithTxHex +// ): Promise { +// const utxoWithTx: BlockstreamUtxoWithTxHex = +// 'hex' in utxo ? utxo : { ...utxo, hex: await fetchTxHex(utxo.txid) }; + +// const tx = btc.Transaction.fromRaw(hexToBytes(utxoWithTx.hex), { +// allowUnknownOutputs: true, +// allowUnknownInputs: true, +// }); + +// const outputToSpend = tx.getOutput(utxo.vout); +// if (!outputToSpend?.script) throw new Error('No script found on utxo tx'); +// const spendScript = btc.OutScript.decode(outputToSpend.script); + +// try { +// switch (spendScript.type) { +// case 'wpkh': +// // +// } +// } catch (e) { +// throw new Error(`Utxo doesn't match spendable type, ${JSON.stringify(utxo)}`); +// } +// } + +type UtxoToSpendableFn = ( + utxo: BlockstreamUtxo | BlockstreamUtxoWithTxHex +) => Promise<{ input: btc.TransactionInput; vsize?: number }>; + +export async function utxoSelect({ + utxos, + utxoToSpendable = defaultUtxoToSpendable, + outputs, + feeRate, +}: { + utxos: (BlockstreamUtxo | BlockstreamUtxoWithTxHex)[]; + utxoToSpendable?: UtxoToSpendableFn; + outputs: btc.TransactionOutput[]; + feeRate: number; +}): Promise<{ + inputs: btc.TransactionInput[]; + outputs: btc.TransactionOutput[]; + totalSats: bigint; + changeSats: bigint; +}> { + const outputsValue = outputs.reduce( + (acc: bigint, o: btc.TransactionOutput) => acc + (o.amount ?? 0n), + 0n + ); + + const inputs: btc.TransactionInput[] = []; // collect inputs + let inputRunning = 0n; + + let vsizeRunning = txBytes([], outputs); + + for (const utxo of utxos) { + try { + const { input, vsize } = await utxoToSpendable(utxo); + const inputVsize = vsize ?? inputBytes(input); + const utxoFee = feeRate * inputVsize; + + if (utxoFee > utxo.value) continue; // skip if utxo is too small to pay fee + + // add input + inputs.push(input); + inputRunning += BigInt(utxo.value); + vsizeRunning += inputVsize; + + // check if we have enough inputs + const fee = feeRate * vsizeRunning; + if (inputRunning >= outputsValue + BigInt(fee)) { + const changeSats = inputRunning - (outputsValue + BigInt(fee)); + return { inputs, outputs, totalSats: inputRunning, changeSats }; + } + } catch (e) { + continue; // skip if utxo is not spendable + } + } + + throw new Error('Not enough funds'); +} + +/** */ +export const buildSbtcDepositTx = buildSbtcDepositTxOpReturn; // default to OP RETURN for developer release + +const SBTC_PEG_ADDRESS = 'tb1q3tj2fr9scwmcw3rq5m6jslva65f2rqjxt2t0zh'; // todo: auto-fetch or hardcode if final /** * */ -export function buildOpReturnDepositTransaction({ - network = NETWORK, - amount, - btcFeeRates, - addressInfo, +export function buildSbtcDepositTxOpReturn({ + network = TEST_NETWORK, // default to testnet for developer release + amountSats, stacksAddress, - sbtcWalletAddress, - cardinal, - userPaymentPubKey, -}: DepositOpts) { - opts.network = opts.network ?? NETWORK; // mainnet by default - const data = buildDepositPayloadOpReturn(network, stacksAddress); - const txFees = calculateDepositFees( - network, - false, - amount, - btcFeeRates.feeInfo, - addressInfo, - sbtcWalletAddress, - data - ); + pegAddress = SBTC_PEG_ADDRESS, +}: { + network?: BitcoinNetwork; + amountSats: number; + stacksAddress: string; + pegAddress?: string; +}) { + const data = buildSBtcDepositBtcPayload({ network, address: stacksAddress }); + const tx = new btc.Transaction({ - allowUnknowInput: true, - allowUnknowOutput: true, + // todo: disbale unknown allowUnknownInputs: true, allowUnknownOutputs: true, }); - // no reveal fee for op_return - addInputs(network, amount, 0, tx, false, addressInfo.utxos, userPaymentPubKey); tx.addOutput({ script: btc.Script.encode(['RETURN', data]), amount: BigInt(0) }); - tx.addOutputAddress(sbtcWalletAddress, BigInt(amount), net); - const changeAmount = inputAmt(tx) - (amount + txFees[1]); - if (changeAmount > 0) tx.addOutputAddress(cardinal, BigInt(changeAmount), net); + tx.addOutputAddress(pegAddress, BigInt(amountSats), network); + return tx; } -/** - * @param network - * @param amount the amount to deposit plus the reveal transaction gas fee - * @param btcFeeRates current rates - * @param addressInfo the utxos to spend from - * @param commitTxAddress the commitment address - contains the taproot data and the payload - * @param cardinal the change address - * @param userPaymentPubKey pubkey needed to spend script hash inputs - * @returns transaction object - */ -export function buildOpDropDepositTransaction( - network: string, - amount: number, - btcFeeRates: any, - addressInfo: any, - commitTxAddress: string, - cardinal: string, - userPaymentPubKey: string -) { - const net = network === 'testnet' ? btc.TEST_NETWORK : btc.NETWORK; - const txFees = calculateDepositFees( - network, - true, - amount, - btcFeeRates.feeInfo, - addressInfo, - commitTxAddress, - undefined - ); - const tx = new btc.Transaction({ - allowUnknowInput: true, - allowUnknowOutput: true, - allowUnknownInputs: true, - allowUnknownOutputs: true, - }); - addInputs(network, amount, REVEAL_PAYMENT, tx, false, addressInfo.utxos, userPaymentPubKey); - tx.addOutputAddress(commitTxAddress, BigInt(amount), net); - const changeAmount = inputAmt(tx) - (amount + txFees[1]); - if (changeAmount > 0) tx.addOutputAddress(cardinal, BigInt(changeAmount), net); +export async function sbtcDepositHelper({ + network = TEST_NETWORK, // default to testnet for developer release + amountSats, + stacksAddress, + bitcoinChangeAddress, + utxos, + feeRate, + pegAddress = SBTC_PEG_ADDRESS, +}: { + network?: BitcoinNetwork; + amountSats: number; + stacksAddress: string; + bitcoinChangeAddress: string; + utxos: (BlockstreamUtxo | BlockstreamUtxoWithTxHex)[]; + feeRate: number; + pegAddress?: string; +}) { + const tx = buildSbtcDepositTxOpReturn({ network, amountSats, stacksAddress, pegAddress }); + + // we separate this part, since wallets could handle it themselves + const pay = await paymentInfo({ tx, utxos, feeRate }); + for (const input of pay.inputs) tx.addInput(input); + // for (const output of pay.outputs) tx.addOutput(output); // outputs are already on tx; todo: refactor? + + const changeAfterAdditionalOutput = BigInt(VSIZE_INPUT_P2WPKH * feeRate) - pay.changeSats; + if (changeAfterAdditionalOutput > dustMinimum(VSIZE_INPUT_P2WPKH, feeRate)) { + tx.addOutputAddress(bitcoinChangeAddress, changeAfterAdditionalOutput, network); + } + return tx; } -export function getOpReturnDepositRequest( - network: string, - amount: number, - commitKeys: any, - stacksAddress: string, - sbtcWalletAddress: string, - cardinal: string -): BridgeTransactionType { - if (!stacksAddress) throw new Error('Stacks address missing'); - const data = buildDepositPayloadOpReturn(network, stacksAddress); - //console.log('reclaimAddr.pubkey: ' + commitKeys.reclaimPubKey) - //console.log('revealAddr.pubkey: ' + commitKeys.revealPubKey) - - const req: BridgeTransactionType = { - originator: stacksAddress, - fromBtcAddress: cardinal, - revealPub: commitKeys.revealPubKey, - reclaimPub: commitKeys.reclaimPubKey, - status: 1, - tries: 0, - mode: 'op_return', - amount: amount, - requestType: 'deposit', - wallet: hex.encode(data), - stacksAddress: stacksAddress, - sbtcWalletAddress: sbtcWalletAddress, - }; - return req; +export async function paymentInfo({ + tx, + feeRate, + utxos, + utxoToSpendable = defaultUtxoToSpendable, +}: { + tx: btc.Transaction; + feeRate: number; + utxos: (BlockstreamUtxo | BlockstreamUtxoWithTxHex)[]; + utxoToSpendable?: UtxoToSpendableFn; +}) { + const outputs = []; + for (let i = 0; i < tx.outputsLength; i++) { + outputs.push(tx.getOutput(i)); + } + + return await utxoSelect({ utxos, utxoToSpendable, outputs, feeRate }); } -export function getOpDropDepositRequest( - network: string, - revealFee: number, - commitKeys: any, - stacksAddress: string, - sbtcWalletAddress: string, - cardinal: string -): BridgeTransactionType { - const net = network === 'testnet' ? btc.TEST_NETWORK : btc.NETWORK; - if (!stacksAddress) throw new Error('Address needed'); - //console.log('reclaimAddr.pubkey: ' + commitKeys.reclaimPubKey) - //console.log('revealAddr.pubkey: ' + commitKeys.revealPubKey) - - const data = buildData(network, stacksAddress, revealFee, true); - const scripts = [ - { script: btc.Script.encode([data, 'DROP', hex.decode(commitKeys.revealPubKey), 'CHECKSIG']) }, - { - script: btc.Script.encode([ - 'IF', - 144, - 'CHECKSEQUENCEVERIFY', - 'DROP', - hex.decode(commitKeys.reclaimPubKey), - 'CHECKSIG', - 'ENDIF', - ]), - }, - ]; - const script = btc.p2tr(btc.TAPROOT_UNSPENDABLE_KEY, scripts, net, true); - const req: BridgeTransactionType = { - originator: stacksAddress, - fromBtcAddress: cardinal, - revealPub: commitKeys.revealPubKey, - reclaimPub: commitKeys.reclaimPubKey, - status: 1, - tries: 0, - mode: 'op_drop', - amount: revealFee, - requestType: 'deposit', - wallet: - "p2tr(TAPROOT_UNSPENDABLE_KEY, [{ script: Script.encode([data, 'DROP', revealPubK, 'CHECKSIG']) }, { script: Script.encode([reclaimPubKey, 'CHECKSIG']) }], net, true)", - stacksAddress: stacksAddress, - sbtcWalletAddress: sbtcWalletAddress, - }; - req.commitTxScript = toStorable(script); - return req; +const plus = (a: number, b: number) => a + b; + +function txBytes(inputs: btc.TransactionInput[], outputs: btc.TransactionOutput[]) { + return ( + OVERHEAD_TX + inputs.map(inputBytes).reduce(plus, 0) + outputs.map(outputBytes).reduce(plus, 0) + ); } -function buildData( - network: string, - sigOrPrin: string, - revealFee: number, - opDrop: boolean -): Uint8Array { - const net = network === 'testnet' ? btc.TEST_NETWORK : btc.NETWORK; - if (opDrop) { - return buildDepositPayload(net, revealFee, sigOrPrin, opDrop, undefined); - } - return buildDepositPayloadOpReturn(net, sigOrPrin); +// todo: switch to estimating? + +function inputBytes(input: btc.TransactionInput) { + const tmpTx = new btc.Transaction({ allowUnknownInputs: true }); + const originalSize = tmpTx.vsize; + tmpTx.addInput(input); + return tmpTx.vsize - originalSize; + // return OVERHEAD_INPUT + (input.finalScriptWitness ? input.finalScriptWitness.byteLength : OVERHEAD_INPUT_P2PKH); } -export function maxCommit(addressInfo: any) { - if (!addressInfo || !addressInfo.utxos || addressInfo.utxos.length === 0) return 0; - const summ = addressInfo?.utxos - ?.map((item: { value: number }) => item.value) - .reduce((prev: number, curr: number) => prev + curr, 0); - return summ || 0; +function outputBytes(output: btc.TransactionOutput) { + const tmpTx = new btc.Transaction({ allowUnknownOutputs: true }); + const originalSize = tmpTx.vsize; + tmpTx.addOutput(output); + return tmpTx.vsize - originalSize; + // return OVERHEAD_OUTPUT + (output.script ? output.script.byteLength : OVERHEAD_OUTPUT_P2PKH); } -export function calculateDepositFees( - network: string, - opDrop: boolean, - amount: number, - feeInfo: any, - addressInfo: any, - commitTxScriptAddress: string, - data: Uint8Array | undefined -) { - try { - const net = network === 'testnet' ? btc.TEST_NETWORK : btc.NETWORK; - let vsize = 0; - const tx = new btc.Transaction({ - allowUnknowInput: true, - allowUnknowOutput: true, - allowUnknownInputs: true, - allowUnknownOutputs: true, - }); - addInputs( - network, - amount, - REVEAL_PAYMENT, - tx, - true, - addressInfo.utxos, - hex.encode(secp.getPublicKey(privKey, true)) - ); - if (!opDrop) { - if (data) tx.addOutput({ script: btc.Script.encode(['RETURN', data]), amount: BigInt(0) }); - tx.addOutputAddress(commitTxScriptAddress, BigInt(amount), net); - } else { - tx.addOutputAddress(commitTxScriptAddress, BigInt(amount), net); - } - const changeAmount = inputAmt(tx) - amount; - if (changeAmount > 0) tx.addOutputAddress(addressInfo.address, BigInt(changeAmount), net); - //tx.sign(privKey); - //tx.finalize(); - vsize = tx.vsize; - const fees = [ - Math.floor((vsize * feeInfo['low_fee_per_kb']) / 1024), - Math.floor((vsize * feeInfo['medium_fee_per_kb']) / 1024), - Math.floor((vsize * feeInfo['high_fee_per_kb']) / 1024), - ]; - return fees; - } catch (err: any) { - return [850, 1000, 1150]; - } +function dustMinimum(inputVsize: number, feeRate: number) { + return inputVsize * feeRate; } diff --git a/packages/sbtc/src/transactions/index.ts b/packages/sbtc/src/transactions/index.ts new file mode 100644 index 000000000..23f5769d0 --- /dev/null +++ b/packages/sbtc/src/transactions/index.ts @@ -0,0 +1,3 @@ +export * from './api'; +export * from './deposit'; +export * from './constants'; diff --git a/packages/sbtc/tests/deposit.test.ts b/packages/sbtc/tests/deposit.test.ts new file mode 100644 index 000000000..3e0c764ef --- /dev/null +++ b/packages/sbtc/tests/deposit.test.ts @@ -0,0 +1,61 @@ +import { HDKey } from '@scure/bip32'; +import * as bip39 from '@scure/bip39'; +import * as btc from '@scure/btc-signer'; +import { describe, expect, test } from 'vitest'; +import { sbtcDepositHelper } from '../src'; +import * as api from '../src/transactions/api'; + +describe('mock integration test', () => { + test('deposit', async () => { + // == Wallet =================================================================== + const seed = await bip39.mnemonicToSeed( + 'twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw' + ); + + const TESTNET_VERSION = { + private: 0x00000000, + public: 0x043587cf, + }; + const hdkey = HDKey.fromMasterSeed(seed, TESTNET_VERSION); + + const chainCode = 1; // testnet = 1, mainnet = 0 + const accountIndex = 0; + const path = `m/84'/${chainCode}'/${accountIndex}'/0/0`; + + const privKey = hdkey.derive(path).privateKey!; + const address = btc.getAddress('wpkh', privKey, btc.TEST_NETWORK)!; + + // == sBTC ===================================================================== + + const TARGET_STACKS_ADDRESS = 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM'; + + // Tx building (most simple interface) + const tx = await sbtcDepositHelper({ + stacksAddress: TARGET_STACKS_ADDRESS, + amountSats: 1_000, + + feeRate: await api.estimateFeeRate('low'), + utxos: await api.fetchUtxos(address), + + bitcoinChangeAddress: address, + }); + + tx.sign(privKey); + tx.finalize(); + + expect(tx).toBeDefined(); // yay, it didn't throw + }); +}); + +// api.broadcastTx(tx); + +// == Advanced ================================================================= + +// const PEG_PUB_KEY = ( +// await api.stacksCallReadOnly({ +// contractAddress: "ST1R1061ZT6KPJXQ7PAXPFB6ZAZ6ZWW28G8HXK9G5.romeo-bridge", +// functionName: "get-bitcoin-wallet-public-key", +// sender: TARGET_STACKS_ADDRESS, // not actually used in call +// }) +// ).value.buffer; +// const PEG_ADDRESS = btc.p2tr(PEG_PUB_KEY, undefined, btc.TEST_NETWORK).address; diff --git a/packages/sbtc/tsconfig.build.json b/packages/sbtc/tsconfig.build.json index 8d57d415d..e6c63a0c7 100644 --- a/packages/sbtc/tsconfig.build.json +++ b/packages/sbtc/tsconfig.build.json @@ -11,9 +11,6 @@ { "path": "../common/tsconfig.build.json" }, - { - "path": "../encryption/tsconfig.build.json" - }, { "path": "../network/tsconfig.build.json" }, @@ -21,7 +18,5 @@ "path": "../transactions/tsconfig.build.json" } ], - "include": [ - "src/**/*" - ] + "include": ["src/**/*"] } diff --git a/packages/sbtc/webpack.config.js b/packages/sbtc/webpack.config.js index a41c22f84..9abb3d136 100644 --- a/packages/sbtc/webpack.config.js +++ b/packages/sbtc/webpack.config.js @@ -1,6 +1,6 @@ const config = require('../../configs/webpack.config.js'); -config.output.library.name = 'StacksStacking'; +config.output.library.name = 'StacksSbtc'; config.resolve.fallback = {}; diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 000000000..31e4d9e3c --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + testTimeout: process.env.CI ? 5_000 : 1_000_000, + }, +});