diff --git a/.prettierrc.json b/.prettierrc.json index 35e12d9..cef8946 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,4 +1,6 @@ { + "plugins": ["prettier-plugin-organize-imports"], + "organizeImportsSkipDestructiveCodeActions": true, "arrowParens": "always", "bracketSpacing": true, "jsxBracketSameLine": false, diff --git a/example/.eslintrc.cjs b/example/.eslintrc.cjs index a11aaec..62786bc 100644 --- a/example/.eslintrc.cjs +++ b/example/.eslintrc.cjs @@ -8,6 +8,7 @@ module.exports = { 'plugin:react-hooks/recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime', + 'prettier', ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', @@ -17,7 +18,7 @@ module.exports = { project: ['./tsconfig.app.json', './tsconfig.node.json'], tsconfigRootDir: __dirname, }, - plugins: ['react-refresh'], + plugins: ['react-refresh', 'prettier'], rules: { 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], }, diff --git a/example/.prettierrc.json b/example/.prettierrc.json new file mode 100644 index 0000000..063d354 --- /dev/null +++ b/example/.prettierrc.json @@ -0,0 +1,15 @@ +{ + "plugins": ["prettier-plugin-organize-imports"], + "organizeImportsSkipDestructiveCodeActions": true, + "arrowParens": "always", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "quoteProps": "as-needed", + "singleQuote": true, + "semi": true, + "printWidth": 100, + "useTabs": false, + "tabWidth": 2, + "trailingComma": "all" +} diff --git a/example/package-lock.json b/example/package-lock.json index a925e2e..3c9a6ec 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -8,6 +8,8 @@ "name": "dapp-cookie-cutter", "version": "0.1.0", "dependencies": { + "@mantine/core": "^7.11.2", + "@mantine/hooks": "^7.11.2", "@stacks/common": "^6.16.0", "@stacks/transactions": "^6.16.1", "@tanstack/react-query": "^5.50.1", @@ -16,6 +18,7 @@ "eslint-plugin-react": "^7.34.3", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^6.26.0", "sats-connect": "file:..", "styled-components": "6.1.11" }, @@ -28,6 +31,11 @@ "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", + "postcss": "^8.4.40", + "postcss-preset-mantine": "^1.17.0", + "postcss-simple-vars": "^7.0.1", + "prettier": "^3.3.3", + "prettier-plugin-organize-imports": "^4.0.0", "typescript": "^5.2.2", "vite": "^5.3.1", "vite-plugin-node-polyfills": "0.17.0" @@ -45,6 +53,7 @@ "husky": "8.0.3", "lint-staged": "13.2.3", "prettier": "2.8.4", + "prettier-plugin-organize-imports": "^4.0.0", "rimraf": "3.0.2", "ts-jest": "29.0.5", "tsup": "8.0.2", @@ -365,6 +374,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", @@ -895,6 +915,54 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz", + "integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==", + "dependencies": { + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz", + "integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.20", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.20.tgz", + "integrity": "sha512-RixKJJG92fcIsVoqrFr4Onpzh7hlOx4U7NV4aLhMLmtvjZ5oTB/WzXaANYUZATKqXvvW7t9sCxtzejip26N5Ag==", + "dependencies": { + "@floating-ui/react-dom": "^2.1.1", + "@floating-ui/utils": "^0.2.5", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", + "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -995,6 +1063,43 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mantine/core": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.11.2.tgz", + "integrity": "sha512-T64RjdgY8UPAv249miW1lQyPPot1JbCcKKsAZMNQHgcttcxLhrFpKVvglc4/48hdSoxI4LYJPNvqp7zciZmucQ==", + "dependencies": { + "@floating-ui/react": "^0.26.9", + "clsx": "^2.1.1", + "react-number-format": "^5.3.1", + "react-remove-scroll": "^2.5.7", + "react-textarea-autosize": "8.5.3", + "type-fest": "^4.12.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.11.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/core/node_modules/type-fest": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.23.0.tgz", + "integrity": "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.11.2.tgz", + "integrity": "sha512-jhyVe/sbDEG2U8rr2lMecUPgQxcfr5hh9HazqGfkS7ZRIMDO7uJ947yAcTMGGkp5Lxtt5TBFt1Cb6tiB2/1agg==", + "peerDependencies": { + "react": "^18.2.0" + } + }, "node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", @@ -1049,6 +1154,14 @@ "node": ">= 8" } }, + "node_modules/@remix-run/router": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", + "integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-inject": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", @@ -1434,13 +1547,13 @@ "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -2419,6 +2532,15 @@ "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -2470,6 +2592,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2616,6 +2746,18 @@ "postcss-value-parser": "^4.0.2" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2732,6 +2874,11 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -3592,6 +3739,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -3911,6 +4066,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -4951,9 +5114,10 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.40", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4970,13 +5134,121 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-mixins": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/postcss-mixins/-/postcss-mixins-9.0.4.tgz", + "integrity": "sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "postcss-js": "^4.0.0", + "postcss-simple-vars": "^7.0.0", + "sugarss": "^4.0.1" + }, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-preset-mantine": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.17.0.tgz", + "integrity": "sha512-ji1PMDBUf2Vsx/HE5faMSs1+ff6qE6YRulTr4Ja+6HD3gop8rSMTCYdpN7KrdsEg079kfBKkO/PaKhG9uR0zwQ==", + "dev": true, + "dependencies": { + "postcss-mixins": "^9.0.4", + "postcss-nested": "^6.0.1" + }, + "peerDependencies": { + "postcss": ">=8.0.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-simple-vars": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz", + "integrity": "sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==", + "dev": true, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.1" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -4990,6 +5262,41 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-organize-imports": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.0.0.tgz", + "integrity": "sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==", + "dev": true, + "peerDependencies": { + "@vue/language-plugin-pug": "^2.0.24", + "prettier": ">=2.0", + "typescript": ">=2.9", + "vue-tsc": "^2.0.24" + }, + "peerDependenciesMeta": { + "@vue/language-plugin-pug": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -5126,6 +5433,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-number-format": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.0.tgz", + "integrity": "sha512-NWdICrqLhI7rAS8yUeLVd6Wr4cN7UjJ9IBTS0f/a9i7UB4x4Ti70kGnksBtZ7o4Z7YRbvCMMR/jQmkoOBa/4fg==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -5135,6 +5454,119 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz", + "integrity": "sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz", + "integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==", + "dependencies": { + "@remix-run/router": "1.19.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz", + "integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==", + "dependencies": { + "@remix-run/router": "1.19.0", + "react-router": "6.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", + "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5168,6 +5600,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -5645,11 +6082,54 @@ "react-dom": ">= 16.8.0" } }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/stylis": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" }, + "node_modules/sugarss": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-4.0.1.tgz", + "integrity": "sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -5673,6 +6153,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5921,6 +6406,84 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", + "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz", + "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -6020,34 +6583,6 @@ "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" } }, - "node_modules/vite/node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", diff --git a/example/package.json b/example/package.json index 46d32a8..a26f996 100644 --- a/example/package.json +++ b/example/package.json @@ -16,6 +16,8 @@ "prepare": "cd .. && npm i && npm run build && cd example" }, "dependencies": { + "@mantine/core": "^7.11.2", + "@mantine/hooks": "^7.11.2", "@stacks/common": "^6.16.0", "@stacks/transactions": "^6.16.1", "@tanstack/react-query": "^5.50.1", @@ -24,6 +26,7 @@ "eslint-plugin-react": "^7.34.3", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^6.26.0", "sats-connect": "file:..", "styled-components": "6.1.11" }, @@ -36,6 +39,11 @@ "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", + "postcss": "^8.4.40", + "postcss-preset-mantine": "^1.17.0", + "postcss-simple-vars": "^7.0.1", + "prettier": "^3.3.3", + "prettier-plugin-organize-imports": "^4.0.0", "typescript": "^5.2.2", "vite": "^5.3.1", "vite-plugin-node-polyfills": "0.17.0" diff --git a/example/public/_redirects b/example/public/_redirects new file mode 100644 index 0000000..7797f7c --- /dev/null +++ b/example/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/example/src/App.styles.ts b/example/src/App.styles.ts index 1711868..5558529 100644 --- a/example/src/App.styles.ts +++ b/example/src/App.styles.ts @@ -1,4 +1,6 @@ +import { Button, Card, Input, NativeSelect } from '@mantine/core'; import styled from 'styled-components'; +export { Button, Card, Input, NativeSelect }; export const ConnectButtonsContainer = styled.div({ display: 'flex', @@ -38,7 +40,8 @@ export const Header = styled.header({ }); export const Logo = styled.img({ - marginBottom: '20px', + width: '200px', + padding: 10, }); export const Action = styled.span({ @@ -50,57 +53,10 @@ export const NetworkSelectionButton = styled.button({ marginBottom: '20px', }); -export const Card = styled.div({ - backgroundColor: 'white', - color: '#282c34', - padding: '0 20px 20px 20px', - marginBottom: '30px', - borderRadius: '20px', -}); - export const Success = styled.div({ color: 'green', }); -export const Button = styled.button({ - opacity: 0.9, - padding: '10px 20px', - minWidth: '130px', - backgroundColor: '#ff4d00', - color: 'white', - fontWeight: 'bold', - border: 'none', - borderRadius: '5px', - cursor: 'pointer', - ':hover': { - opacity: 1, - }, - ':active': { - backgroundColor: '#ff6a00', - }, - ':disabled': { - opacity: 0.5, - backgroundColor: '#ff4d00', - cursor: 'not-allowed', - }, - '&.secondary': { - backgroundColor: 'transparent', - border: '1px solid #ff4d00', - color: '#ff4d00', - }, -}); - -export const Input = styled.input({ - marginTop: '5px', - width: '300px', - padding: '10px', - fontWeight: 'bold', - fontSize: '1rem', - ':focus-visible': { - outline: '#ff4d00 auto 1px', - }, -}); - export const H4 = styled.h4({ marginBlockEnd: '0.5em', }); diff --git a/example/src/App.tsx b/example/src/App.tsx index 0e3f24a..53b8c1e 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,30 +1,57 @@ -import Wallet, { type Address, AddressPurpose, BitcoinNetworkType } from 'sats-connect'; -import { - AddressDisplay, - EtchRunes, - MintRunes, - NetworkSelector, - SendBtc, - SendStx, -} from './components'; -import { useLocalStorage } from './hooks'; -import { useCallback, useEffect } from 'react'; -import GetBtcBalance from './components/GetBtcBalance'; -import GetRunesBalance from './components/GetRunesBalance'; -import { Container, ConnectButtonsContainer, Header, Logo, Body, Button } from './App.styles'; -import GetInscriptions from './components/GetInscriptions'; +import { Container, createTheme, MantineProvider, Stack } from '@mantine/core'; +import '@mantine/core/styles.css'; import { QueryClient, QueryClientProvider, useQueryClient } from '@tanstack/react-query'; -import { WalletType } from './components/wallet/WalletType'; +import { createContext, useCallback, useContext, useEffect, useMemo } from 'react'; +import { + createBrowserRouter, + createRoutesFromElements, + Link, + Outlet, + Route, + RouterProvider, +} from 'react-router-dom'; +import Wallet, { AddressPurpose, BitcoinNetworkType, type Address } from 'sats-connect'; +import { Button, ConnectButtonsContainer, Header, Logo } from './App.styles'; import { GetAccounts } from './components/bitcoin/GetAccounts'; -import { SignMessage } from './components/SignMessage'; -import SendInscription from './components/sendInscriptions'; -import SignTransaction from './components/signTransaction'; - -function AppWithProviders() { +import { GetBtcBalance } from './components/bitcoin/GetBtcBalance'; +import { SignMessage } from './components/bitcoin/SignMessage'; +import { GetInscriptions } from './components/GetInscriptions'; +import { GetRunesBalance } from './components/GetRunesBalance'; +import { SendInscription } from './components/sendInscriptions'; + +import AddressDisplay from './components/AddressDisplay'; +import { SendBtc } from './components/bitcoin/SendBtc'; +import EtchRunes from './components/EtchRunes'; +import MintRunes from './components/MintRunes'; +import { NetworkSelector } from './components/NetworkSelector'; +import { SendSip10 } from './components/stacks/SendSip10'; +import { SendStx } from './components/stacks/SendStx'; +import { SignTransaction } from './components/stacks/SignTransaction.tsx'; +import { WalletType } from './components/wallet/WalletType'; +import { useLocalStorage } from './hooks'; +import { CollapseDesktop } from './layouts/CollapseDesktop'; + +const ConnectionContext = createContext<{ + network: BitcoinNetworkType; + btcAddressInfo: Address[]; + stxAddressInfo: Address[]; + onDisconnect: () => void; +}>({ + network: BitcoinNetworkType.Mainnet, + btcAddressInfo: [], + stxAddressInfo: [], + onDisconnect: () => { + console.log('onDisconnect not implemented'); + }, +}); + +const useConnectionContext = () => useContext(ConnectionContext); + +function AppWithProviders({ children }: React.PropsWithChildren<{}>) { const queryClient = useQueryClient(); const [network, setNetwork] = useLocalStorage( 'network', - BitcoinNetworkType.Mainnet + BitcoinNetworkType.Mainnet, ); const [btcAddressInfo, setBtcAddressInfo] = useLocalStorage('btc-addresses', []); const [stxAddressInfo, setStxAddressInfo] = useLocalStorage('stx-addresses', []); @@ -40,6 +67,7 @@ function AppWithProviders() { return removeListener; }); + const onConnectLegacy = useCallback(() => { (async () => { const response = await Wallet.request('getAccounts', { @@ -61,28 +89,23 @@ function AppWithProviders() { console.error(res); return; } - const res2 = await Wallet.request('getAddresses', { purposes: [AddressPurpose.Ordinals, AddressPurpose.Payment], }); - if (res2.status === 'error') { console.error('Error retrieving bitcoin addresses after having requested permissions.'); console.error(res2); return; } - setBtcAddressInfo(res2.result.addresses); const res3 = await Wallet.request('stx_getAddresses', null); - if (res3.status === 'error') { alert( - 'Error retrieving stacks addresses after having requested permissions. Details in terminal.' + 'Error retrieving stacks addresses after having requested permissions. Details in terminal.', ); console.error(res3); return; } - setStxAddressInfo(res3.result.addresses); })().catch(console.error); }, [setBtcAddressInfo, setStxAddressInfo]); @@ -96,6 +119,11 @@ function AppWithProviders() { })().catch(console.error); }, [queryClient, setBtcAddressInfo, setStxAddressInfo]); + const connectionContextValue = useMemo( + () => ({ network, btcAddressInfo, stxAddressInfo, onDisconnect }), + [network, btcAddressInfo, stxAddressInfo, onDisconnect], + ); + if (!isConnected) { return ( @@ -113,42 +141,110 @@ function AppWithProviders() { } return ( - - -
- -
- - - - - - {stxAddressInfo?.[0]?.publicKey ? ( - - ) : null} - - - - - - - - -
+ + {children} + ); } +// TODO move to pages or routes.tsx +const WalletMethods = () => { + const { network, btcAddressInfo, stxAddressInfo, onDisconnect } = useConnectionContext(); + return ( + <> +
+ +
+ + + + + ); +}; + +const BitcoinMethods = () => { + const { network, btcAddressInfo, onDisconnect } = useConnectionContext(); + return ( + <> + + + + + + + + + + + ); +}; + +const StacksMethods = () => { + const { network, stxAddressInfo, onDisconnect } = useConnectionContext(); + return ( + <> + + + + {stxAddressInfo?.[0]?.publicKey ? ( + + ) : null} + + ); +}; + +const Layout = () => ( + + + + + +); + +const NoMatch = () => ( +
+

Nothing to see here!

+

+ Go to the home page +

+
+); + +const router = createBrowserRouter( + createRoutesFromElements( + }> + } /> + } /> + } /> + } /> + , + ), +); + const queryClient = new QueryClient(); -function App() { +const theme = createTheme({ + primaryColor: 'orange', +}); + +export default function App() { return ( - + + + ); } -export default App; diff --git a/example/src/components/AddressDisplay/index.tsx b/example/src/components/AddressDisplay/index.tsx index b7993fc..e892ff3 100644 --- a/example/src/components/AddressDisplay/index.tsx +++ b/example/src/components/AddressDisplay/index.tsx @@ -7,7 +7,7 @@ interface Props { onDisconnect: () => void; } -const AddressDisplay = ({ network, addresses, onDisconnect }: Props) => { +export const AddressDisplay = ({ network, addresses, onDisconnect }: Props) => { return (

Connected Addresses - ({network})

diff --git a/example/src/components/EtchRunes/index.tsx b/example/src/components/EtchRunes/index.tsx index 7daf693..8254a10 100644 --- a/example/src/components/EtchRunes/index.tsx +++ b/example/src/components/EtchRunes/index.tsx @@ -8,7 +8,7 @@ interface Props { addresses: Address[]; } -const EtchRunes = ({ addresses, network }: Props) => { +export const EtchRunes = ({ addresses, network }: Props) => { const [totalCost, setTotalCost] = useState(); const [totalSize, setTotalSize] = useState(); const [fundTxId, setFundTxId] = useState(''); @@ -25,12 +25,12 @@ const EtchRunes = ({ addresses, network }: Props) => { const ordinalsAddress = useMemo( () => addresses.find((a) => a.purpose === AddressPurpose.Ordinals)?.address ?? '', - [addresses] + [addresses], ); const paymentAddress = useMemo( () => addresses.find((a) => a.purpose === AddressPurpose.Payment)?.address ?? '', - [addresses] + [addresses], ); const onClickEstimate = useCallback(() => { diff --git a/example/src/components/GetInscriptions/index.tsx b/example/src/components/GetInscriptions/index.tsx index dc8b35e..4cbbe53 100644 --- a/example/src/components/GetInscriptions/index.tsx +++ b/example/src/components/GetInscriptions/index.tsx @@ -1,8 +1,8 @@ +import { useCallback } from 'react'; import Wallet from 'sats-connect'; import { Button, Card } from '../../App.styles'; -import { useCallback } from 'react'; -const GetInscriptions = () => { +export const GetInscriptions = () => { const onClick = useCallback(() => { (async () => { const response = await Wallet.request('ord_getInscriptions', { @@ -29,5 +29,3 @@ const GetInscriptions = () => {
); }; - -export default GetInscriptions; diff --git a/example/src/components/GetRunesBalance/index.tsx b/example/src/components/GetRunesBalance/index.tsx index a36155a..dbbfd97 100644 --- a/example/src/components/GetRunesBalance/index.tsx +++ b/example/src/components/GetRunesBalance/index.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react'; import Wallet, { GetRunesBalanceResult } from 'sats-connect'; import { Button, Card } from '../../App.styles'; -const GetRunesBalance = () => { +export const GetRunesBalance = () => { const [balances, setBalances] = useState([]); const getBalance = useCallback(() => { @@ -47,5 +47,3 @@ const GetRunesBalance = () => { ); }; - -export default GetRunesBalance; diff --git a/example/src/components/MintRunes/index.tsx b/example/src/components/MintRunes/index.tsx index e837f6b..dc33806 100644 --- a/example/src/components/MintRunes/index.tsx +++ b/example/src/components/MintRunes/index.tsx @@ -7,7 +7,7 @@ interface Props { addresses: Address[]; } -const MintRunes = ({ addresses, network }: Props) => { +export const MintRunes = ({ addresses, network }: Props) => { const [totalCost, setTotalCost] = useState(); const [totalSize, setTotalSize] = useState(); const [fundTxId, setFundTxId] = useState(''); @@ -17,12 +17,12 @@ const MintRunes = ({ addresses, network }: Props) => { const ordinalsAddress = useMemo( () => addresses.find((a) => a.purpose === AddressPurpose.Ordinals)?.address ?? '', - [addresses] + [addresses], ); const paymentAddress = useMemo( () => addresses.find((a) => a.purpose === AddressPurpose.Payment)?.address ?? '', - [addresses] + [addresses], ); const onClickEstimate = useCallback(() => { diff --git a/example/src/components/NetworkSelector/index.tsx b/example/src/components/NetworkSelector/index.tsx index c0b16d3..71bbcac 100644 --- a/example/src/components/NetworkSelector/index.tsx +++ b/example/src/components/NetworkSelector/index.tsx @@ -6,7 +6,7 @@ interface Props { setNetwork: (newNetwork: BitcoinNetworkType) => void; } -const NetworkSelector = ({ network, setNetwork }: Props) => { +export const NetworkSelector = ({ network, setNetwork }: Props) => { const onNetworkChange = () => { const newNetwork = network === BitcoinNetworkType.Mainnet @@ -28,5 +28,3 @@ const NetworkSelector = ({ network, setNetwork }: Props) => { ); }; - -export default NetworkSelector; diff --git a/example/src/components/GetBtcBalance/index.tsx b/example/src/components/bitcoin/GetBtcBalance/index.tsx similarity index 89% rename from example/src/components/GetBtcBalance/index.tsx rename to example/src/components/bitcoin/GetBtcBalance/index.tsx index b834a9e..f7dd858 100644 --- a/example/src/components/GetBtcBalance/index.tsx +++ b/example/src/components/bitcoin/GetBtcBalance/index.tsx @@ -1,8 +1,8 @@ import { useCallback, useState } from 'react'; import Wallet from 'sats-connect'; -import { Button, Card } from '../../App.styles'; +import { Button, Card } from '../../../App.styles'; -const GetBtcBalance = () => { +export const GetBtcBalance = () => { const [confirmed, setConfirmed] = useState(''); const [unconfirmed, setUnconfirmed] = useState(''); const [total, setTotal] = useState(''); @@ -37,5 +37,3 @@ const GetBtcBalance = () => { ); }; - -export default GetBtcBalance; diff --git a/example/src/components/SendBtc/index.tsx b/example/src/components/bitcoin/SendBtc/index.tsx similarity index 96% rename from example/src/components/SendBtc/index.tsx rename to example/src/components/bitcoin/SendBtc/index.tsx index aecd1e6..8bb2454 100644 --- a/example/src/components/SendBtc/index.tsx +++ b/example/src/components/bitcoin/SendBtc/index.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; import Wallet, { BitcoinNetworkType } from 'sats-connect'; -import { Button, Card, Input, Success } from '../../App.styles'; +import { Button, Card, Input, Success } from '../../../App.styles'; interface Props { network: BitcoinNetworkType; @@ -11,7 +11,7 @@ interface Recipient { amount: string; } -const SendBtc = ({ network }: Props) => { +export const SendBtc = ({ network }: Props) => { const [recipients, setRecipients] = useState([{ address: '', amount: '' }]); const [txnId, setTxnId] = useState(''); @@ -118,5 +118,3 @@ const SendBtc = ({ network }: Props) => { ); }; - -export default SendBtc; diff --git a/example/src/components/SignMessage/index.tsx b/example/src/components/bitcoin/SignMessage/index.tsx similarity index 82% rename from example/src/components/SignMessage/index.tsx rename to example/src/components/bitcoin/SignMessage/index.tsx index 58a5752..2eb4178 100644 --- a/example/src/components/SignMessage/index.tsx +++ b/example/src/components/bitcoin/SignMessage/index.tsx @@ -1,8 +1,8 @@ -import { useState } from 'react'; -import Wallet, { Address, MessageSigningProtocols, RpcErrorCode } from 'sats-connect'; +import { Button, Card, Input, NativeSelect } from '@mantine/core'; import { Verifier } from 'bip322-js'; import { verify } from 'bitcoinjs-message'; -import { Card, Button } from '../../App.styles'; +import { useState } from 'react'; +import Wallet, { Address, MessageSigningProtocols, RpcErrorCode } from 'sats-connect'; interface Props { addresses: Address[]; @@ -10,7 +10,7 @@ interface Props { export const SignMessage = ({ addresses }: Props) => { const [message, setMessage] = useState(''); - const [address, setAddress] = useState(addresses[0].address); + const [address, setAddress] = useState(addresses[0]?.address); const [protocol, setProtocol] = useState(MessageSigningProtocols.ECDSA); const onClick = async () => { @@ -52,24 +52,24 @@ export const SignMessage = ({ addresses }: Props) => { <>
Message
- setMessage(e.target.value)} /> + setMessage(e.target.value)} />
Address
- + setAddress(e.target.value)}> + + +
Protocol
- +
- -
- {(() => { - if (contractCallMutation.isPending) { - return

Loading...

; - } - - if (contractCallMutation.isError) { - console.error(contractCallMutation.error); - return

{errorMessage}

; - } - - if (contractCallMutation.isSuccess) { - console.log('Signed transaction:', contractCallMutation.data); - return

Transaction signed successfully. Check console for details.

; - } - })()} -
- -
-
- -
-
- {(() => { - if (tokenTransferMutation.isPending) { - return

Loading...

; - } - - if (tokenTransferMutation.isError) { - console.error(tokenTransferMutation.error); - return

{errorMessage}

; - } - - if (tokenTransferMutation.isSuccess) { - console.log(tokenTransferMutation.data); - return

Transaction signed successfully. Check console for details.

; - } - })()} -
-
-
-
- -
-
- {(() => { - if (contractDeployMutation.isPending) { - return

Loading...

; - } - - if (contractDeployMutation.isError) { - console.error(contractDeployMutation.error); - return

{errorMessage}

; - } - - if (contractDeployMutation.isSuccess) { - console.log(contractDeployMutation.data); - return

Transaction signed successfully. Check console for details.

; - } - })()} -
-
- - - ); -} - -export default SignTransaction; diff --git a/example/src/components/stacks/SendSip10/index.tsx b/example/src/components/stacks/SendSip10/index.tsx new file mode 100644 index 0000000..91f5eff --- /dev/null +++ b/example/src/components/stacks/SendSip10/index.tsx @@ -0,0 +1,127 @@ +import { Button, Card, Stack, TextInput } from '@mantine/core'; +import { + // bufferCV, + cvToString, + noneCV, + // someCV, + standardPrincipalCV, + uintCV, +} from '@stacks/transactions'; +import { ChangeEventHandler, useState } from 'react'; +import Wallet, { Address, BitcoinNetworkType } from 'sats-connect'; +import { Success } from '../../../App.styles'; + +// TODO use emptry strings once done testing +const formInitialState = { + amount: '100000000', // 100LEO + contract: 'SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token', // LEO token contract + address: 'SP313FW47A0XR7HCBFQ0ZZHS47Q265AEBMPK1GD4N', // account 2 + memo: '', +}; + +const formInputs: { + field: keyof typeof formInitialState; + label: string; + type: 'text' | 'number'; +}[] = [ + { + field: 'contract', + label: 'Contract', + type: 'text', + }, + { + field: 'amount', + label: 'Amount', + type: 'number', + }, + { + field: 'address', + label: 'Address', + type: 'text', + }, + { + field: 'memo', + label: 'Memo (optional)', + type: 'text', + }, +]; + +export const SendSip10 = ({ + network, + stxAddressInfo, +}: { + network: BitcoinNetworkType; + stxAddressInfo: Address[]; +}) => { + const [form, setForm] = useState(formInitialState); + const [txnId, setTxnId] = useState(''); + + const canSubmit = form.amount && form.address && form.contract; + + const getChangeFormHandler = + (fieldName: keyof typeof formInitialState): ChangeEventHandler => + (e) => { + setForm((prevForm) => ({ ...prevForm, [fieldName]: e.target.value })); + }; + + const onClick = () => { + (async () => { + const response = await Wallet.request('stx_callContract', { + contract: form.contract, + functionName: 'transfer', + arguments: [ + uintCV(+form.amount), + standardPrincipalCV(stxAddressInfo?.[0].address), + standardPrincipalCV(form.address), + noneCV(), + //form.memo ? someCV(bufferCV(Buffer.from(form.memo))) : noneCV(), + ].map((arg) => cvToString(arg)), + }); + + if (response.status === 'error') { + console.error(response.error); + alert('Error sending. See console for details.'); + return; + } + + setTxnId(response.result.txid); + setForm(formInitialState); + })().catch(console.error); + }; + + const explorerUrl = + network === BitcoinNetworkType.Mainnet + ? `https://explorer.hiro.so/txid/${txnId}` + : `https://explorer.hiro.so/txid/${txnId}?chain=testnet`; + + return ( + +

Send SIP-10

+ {!txnId && ( + + {formInputs.map(({ field, label, type }) => ( + + ))} + + + )} + {txnId && ( + + Success! Click{' '} + + here + {' '} + to see your transaction + + )} +
+ ); +}; diff --git a/example/src/components/SendStx/index.tsx b/example/src/components/stacks/SendStx/index.tsx similarity index 93% rename from example/src/components/SendStx/index.tsx rename to example/src/components/stacks/SendStx/index.tsx index 2c71198..e00038d 100644 --- a/example/src/components/SendStx/index.tsx +++ b/example/src/components/stacks/SendStx/index.tsx @@ -1,12 +1,12 @@ import { useCallback, useState } from 'react'; import Wallet, { BitcoinNetworkType } from 'sats-connect'; -import { Button, Card, Input, Success } from '../../App.styles'; +import { Button, Card, Input, Success } from '../../../App.styles'; interface Props { network: BitcoinNetworkType; } -const SendStx = ({ network }: Props) => { +export const SendStx = ({ network }: Props) => { const [amount, setAmount] = useState(''); const [address, setAddress] = useState(''); const [memo, setMemo] = useState(''); @@ -71,5 +71,3 @@ const SendStx = ({ network }: Props) => { ); }; - -export default SendStx; diff --git a/example/src/components/stacks/SignTransaction.tsx b/example/src/components/stacks/SignTransaction.tsx new file mode 100644 index 0000000..bceff59 --- /dev/null +++ b/example/src/components/stacks/SignTransaction.tsx @@ -0,0 +1,132 @@ +import { Button, Card, Stack, Switch } from '@mantine/core'; +import { BitcoinNetworkType } from '@sats-connect/core'; +import { + PostConditionMode, + makeUnsignedContractCall, + makeUnsignedContractDeploy, + makeUnsignedSTXTokenTransfer, + uintCV, +} from '@stacks/transactions'; +import { useState } from 'react'; +import { request } from 'sats-connect'; + +const codeBody = ` +(define-data-var greeting (string-ascii 100) "Hello, World!") + +(define-read-only (get-greeting) + (ok (var-get greeting)) +) + +(define-public (set-greeting (new-greeting (string-ascii 100))) + (begin + (var-set greeting new-greeting) + (ok new-greeting)) +) +`; + +function uint8ArrayToHex(uint8Array: Uint8Array) { + return Array.from(uint8Array, (byte) => byte.toString(16).padStart(2, '0')).join(''); +} + +const errorMessage = 'Error signing transaction. Check console for error logs.'; + +interface Props { + network: BitcoinNetworkType; // TODO handle networks + publicKey: string; +} + +export function SignTransaction({ publicKey }: Props) { + const [broadcast, setBroadcast] = useState(false); + const [postConditionMode, setPostConditionMode] = useState( + PostConditionMode.Deny, + ); + + const requestSignTransaction = async (transaction: any) => { + try { + const response = await request('stx_signTransaction', { + transaction: uint8ArrayToHex(transaction.serialize()), + broadcast, + }); + if (response.status === 'success') { + alert('Success! Check console for result.'); + console.log(response.result.transaction); + } else { + alert('Error signing transaction. Check console for error logs'); + console.error(response.error); + } + } catch (error) { + alert(errorMessage); + console.error(error); + } + }; + + const handleSignTransactionContractCallClick = async () => { + const transaction = await makeUnsignedContractCall({ + fee: 3000, + anchorMode: 'onChainOnly', + contractAddress: 'SP21YTSM60CAY6D011EZVEVNKXVW8FVZE198XEFFP', + contractName: 'pox-fast-pool-v2', + functionName: 'set-stx-buffer', + functionArgs: [uintCV(1)], + postConditionMode, + publicKey, + }); + requestSignTransaction(transaction); + }; + + const handleSignTransactionSTXTokenTransferClick = async () => { + const transaction = await makeUnsignedSTXTokenTransfer({ + anchorMode: 'any', + fee: 3000, + recipient: 'SP2FFKDKR122BZWS7GDPFWC0J0FK4WMW5NPQ0Z21M', // account 4 + amount: 1000, + publicKey, + }); + requestSignTransaction(transaction); + }; + + const handleSignTransactionContractDeployClick = async () => { + const transaction = await makeUnsignedContractDeploy({ + anchorMode: 'any', + contractName: 'my-contract', + codeBody, + fee: 3000, + postConditionMode, + publicKey, + }); + requestSignTransaction(transaction); + }; + + return ( + +

Sign transaction

+ + setBroadcast((prev) => !prev)} + label={`Broadcast: ${broadcast ? 'True' : 'False'}`} + /> + + setPostConditionMode((prev) => + prev === PostConditionMode.Allow ? PostConditionMode.Deny : PostConditionMode.Allow, + ) + } + label={`Post condition mode: ${ + postConditionMode === PostConditionMode.Allow ? 'Allow' : 'Deny' + } `} + /> + + + + +
+ ); +} diff --git a/example/src/layouts/CollapseDesktop.tsx b/example/src/layouts/CollapseDesktop.tsx new file mode 100644 index 0000000..015ffeb --- /dev/null +++ b/example/src/layouts/CollapseDesktop.tsx @@ -0,0 +1,50 @@ +import { AppShell, Burger, Group, NavLink } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { Logo } from '../App.styles'; + +export function CollapseDesktop({ children }: React.PropsWithChildren<{}>) { + const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(); + const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true); + + return ( + + + + + + + + + + {[ + { + icon: 'home', + label: 'Wallet', + href: '/', + }, + { + icon: 'bitcoin', + label: 'Bitcoin Methods', + href: '/bitcoin-methods', + }, + { + icon: 'stacks', + label: 'Stacks Methods', + href: '/stacks-methods', + }, + ].map(({ label, href }) => ( + + ))} + + {children} + + ); +} diff --git a/example/src/postcss.config.cjs b/example/src/postcss.config.cjs new file mode 100644 index 0000000..bfba0dd --- /dev/null +++ b/example/src/postcss.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + 'postcss-preset-mantine': {}, + 'postcss-simple-vars': { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, + }, +}; diff --git a/package-lock.json b/package-lock.json index b6c4eca..9c87025 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "husky": "8.0.3", "lint-staged": "13.2.3", "prettier": "2.8.4", + "prettier-plugin-organize-imports": "^4.0.0", "rimraf": "3.0.2", "ts-jest": "29.0.5", "tsup": "8.0.2", @@ -5055,6 +5056,26 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-plugin-organize-imports": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.0.0.tgz", + "integrity": "sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==", + "dev": true, + "peerDependencies": { + "@vue/language-plugin-pug": "^2.0.24", + "prettier": ">=2.0", + "typescript": ">=2.9", + "vue-tsc": "^2.0.24" + }, + "peerDependenciesMeta": { + "@vue/language-plugin-pug": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", diff --git a/package.json b/package.json index 52bb1e6..66940f9 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "husky": "8.0.3", "lint-staged": "13.2.3", "prettier": "2.8.4", + "prettier-plugin-organize-imports": "^4.0.0", "rimraf": "3.0.2", "ts-jest": "29.0.5", "tsup": "8.0.2", diff --git a/src/index.ts b/src/index.ts index 55b60ec..f68c707 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,27 +1,27 @@ import { + BaseAdapter, Params, Requests, + RpcErrorCode, RpcResult, + SatsConnectAdapter, SupportedWallet, defaultAdapters, - getSupportedWallets, - SatsConnectAdapter, - setDefaultProvider, getDefaultProvider, + getSupportedWallets, removeDefaultProvider, - RpcErrorCode, - BaseAdapter, + setDefaultProvider, type AddListener, } from '@sats-connect/core'; +import { makeDefaultConfig } from '@sats-connect/make-default-provider-config'; import { Config, + close, loadSelector, selectWalletProvider, - close, - walletOpen, walletClose, + walletOpen, } from '@sats-connect/ui'; -import { makeDefaultConfig } from '@sats-connect/make-default-provider-config'; class Wallet { private providerId: string | undefined;