diff --git a/index.js b/index.js index 12898d4..620efad 100644 --- a/index.js +++ b/index.js @@ -33,10 +33,10 @@ /** @typedef {import('./src/wallet-account-evm.js').ApproveOptions} ApproveOptions */ -/** @typedef {import('./src/utils/tx-populator-evm.js').UnsignedEvmTransaction} UnsignedEvmTransaction */ - export { default } from './src/wallet-manager-evm.js' export { default as WalletAccountReadOnlyEvm } from './src/wallet-account-read-only-evm.js' export { default as WalletAccountEvm } from './src/wallet-account-evm.js' + +export { ISigner } from '@tetherto/wdk-wallet' diff --git a/package-lock.json b/package-lock.json index f375ac1..0585f8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@noble/hashes": "1.8.0", "@noble/secp256k1": "2.2.3", "@tetherto/wdk-failover-provider": "1.0.0-beta.2", - "@tetherto/wdk-wallet": "1.0.0-beta.13", + "@tetherto/wdk-wallet": "github:claudiovb/wdk-wallet#fix/universal-signer", "bare-node-runtime": "^1.5.0", "bip39": "3.1.0", "ethers": "6.17.0", @@ -2142,9 +2142,9 @@ "license": "0BSD" }, "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", "dev": true, "license": "MIT" }, @@ -2175,9 +2175,8 @@ "license": "Apache-2.0" }, "node_modules/@tetherto/wdk-wallet": { - "version": "1.0.0-beta.13", - "resolved": "https://registry.npmjs.org/@tetherto/wdk-wallet/-/wdk-wallet-1.0.0-beta.13.tgz", - "integrity": "sha512-0lF6TVxcgui/uLpU54x+3+qcAiypiMhJhnHbnHlyCxwKNjpzncq2DsJwJg/IeyeH1mm2wKWE4QqOohRr/VQbwA==", + "version": "1.0.0-beta.14", + "resolved": "git+ssh://git@github.com/claudiovb/wdk-wallet.git#310b5677052363c1e11c878f733335d6e4d45a04", "license": "Apache-2.0", "dependencies": { "bare-node-runtime": "^1.4.0", @@ -2324,9 +2323,9 @@ "license": "MIT" }, "node_modules/@ungap/structured-clone": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", - "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", "dev": true, "license": "ISC" }, @@ -2838,9 +2837,9 @@ } }, "node_modules/bare-addon-resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", - "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.1.tgz", + "integrity": "sha512-F/SD2du8keuYSb4xipnGz5j2E6yhNdHA8ZVxtHae6h2uOrpBIjjbhXvjzKZbr5XUOzqBzh/i8GVFycj2DlFQIA==", "license": "Apache-2.0", "dependencies": { "bare-module-resolve": "^1.10.0", @@ -2899,9 +2898,9 @@ } }, "node_modules/bare-buffer": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.6.1.tgz", - "integrity": "sha512-8mzp60t6jdSD3cEGmyxAV1YrAN2+mnxiUvBPQHJJ4WNk0hSVLJGSIEolRj09ZP8yt/+oSj2N2f5kDVW8297n4A==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.6.2.tgz", + "integrity": "sha512-WT9xx12FJvWbBCkgfjuAeWfY40RW6BKVr2fK9UGrTEYKbvqhcaW0J9AMkA3Sj36Wgi+DUuGXYkZPxn5jVH61LA==", "license": "Apache-2.0", "engines": { "bare": ">=1.20.0" @@ -3019,9 +3018,9 @@ } }, "node_modules/bare-env": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", - "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.1.tgz", + "integrity": "sha512-BdoLvnzaWR0YyyJreEx4zG2Od0AC/s9JSZ+EXyMZKunpn8zEgR/dGv37hdFUaY1bjDkcRKvcKkFyJIwlJ0CaYA==", "license": "Apache-2.0", "dependencies": { "bare-os": "^3.0.1" @@ -3042,15 +3041,16 @@ } }, "node_modules/bare-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bare-fetch/-/bare-fetch-3.0.1.tgz", - "integrity": "sha512-OWC8Z62E8JmomltTkXt9cCPMPj2DNi2vp66FOj3BkglNKNshZuk8n98Ba3afUxrrM4kv9/eMzh9+U9dXZSQyOg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bare-fetch/-/bare-fetch-3.1.0.tgz", + "integrity": "sha512-+l0Ex1cXa50M/UaOYQPyWk76UKJ3odkgHkdcFqTQfbUbHuP/32OE0qVMYveHK4Dc6hG8Y3HQvn5RNC21IbJeKw==", "license": "Apache-2.0", "dependencies": { "bare-form-data": "^1.2.0", "bare-http1": "^4.5.2", "bare-https": "^3.0.0", "bare-mime": "^1.0.0", + "bare-performance": "^2.1.1", "bare-stream": "^2.9.1", "bare-url": "^2.4.0", "bare-zlib": "^1.3.0" @@ -3088,9 +3088,9 @@ } }, "node_modules/bare-fs": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz", - "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", + "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", "license": "Apache-2.0", "dependencies": { "bare-events": "^2.5.4", @@ -3236,9 +3236,9 @@ } }, "node_modules/bare-module-lexer": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.5.3.tgz", - "integrity": "sha512-URLPza0jP3aVzeIX5MDKAl5tGH7zLW1dNCVmSvg3so6PM0/HhoDk2AfVQVeTFJ1xZaxFfaLuGa43q8uC1MmY1g==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.6.3.tgz", + "integrity": "sha512-NQY7cnPV3GZlHJphX4nXmPdNPER/Tp17pVi9/he2ODw/GNZ7FXzrZlrS7WMF8zbtWigqW/NMc9aQc2BH8UJXqA==", "license": "Apache-2.0", "dependencies": { "require-addon": "^1.0.2" @@ -3253,9 +3253,9 @@ } }, "node_modules/bare-module-resolve": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.2.tgz", - "integrity": "sha512-j+hiD5k99qec4KjJvYsI67q5AOBifmy9JG3oeMVxTmvrhn2sIdp8StrUvZu4YNgwTpO+NhniQG16N1ETDe1k5w==", + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.4.tgz", + "integrity": "sha512-xcfgg2u7HqgJiBmah71O9vvdFAgHCvkqC/WSC2O7Bbgosoc1eC/BWe/6IDJ4OsfKlkxuvC/TDWXC+oH5yeW8mA==", "license": "Apache-2.0", "dependencies": { "bare-semver": "^1.0.0" @@ -3270,13 +3270,14 @@ } }, "node_modules/bare-module-traverse": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.1.2.tgz", - "integrity": "sha512-AXpbip2YkkDPqlLlZTKjEjfHpjcjOJB+x0BGw0upCchka1lklnYxUmCaPreRzyFzkYuEUBMeIQ4hEAkHZXgQ6g==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.4.4.tgz", + "integrity": "sha512-zK4bDxqeD15Tvu/C5B1Mi/epl9PavjIhU3SVh5RhDb4Hpet6applPVX8Q9rinu7nT80yzgSSlBn0exzJaNXv0A==", "license": "Apache-2.0", "dependencies": { "bare-addon-resolve": "^1.5.0", - "bare-module-lexer": "^1.4.0", + "bare-mime": "^1.0.0", + "bare-module-lexer": "^1.6.0", "bare-module-resolve": "^1.7.0" }, "peerDependencies": { @@ -3361,19 +3362,19 @@ } }, "node_modules/bare-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz", - "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==", - "license": "Apache-2.0", - "dependencies": { - "bare-os": "^3.0.1" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "license": "Apache-2.0" }, "node_modules/bare-performance": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bare-performance/-/bare-performance-2.0.0.tgz", - "integrity": "sha512-8iCIWtHcChombHshFqYCy0dtFFLUJur9Ahy+T7U49LqrhQ6FtUulat4kGlo6rMkBiBkeFCysTjPvm3nd+cao6w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bare-performance/-/bare-performance-2.1.1.tgz", + "integrity": "sha512-nVlulswnYgXS2Fkbk4ZIKgfIWY/rmeG8ljM9aryPYClgPNzpOgOSLQzVSgU/K+ReLJHq7fEUQ9SBdjEs1QTIfw==", "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.9.1" + }, "engines": { "bare": ">=1.27.0" } @@ -3398,9 +3399,9 @@ "license": "Apache-2.0" }, "node_modules/bare-process": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.0.tgz", - "integrity": "sha512-NTtYDiwleJjWWNg4+yzS6B/RovL/LLPHVvoOM+18W+dYAXCmky0zFfN66hqqBbrlGAlb52/QgKrdzPR3x1QyHA==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.1.tgz", + "integrity": "sha512-CaAvy1trputD49mtwfJ6G75vydhnirLrW/F3Sznp4H556e1uZn8YMo9ELicBTrGYy7RBNUgPl9bpB/ERzRCiDw==", "license": "Apache-2.0", "dependencies": { "bare-abort": "^2.0.13", @@ -3409,7 +3410,7 @@ "bare-hrtime": "^2.0.0", "bare-os": "^3.7.1", "bare-posix": "^1.0.1", - "bare-signals": "^4.0.0", + "bare-signals": "^5.0.0", "bare-stdio": "^1.0.1" } }, @@ -3448,14 +3449,13 @@ } }, "node_modules/bare-repl": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/bare-repl/-/bare-repl-6.1.0.tgz", - "integrity": "sha512-pVTbak3RQCUxC0Z4kiMjS0S+P5wJedU5947yV6rTtOmhmQ62GYdoruPMJGTcSONsX2KJd+lHzxVipoIXSfvfIw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/bare-repl/-/bare-repl-6.1.1.tgz", + "integrity": "sha512-vV52s+zLcwf9WB6y09KwHrqHrCR0UUgcKz3NPhPAwmO62ctFYbBfjskBch92wWk7vRrF68dHJj3hmYNCe8d18g==", "license": "Apache-2.0", "dependencies": { "bare-inspect": "^3.0.0", "bare-module": "^6.4.0", - "bare-os": "^3.0.1", "bare-path": "^3.0.0", "bare-pipe": "^4.0.0", "bare-readline": "^1.0.0", @@ -3470,13 +3470,12 @@ "license": "Apache-2.0" }, "node_modules/bare-signals": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", - "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-5.0.0.tgz", + "integrity": "sha512-8Gn8bBFUh2AUCJ9wWWFjSGWIo1HIUSIQnXRJGSm/f7GCDMqsuJhRmR1dT+HtDaDBkeu2l8Koxt2JLurfJ5yHVw==", "license": "Apache-2.0", "dependencies": { - "bare-events": "^2.5.3", - "bare-os": "^3.3.1" + "bare-events": "^2.5.3" }, "engines": { "bare": ">=1.7.0" @@ -3497,9 +3496,9 @@ } }, "node_modules/bare-stdio": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.2.tgz", - "integrity": "sha512-3WJDqtvVGP4f+j68kyEC05umOYNwKJ1xG+YAXL8yZ605WgNqiRhVaFq+mVIhBt2eKNp7pa5vCQdhOt1pNh79SA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.3.tgz", + "integrity": "sha512-8BRMx7RWMWCbBmKhyHgBco62J+Pgss7+EPI21L3R9+w0UpwDYWUUcbE0YTOesGyt7M6N7VtoshYbzwrP0pI52Q==", "license": "Apache-2.0", "dependencies": { "bare-fs": "^4.5.2", @@ -3612,9 +3611,9 @@ } }, "node_modules/bare-tcp": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.5.1.tgz", - "integrity": "sha512-N32OosAegmGzksr/huxIYLU/uaUARte6utO+vAu/EPQztMM7iAC55VeQSfzr2/XBJoz2OIETMvmU7/sdfiOGjw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.5.2.tgz", + "integrity": "sha512-9SAF0r+p5w3QxsI9BGBqj/0VlNwJGkEj/1AAJi+4Pl0W7ke2JiYmt7GhJss/uaEnY0s4/N/A/Os6Dr+dVfVzyQ==", "license": "Apache-2.0", "dependencies": { "bare-dns": "^2.0.4", @@ -3676,13 +3675,13 @@ } }, "node_modules/bare-tty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.1.tgz", - "integrity": "sha512-cXCMAlcLCFTCWwCtXBIhDuXa4hutTEbrHHIK5pvSSb4BLaSGJ9tvxVN3mvGOvXXqONS7l04mTiP+eIUlZKI92g==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.2.tgz", + "integrity": "sha512-wDHPU/yVQtrDCUrKVdIVOLmfIw7dFPETz30UayuFlROvgz8Juv2SmISSMhqxYQVO+fVnG/1ZoLcbGh/35eAjDA==", "license": "Apache-2.0", "dependencies": { "bare-events": "^2.2.0", - "bare-signals": "^4.0.0", + "bare-signals": "^5.0.0", "bare-stream": "^2.0.0" }, "engines": { @@ -3699,9 +3698,9 @@ } }, "node_modules/bare-type-stripper": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/bare-type-stripper/-/bare-type-stripper-0.1.2.tgz", - "integrity": "sha512-ArhqyRF9PramHfuSkZHp6DeMg8UdXWN2vjH0dvJ8PNJW15UDM6X/cSeT40R1IsKbyFgpM6+lZd2ZBlAKmKaGyg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/bare-type-stripper/-/bare-type-stripper-0.1.4.tgz", + "integrity": "sha512-FdZhp9XEnQpj8AWFmIft/sVUyKS9XSmB6PhcxBHhuEDxxZM5Kkt8+kFS7eEpLXR7TkaRkNpSENoGH/8lpAmtkA==", "license": "Apache-2.0", "dependencies": { "require-addon": "^1.0.2" @@ -3754,9 +3753,9 @@ } }, "node_modules/bare-worker": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/bare-worker/-/bare-worker-4.3.0.tgz", - "integrity": "sha512-aWwGexQlMqXWHH7GjvWrLUKI4o5ZPfNQVCr0QdY79JBu0hKLaRXvwos80YpCgKxl8eSaQtCEQHR0qRAxPvs8zA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/bare-worker/-/bare-worker-4.4.0.tgz", + "integrity": "sha512-zSc1biis9ks03nj/24M7tYS2V0CPhefizzRIxVYdglbbUAgA0zakwSgTKLJshZ6P+9NA55M/eG4k/rBALZVbxg==", "license": "Apache-2.0", "dependencies": { "bare-broadcast-channel": "^0.2.0", @@ -3793,9 +3792,9 @@ } }, "node_modules/bare-zlib": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/bare-zlib/-/bare-zlib-1.4.0.tgz", - "integrity": "sha512-xd0DT6nrj7553IkvfpJJSJiN0SM1OWQ4ycP53DET2zuF9UmDOh5XmWr+cA8pwkvS7YQyGHEm+kwtCZsUctUh5A==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/bare-zlib/-/bare-zlib-1.4.1.tgz", + "integrity": "sha512-CsnQl+XyLaUecB9/OUpjqmemung10M7J2UNXz+6NAVrZAI3HC9c5Kxw34aI0jaU9+gb2yUCD31hOrtPZlnE3bA==", "license": "Apache-2.0", "dependencies": { "bare-stream": "^2.0.0" @@ -3810,9 +3809,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.40", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", - "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3845,9 +3844,9 @@ } }, "node_modules/bn.js": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.4.tgz", - "integrity": "sha512-QL7sb18rJ1PbdsKsqPA0guxL563vIMwRHgzNrW/uzQuRGN1Cjqd/wonUBAVqHox9KwzHA6vCbM0lXx3k4iQMow==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", + "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", "dev": true, "license": "MIT" }, @@ -3926,9 +3925,9 @@ "license": "ISC" }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "dev": true, "funding": [ { @@ -3946,10 +3945,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -4083,9 +4082,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "dev": true, "funding": [ { @@ -4568,9 +4567,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.381", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.381.tgz", - "integrity": "sha512-n9Wa6yB+vDsGuA8AKbl/0z7HbvWqt5jxIdvr1IUicd0ryPrk7/xzwqLv8D9AbbvZ6avVNtXYLTfmgFHkwkyelg==", + "version": "1.5.393", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz", + "integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==", "dev": true, "license": "ISC" }, @@ -4591,9 +4590,9 @@ } }, "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.4", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.4.tgz", - "integrity": "sha512-njR1b+ixG2ufvL9Zn9JGneW+b5GV6jqpYyPPpg4QVt723b5kJPGUczkUyWEH9BwEA74UakJZ43I4FDLBF7ci0g==", + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", "dev": true, "license": "MIT" }, @@ -4760,9 +4759,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.3.tgz", - "integrity": "sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5032,9 +5031,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.13.0.tgz", - "integrity": "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", "dev": true, "license": "MIT", "dependencies": { @@ -6158,9 +6157,9 @@ } }, "node_modules/hardhat/node_modules/ws": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", - "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "dev": true, "license": "MIT", "engines": { @@ -8543,9 +8542,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, "license": "MIT", "engines": { @@ -9534,9 +9533,9 @@ } }, "node_modules/serialize-javascript": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.6.tgz", - "integrity": "sha512-ATTK5Q4gFVg0YDp1my2vqygyvhcklD/UV5GIlYHooGTn/NogJqIzpetkD6E5kmuVULqz/S9inUL25XcAgDRJQg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", + "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -10248,9 +10247,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index b261874..4a6a349 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "dependencies": { "@noble/hashes": "1.8.0", "@noble/secp256k1": "2.2.3", - "@tetherto/wdk-wallet": "1.0.0-beta.13", + "@tetherto/wdk-wallet": "github:claudiovb/wdk-wallet#fix/universal-signer", "@tetherto/wdk-failover-provider": "1.0.0-beta.2", "bare-node-runtime": "^1.5.0", "bip39": "3.1.0", @@ -62,6 +62,7 @@ }, "./signers": { "types": "./types/src/signers/index.d.ts", + "bare": "./src/signers/bare.js", "default": "./src/signers/index.js" }, "./package": { @@ -75,6 +76,7 @@ "standard": { "ignore": [ "bare.js", + "src/signers/bare.js", "tests/**/*.js" ] } diff --git a/src/signers/bare.js b/src/signers/bare.js new file mode 100644 index 0000000..d290f63 --- /dev/null +++ b/src/signers/bare.js @@ -0,0 +1,20 @@ +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict' + +import 'bare-node-runtime/global' + +export * from './index.js' with { imports: 'bare-node-runtime/imports' } + +export { default } from './index.js' with { imports: 'bare-node-runtime/imports' } diff --git a/src/signers/index.js b/src/signers/index.js index 244c5ba..1ebd0ca 100644 --- a/src/signers/index.js +++ b/src/signers/index.js @@ -15,11 +15,11 @@ /** * Barrel exports for EVM signers. * + * - `ISignerEvm`: the interface implemented by all EVM signers. * - `SeedSignerEvm`: derives accounts from a BIP-39 seed (BIP-44 path). * - `PrivateKeySignerEvm`: memory-safe wrapper around a raw private key. */ -/** @typedef {import('../utils/tx-populator-evm.js').UnsignedEvmTransaction} UnsignedEvmTransaction */ - +export { ISignerEvm } from './signer-evm.js' export { default, default as SeedSignerEvm } from './seed-signer-evm.js' export { default as PrivateKeySignerEvm } from './private-key-signer-evm.js' diff --git a/src/signers/private-key-signer-evm.js b/src/signers/private-key-signer-evm.js index 9aead87..5b069c8 100644 --- a/src/signers/private-key-signer-evm.js +++ b/src/signers/private-key-signer-evm.js @@ -18,29 +18,28 @@ import { BaseWallet } from 'ethers' import { SignerError } from '@tetherto/wdk-wallet' import MemorySafeSigningKey from '../memory-safe/signing-key.js' -import { ISignerEvm } from './seed-signer-evm.js' -/** @typedef {import('../utils/tx-populator-evm.js').UnsignedEvmTransaction} UnsignedEvmTransaction */ +/** @typedef {import('./signer-evm.js').ISignerEvm} ISignerEvm */ /** @typedef {import('@tetherto/wdk-wallet').KeyPair} KeyPair */ +/** @typedef {import('ethers').TransactionLike} TransactionLike */ /** @typedef {import('ethers').AuthorizationRequest} AuthorizationRequest */ /** @typedef {import('ethers').Authorization} Authorization */ /** @typedef {import('../wallet-account-read-only-evm.js').TypedData} TypedData */ /** - * @extends {ISignerEvm} * Signer that wraps a raw private key in a memory-safe buffer, exposing a minimal * interface for signing messages, transactions and typed data. This signer does * not support derivation and always represents a single account. + * + * @implements {ISignerEvm} */ -export default class PrivateKeySignerEvm extends ISignerEvm { +export default class PrivateKeySignerEvm { /** * Create a signer from a raw private key. * * @param {string|Uint8Array} privateKey - Hex string (with/without 0x) or raw key bytes. */ constructor (privateKey) { - super() - // Expect a Uint8Array buffer; accept hex string as convenience let privateKeyBuffer = privateKey if (typeof privateKey === 'string') { @@ -54,37 +53,33 @@ export default class PrivateKeySignerEvm extends ISignerEvm { this._wallet = new BaseWallet(this._signingKey, null) /** @private */ this._address = this._wallet.address - /** @private */ - this._path = undefined } /** * Whether this signer can derive child signers. Always false: a private-key signer is a * single standalone account and is bound directly to a wallet account. + * * @type {boolean} */ get isDerivable () { return false } /** - * The account index. Always undefined for private key signers: a raw key has no - * BIP-44 position, so reporting an index would be misleading. - * @type {number|undefined} - */ - get index () { return undefined } - - /** - * The derivation path. Always undefined for private key signers. - * @type {string|undefined} + * The BIP 0044 derivation path. + * + * @type {string | null} */ - get path () { return this._path } + get path () { return null } /** * The account's address. + * * @type {string} */ get address () { return this._address } + /** * The account's key pair (private and public key buffers). + * * @type {KeyPair} */ get keyPair () { @@ -95,15 +90,21 @@ export default class PrivateKeySignerEvm extends ISignerEvm { } /** - * PrivateKeySignerEvm is not a hierarchical signer and cannot derive. - * @returns {Promise} - * @throws {SignerError} Always — private-key signers do not support derivation. + * Derive a child signer using a relative path (e.g., "0'/0/0"). + * + * @param {string} relPath - The relative derivation path. + * @returns {Promise} The derived signer. + * @throws {SignerError} If the signer does not support derivation. */ - async derive () { + async derive (relPath) { throw new SignerError('PrivateKeySignerEvm does not support derivation.') } - /** @returns {Promise} */ + /** + * Returns the account's address. + * + * @returns {Promise} The account's address. + */ async getAddress () { return this._address } @@ -119,13 +120,13 @@ export default class PrivateKeySignerEvm extends ISignerEvm { } /** - * Signs a transaction and returns the serialized signed transaction hex. + * Signs a transaction. * - * @param {UnsignedEvmTransaction} unsignedTx - The unsigned transaction object. - * @returns {Promise} + * @param {TransactionLike} tx - The transaction to sign. + * @returns {Promise} The signed transaction as a hex string. */ - async signTransaction (unsignedTx) { - return this._wallet.signTransaction(unsignedTx) + async signTransaction (tx) { + return this._wallet.signTransaction(tx) } /** @@ -139,15 +140,18 @@ export default class PrivateKeySignerEvm extends ISignerEvm { } /** - * Sign an ERC-7702 authorization tuple. - * @param {AuthorizationRequest} auth - * @returns {Promise} + * Signs an ERC-7702 authorization tuple. + * + * @param {AuthorizationRequest} auth - The authorization request. + * @returns {Promise} The signed authorization. */ async signAuthorization (auth) { return this._wallet.authorizeSync(auth) } - /** Dispose secrets from memory. */ + /** + * Disposes the signer, erasing its secrets from memory. + */ dispose () { if (this._signingKey) this._signingKey.dispose() this._signingKey = undefined diff --git a/src/signers/seed-signer-evm.js b/src/signers/seed-signer-evm.js index ca88467..8f56751 100644 --- a/src/signers/seed-signer-evm.js +++ b/src/signers/seed-signer-evm.js @@ -16,17 +16,15 @@ import * as bip39 from 'bip39' import MemorySafeHDNodeWallet from '../memory-safe/hd-node-wallet.js' -import { ISigner, NotImplementedError } from '@tetherto/wdk-wallet' const BIP_44_ETH_DERIVATION_PATH_PREFIX = "m/44'/60'" // Relative path of the account derived when none is provided. const DEFAULT_ACCOUNT_PATH = "0'/0/0" -/** @typedef {import('../utils/tx-populator-evm.js').UnsignedEvmTransaction} UnsignedEvmTransaction */ -/** @typedef {import('@tetherto/wdk-wallet').ISigner} ISigner */ -/** @typedef {import('@tetherto/wdk-wallet').SignerError} SignerError */ +/** @typedef {import('./signer-evm.js').ISignerEvm} ISignerEvm */ /** @typedef {import('@tetherto/wdk-wallet').KeyPair} KeyPair */ +/** @typedef {import('ethers').TransactionLike} TransactionLike */ /** @typedef {import('ethers').AuthorizationRequest} AuthorizationRequest */ /** @typedef {import('ethers').Authorization} Authorization */ /** @typedef {import('../wallet-account-read-only-evm.js').TypedData} TypedData */ @@ -40,135 +38,22 @@ const DEFAULT_ACCOUNT_PATH = "0'/0/0" */ /** - * Interface for EVM signers, extending the base `ISigner` from `@tetherto/wdk-wallet`. - * - * @extends {ISigner} - * @interface - */ -export class ISignerEvm extends ISigner { - /** - * Whether this signer can derive child signers (i.e. it holds an HD root). Non-derivable - * signers (e.g. private-key signers) are bound directly to an account; derivable signers - * derive child accounts and keep the root for management only. - * @type {boolean} - */ - get isDerivable () { - throw new NotImplementedError('isDerivable') - } - - /** - * The last component index for the derivation path of this signer, when applicable. - * @type {number|undefined} - */ - get index () { - throw new NotImplementedError('index') - } - - /** - * The full derivation path if this is a child signer. - * @type {string|undefined} - */ - get path () { - throw new NotImplementedError('path') - } - - /** - * The account's address, if available. - * @type {string|undefined} - */ - get address () { - throw new NotImplementedError('address') - } - - /** - * The account's key pair. - * @type {KeyPair} - */ - get keyPair () { - throw new NotImplementedError('keyPair') - } - - /** - * Derive a child signer from this signer using a relative path (e.g. "0'/0/0"). - * - * @param {string} relPath - The relative BIP-44 path segment. - * @returns {Promise} The derived child signer. - * @throws {SignerError} If the signer does not support derivation (e.g. private-key signers). - */ - async derive (relPath) { - throw new NotImplementedError('derive(relPath)') - } - - /** - * Returns the account's address. - * @returns {Promise} - */ - async getAddress () { - throw new NotImplementedError('getAddress()') - } - - /** - * Sign a plain message. - * @param {string} message - * @returns {Promise} - */ - async sign (message) { - throw new NotImplementedError('sign(message)') - } - - /** - * Sign a transaction-like object compatible with ethers Transaction.from. - * @param {UnsignedEvmTransaction} unsignedTx - * @returns {Promise} The serialized signed transaction hex. - */ - async signTransaction (unsignedTx) { - throw new NotImplementedError('signTransaction(unsignedTx)') - } - - /** - * Signs typed data according to EIP-712. - * - * @param {TypedData} typedData - The typed data to sign. - * @returns {Promise} The typed data signature. - */ - async signTypedData ({ domain, types, message }) { - throw new NotImplementedError('signTypedData(typedData)') - } - - /** - * Sign an ERC-7702 authorization tuple. - * @param {AuthorizationRequest} auth - * @returns {Promise} - */ - async signAuthorization (auth) { - throw new NotImplementedError('signAuthorization(auth)') - } - - /** Clear any secret material from memory. */ - dispose () { - throw new NotImplementedError('dispose()') - } -} - -/** - * @extends {ISignerEvm} * Signer implementation that derives keys from a BIP-39 seed using the BIP-44 Ethereum path. * Always holds a derived account (index 0 by default). A root signer also retains the HD root * and can derive child signers; a derived child holds only its own account. + * + * @implements {ISignerEvm} */ -export default class SeedSignerEvm extends ISignerEvm { +export default class SeedSignerEvm { /** * Create a SeedSignerEvm. - * Provide either a mnemonic/seed or an existing root via opts.root (for children root is not stored internally) * * @param {string|Uint8Array|null} seed - BIP-39 mnemonic or seed bytes. Omit when providing `opts.root`. * @param {SeedSignerEvmOpts} [opts] - Construction options for root reuse, direct child derivation or path definition (default is index 0). - * @throws {Error} If neither a seed nor a root is provided, or if both are provided. - * @throws {Error} If a seed is provided but is not a valid BIP-39 mnemonic. + * @throws {Error} If neither a seed nor a root is provided, or if both are provided. + * @throws {Error} If a seed is provided but is not a valid BIP-39 mnemonic. */ constructor (seed, opts = {}) { - super() - // If a root is provided, do not expect a seed if (opts.root && seed) { throw new Error('Provide either a seed or a root, not both.') @@ -205,6 +90,7 @@ export default class SeedSignerEvm extends ISignerEvm { /** * Whether this signer can derive child signers. True for a root signer (which holds the * HD root); false for a derived child, which does not retain the root. + * * @type {boolean} */ get isDerivable () { @@ -212,17 +98,9 @@ export default class SeedSignerEvm extends ISignerEvm { } /** - * The last component index of the derivation path, if available. - * @type {number|undefined} - */ - get index () { - if (!this._path) return undefined - return +this._path.split('/').pop() - } - - /** - * The full derivation path of this signer's account. - * @type {string|undefined} + * The BIP 0044 derivation path. + * + * @type {string} */ get path () { return this._path @@ -230,6 +108,7 @@ export default class SeedSignerEvm extends ISignerEvm { /** * The account's derived address. + * * @type {string} */ get address () { @@ -238,6 +117,7 @@ export default class SeedSignerEvm extends ISignerEvm { /** * The account's key pair (private and public key buffers). + * * @type {KeyPair} */ get keyPair () { @@ -249,8 +129,9 @@ export default class SeedSignerEvm extends ISignerEvm { /** * Derive a child signer using the provided relative path (e.g. "0'/0/0"). - * @param {string} relPath - * @returns {Promise} + * + * @param {string} relPath - The relative BIP-44 path segment. + * @returns {Promise} The derived child signer. * @throws {Error} If called on a derived child signer, which does not retain the root. */ async derive (relPath) { @@ -262,28 +143,31 @@ export default class SeedSignerEvm extends ISignerEvm { /** * Returns the account's derived address. - * @returns {Promise} + * + * @returns {Promise} The account's address. */ async getAddress () { return this._address } /** - * Sign a plain message string. - * @param {string} message - * @returns {Promise} + * Signs a message. + * + * @param {string} message - The message to sign. + * @returns {Promise} The message's signature. */ async sign (message) { return this._account.signMessage(message) } /** - * Sign a transaction object and return its serialized form. - * @param {UnsignedEvmTransaction} unsignedTx - * @returns {Promise} + * Signs a transaction. + * + * @param {TransactionLike} tx - The transaction to sign. + * @returns {Promise} The signed transaction as a hex string. */ - async signTransaction (unsignedTx) { - return this._account.signTransaction(unsignedTx) + async signTransaction (tx) { + return this._account.signTransaction(tx) } /** @@ -297,15 +181,18 @@ export default class SeedSignerEvm extends ISignerEvm { } /** - * Sign an ERC-7702 authorization tuple. - * @param {AuthorizationRequest} auth - * @returns {Promise} + * Signs an ERC-7702 authorization tuple. + * + * @param {AuthorizationRequest} auth - The authorization request. + * @returns {Promise} The signed authorization. */ async signAuthorization (auth) { return this._account.authorizeSync(auth) } - /** Disposes secrets from memory. */ + /** + * Disposes the signer, erasing its secrets from memory. + */ dispose () { if (this._account) this._account.dispose() this._account = undefined diff --git a/src/signers/signer-evm.js b/src/signers/signer-evm.js new file mode 100644 index 0000000..8ea6dd4 --- /dev/null +++ b/src/signers/signer-evm.js @@ -0,0 +1,67 @@ +// Copyright 2024 Tether Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict' + +import { ISigner, NotImplementedError } from '@tetherto/wdk-wallet' + +/** @typedef {import('ethers').TransactionLike} TransactionLike */ +/** @typedef {import('ethers').AuthorizationRequest} AuthorizationRequest */ +/** @typedef {import('ethers').Authorization} Authorization */ +/** @typedef {import('../wallet-account-read-only-evm.js').TypedData} TypedData */ + +/** + * Interface for EVM signers, extending the base `ISigner` from `@tetherto/wdk-wallet`. + * + * @interface + */ +export class ISignerEvm extends ISigner { + /** + * The account's address, if available. + * + * @type {string | undefined} + */ + get address () { + throw new NotImplementedError('address') + } + + /** + * Signs a transaction. + * + * @param {TransactionLike} tx - The transaction to sign. + * @returns {Promise} The signed transaction as a hex string. + */ + async signTransaction (tx) { + throw new NotImplementedError('signTransaction(tx)') + } + + /** + * Signs typed data according to EIP-712. + * + * @param {TypedData} typedData - The typed data to sign. + * @returns {Promise} The typed data signature. + */ + async signTypedData (typedData) { + throw new NotImplementedError('signTypedData(typedData)') + } + + /** + * Signs an ERC-7702 authorization tuple. + * + * @param {AuthorizationRequest} auth - The authorization request. + * @returns {Promise} The signed authorization. + */ + async signAuthorization (auth) { + throw new NotImplementedError('signAuthorization(auth)') + } +} diff --git a/src/utils/tx-populator-evm.js b/src/utils/tx-populator-evm.js deleted file mode 100644 index 8f500af..0000000 --- a/src/utils/tx-populator-evm.js +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2024 Tether Operations Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -'use strict' - -import { Signature, toQuantity } from 'ethers' - -/** @typedef {import('ethers').Provider} Provider */ -/** @typedef {import('ethers').AuthorizationLike} AuthorizationLike */ - -/** - * A fully or partially specified EVM transaction, prior to signing. - * - * @typedef {Object} UnsignedEvmTransaction - * @property {number} chainId - The id of the chain the transaction targets. - * @property {number} nonce - The sender's transaction count, used to order transactions. - * @property {string} from - The sender's address. - * @property {string|null} to - The recipient's address, or null for contract creation. - * @property {string} data - The transaction's calldata as a hex string. - * @property {number|bigint} value - The amount of native currency (in wei) to transfer. - * @property {number} type - The EIP-2718 transaction type (0/1 legacy, 2 EIP-1559, 3 EIP-4844, 4 EIP-7702). - * @property {number|bigint} gasLimit - The maximum amount of gas the transaction may consume. - * @property {number|bigint} [gasPrice] - The gas price (in wei) for legacy (type 0/1) transactions. - * @property {number|bigint} [maxFeePerGas] - The maximum total fee (in wei) per gas for EIP-1559 transactions. - * @property {number|bigint} [maxPriorityFeePerGas] - The maximum priority fee (in wei) per gas for EIP-1559 transactions. - * @property {any[]} [accessList] - The EIP-2930 access list of addresses and storage keys. - * @property {number|bigint} [maxFeePerBlobGas] - The maximum fee (in wei) per blob gas for EIP-4844 transactions. - * @property {any[]} [blobs] - The blobs to include in an EIP-4844 transaction. - * @property {string[]} [blobVersionedHashes] - The versioned hashes of the EIP-4844 blobs. - * @property {AuthorizationLike[]} [authorizationList] - The EIP-7702 authorization tuples. - */ - -/** - * Build a fully populated unsigned transaction ready for signing. - * - * Resolves chain ID, nonce, gas limit and fee fields from the provider when not - * explicitly supplied in `tx`. Supports legacy (type 0/1), EIP-1559 (type 2), - * EIP-4844 (type 3) and EIP-7702 (type 4) transaction styles. - * - * @param {Provider} provider - An ethers-compatible JSON-RPC provider. - * @param {string} from - The sender address. - * @param {UnsignedEvmTransaction} tx - The partial transaction to populate. - * @returns {Promise} The fully populated unsigned transaction. - */ -export async function populateTransactionEvm (provider, from, tx) { - const net = await provider.getNetwork() - const chainId = Number(net.chainId) - - const has1559 = (tx.maxFeePerGas != null || tx.maxPriorityFeePerGas != null) - const hasLegacy = (tx.gasPrice != null) - const hasAccessList = (tx.accessList != null && Array.isArray(tx.accessList)) - const hasBlobs = (tx.blobs != null || tx.blobVersionedHashes != null || tx.maxFeePerBlobGas != null) - const hasAuthList = (tx.authorizationList != null && Array.isArray(tx.authorizationList)) - - const explicitType = (tx.type != null) ? Number(tx.type) : null - - if ((explicitType === 2 || (explicitType == null && has1559)) && hasLegacy) { - throw new Error('eip-1559 transaction does not support gasPrice') - } - if ((explicitType === 0 || explicitType === 1) && has1559) { - throw new Error('pre-eip-1559 transaction does not support maxFeePerGas/maxPriorityFeePerGas') - } - if ((explicitType === 3 || hasBlobs) && hasLegacy) { - throw new Error('blob transaction does not support gasPrice') - } - - const feeData = await provider.getFeeData() - - let type = explicitType - if (type == null) { - if (hasAuthList) { - type = 4 - } else if (hasBlobs) { - type = 3 - } else if (feeData.maxFeePerGas != null && feeData.maxPriorityFeePerGas != null) { - type = 2 - } else { - type = 0 - } - } - - let gasLimit - if (tx.gasLimit != null) { - gasLimit = tx.gasLimit - } else if (hasAuthList) { - gasLimit = await _estimateGasWithAuthList(provider, { from, ...tx }) - } else { - gasLimit = await provider.estimateGas({ from, to: tx.to ?? null, data: tx.data ?? '0x', value: tx.value ?? 0 }) - } - - const populated = { - from, - to: tx.to ?? null, - data: tx.data ?? '0x', - value: tx.value ?? 0, - chainId, - nonce: (tx.nonce != null) ? Number(tx.nonce) : Number(await provider.getTransactionCount(from, 'pending')), - gasLimit - } - - if (type === 0 || type === 1) { - populated.type = type - populated.gasPrice = tx.gasPrice ?? feeData.gasPrice ?? feeData.maxFeePerGas - if (type === 1 && hasAccessList) populated.accessList = tx.accessList - return populated - } - - if (type === 2) { - populated.type = 2 - if (tx.gasPrice != null) { - populated.maxFeePerGas = tx.gasPrice - populated.maxPriorityFeePerGas = tx.gasPrice - } else { - populated.maxFeePerGas = tx.maxFeePerGas ?? feeData.maxFeePerGas - populated.maxPriorityFeePerGas = tx.maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas - } - if (hasAccessList) populated.accessList = tx.accessList - return populated - } - - if (type === 3) { - populated.type = 3 - populated.maxFeePerGas = tx.maxFeePerGas ?? feeData.maxFeePerGas - populated.maxPriorityFeePerGas = tx.maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas - if (tx.maxFeePerBlobGas == null) throw new Error('maxFeePerBlobGas is required for type 3 transactions') - populated.maxFeePerBlobGas = tx.maxFeePerBlobGas - if (tx.blobs != null) populated.blobs = tx.blobs - if (tx.blobVersionedHashes != null) populated.blobVersionedHashes = tx.blobVersionedHashes - if (hasAccessList) populated.accessList = tx.accessList - return populated - } - - // Type 4 (EIP-7702) and future types; pass-through - populated.type = type - if (hasAccessList) populated.accessList = tx.accessList - if (hasLegacy) { - populated.gasPrice = tx.gasPrice - } else { - populated.maxFeePerGas = tx.maxFeePerGas ?? feeData.maxFeePerGas - populated.maxPriorityFeePerGas = tx.maxPriorityFeePerGas ?? feeData.maxPriorityFeePerGas - } - if (hasBlobs) { - populated.maxFeePerBlobGas = tx.maxFeePerBlobGas - if (tx.blobs != null) populated.blobs = tx.blobs - if (tx.blobVersionedHashes != null) populated.blobVersionedHashes = tx.blobVersionedHashes - } - if (tx.authorizationList != null) populated.authorizationList = tx.authorizationList - - return populated -} - -async function _estimateGasWithAuthList (provider, { from, to, value, data, authorizationList }) { - const formatAuth = (auth) => { - const { address, nonce, chainId } = auth - const signature = auth.signature instanceof Signature - ? auth.signature - : Signature.from(auth.signature) - return { - address, - nonce: toQuantity(nonce), - chainId: toQuantity(chainId), - r: toQuantity(signature.r), - s: toQuantity(signature.s), - yParity: toQuantity(signature.yParity) - } - } - const rpcTx = { - from, - to, - value: toQuantity(value ?? 0), - data: data ?? '0x', - type: '0x04', - authorizationList: authorizationList.map(formatAuth) - } - const result = await provider.send('eth_estimateGas', [rpcTx]) - return BigInt(result) -} diff --git a/src/wallet-account-evm.js b/src/wallet-account-evm.js index 0286c24..5e98251 100644 --- a/src/wallet-account-evm.js +++ b/src/wallet-account-evm.js @@ -14,15 +14,14 @@ 'use strict' -import { Contract, ZeroAddress } from 'ethers' +import { Contract, VoidSigner, ZeroAddress } from 'ethers' import WalletAccountReadOnlyEvm from './wallet-account-read-only-evm.js' import SeedSignerEvm from './signers/seed-signer-evm.js' import PrivateKeySignerEvm from './signers/private-key-signer-evm.js' -import { populateTransactionEvm } from './utils/tx-populator-evm.js' -/** @typedef {import('./signers/seed-signer-evm.js').ISignerEvm} ISignerEvm */ +/** @typedef {import('./signers/signer-evm.js').ISignerEvm} ISignerEvm */ /** @typedef {import('ethers').HDNodeWallet} HDNodeWallet */ /** @typedef {import('ethers').AuthorizationRequest} AuthorizationRequest */ /** @typedef {import('ethers').Authorization} Authorization */ @@ -90,18 +89,10 @@ export default class WalletAccountEvm extends WalletAccountReadOnlyEvm { } /** - * The derivation path's index of this account. + * The derivation path of this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)), + * or null if the account's signer is not bound to a BIP-44 position (e.g. private-key signers). * - * @type {number} - */ - get index () { - return this._signer.index - } - - /** - * The derivation path of this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)). - * - * @type {string} + * @type {string | null} */ get path () { return this._signer.path @@ -114,7 +105,7 @@ export default class WalletAccountEvm extends WalletAccountReadOnlyEvm { * it's strongly recommended to treat the key pair as a read-only view of the keys. While it's still technically possible to alter their * content, client code should never do so. * - * @type {KeyPair} + * @type {KeyPair | null} */ get keyPair () { return this._signer.keyPair @@ -133,8 +124,7 @@ export default class WalletAccountEvm extends WalletAccountReadOnlyEvm { } /** - * Returns the account's address. If it wasn't resolved at construction time (e.g hardware signers), it asks the - * underlying signer to resolve it, then caches it locally. + * Returns the account's address. * * @returns {Promise} The account's address. */ @@ -204,9 +194,10 @@ export default class WalletAccountEvm extends WalletAccountReadOnlyEvm { throw new Error('Exceeded maximum fee cost for transaction operation.') } // Build, sign and broadcast raw transaction using the signer - const from = await this.getAddress() - const unsignedTx = await populateTransactionEvm(this._provider, from, tx) - const signed = await this._signer.signTransaction(unsignedTx) + const address = await this.getAddress() + const voidSigner = new VoidSigner(address, this._provider) + const populated = await voidSigner.populateTransaction(tx) + const signed = await this._signer.signTransaction(populated) const hash = await this._provider.send('eth_sendRawTransaction', [signed]) return { hash, fee } } @@ -288,12 +279,18 @@ export default class WalletAccountEvm extends WalletAccountReadOnlyEvm { /** * Signs an ERC-7702 authorization tuple. * + * The chainId and nonce are populated from the provider when not explicitly provided. + * * @param {AuthorizationRequest} auth - The authorization request. * @returns {Promise} The signed authorization. + * @throws {Error} If the chainId or nonce are not provided and the wallet is not connected to a provider. */ async signAuthorization (auth) { const populated = { ...auth } - if (this._provider) { + if (populated.chainId == null || populated.nonce == null) { + if (!this._provider) { + throw new Error('The wallet must be connected to a provider to populate the authorization chainId and nonce. Provide them explicitly to sign offline.') + } if (populated.chainId == null) { const { chainId } = await this._provider.getNetwork() populated.chainId = chainId diff --git a/src/wallet-manager-evm.js b/src/wallet-manager-evm.js index 859b6fc..a9262a4 100644 --- a/src/wallet-manager-evm.js +++ b/src/wallet-manager-evm.js @@ -14,7 +14,7 @@ 'use strict' -import WalletManager from '@tetherto/wdk-wallet' +import WalletManager, { SignerError } from '@tetherto/wdk-wallet' import { BrowserProvider, JsonRpcProvider } from 'ethers' @@ -23,7 +23,7 @@ import FailoverProvider from '@tetherto/wdk-failover-provider' import WalletAccountEvm from './wallet-account-evm.js' import SeedSignerEvm from './signers/seed-signer-evm.js' -/** @typedef {import('./signers/seed-signer-evm.js').ISignerEvm} ISignerEvm */ +/** @typedef {import('./signers/signer-evm.js').ISignerEvm} ISignerEvm */ /** @typedef {import('ethers').Provider} Provider */ /** @typedef {import("@tetherto/wdk-wallet").FeeRates} FeeRates */ @@ -50,15 +50,26 @@ export default class WalletManagerEvm extends WalletManager { static _FEE_RATE_FAST_MULTIPLIER = 200n /** - * Creates a new wallet manager for evm blockchains. + * Creates a new wallet manager for evm blockchains from a BIP-39 seed. * - * Accepts either a BIP-39 seed (string/Uint8Array) for backwards compatibility, or a - * pre-built root signer object. The default signer must be derivable (it must be able to - * derive child accounts); non-derivable signers (e.g. private-key signers) are not allowed - * as the default but may be registered by name via {@link addSigner} - If not adding to your global account managment for using just one non derivable signer create a standalone account. + * @overload + * @param {string | Uint8Array} seed - The BIP-39 seed phrase or raw seed bytes. + * @param {EvmWalletConfig} [config] - The configuration object. + * @throws {Error} If the seed phrase is invalid. + */ + + /** + * Creates a new wallet manager for evm blockchains from a default signer. + * + * The default signer must be derivable (it must be able to derive child accounts); + * non-derivable signers (e.g. private-key signers) are not allowed as the default but + * may be registered by name via {@link addSigner}. To use a single non-derivable signer + * outside of the wallet manager, create a standalone account instead. * - * @param {string|Uint8Array|ISigner} seedOrSigner - A BIP-39 seed phrase, seed bytes, or a root signer. Root signers must be derivable — non-derivable signers (e.g. private-key signers) can only be registered by name via {@link addSigner}. + * @overload + * @param {ISigner} signer - The default signer. * @param {EvmWalletConfig} [config] - The configuration object. + * @throws {SignerError} If the default signer does not support account derivation. */ constructor (seedOrSigner, config = {}) { let signer = seedOrSigner @@ -66,7 +77,7 @@ export default class WalletManagerEvm extends WalletManager { signer = new SeedSignerEvm(seedOrSigner) } if (!signer.isDerivable) { - throw new Error('The default signer must be derivable. Non-derivable signers (e.g. private-key signers) can only be registered by name via addSigner.') + throw new SignerError('The default signer must be derivable. Non-derivable signers (e.g. private-key signers) can only be registered by name via addSigner.') } super(signer, config) @@ -122,9 +133,7 @@ export default class WalletManagerEvm extends WalletManager { */ /** - * Returns the wallet account associated with a registered signer. Non-derivable - * signers (e.g. private-key signers) return the signer's single account; derivable signers - * derive a detached child at the signer's own account (the root is never handed out). + * Returns the wallet account associated with a registered signer. * * @overload * @param {string} signerName - The signer name registered via {@link addSigner}. @@ -134,7 +143,7 @@ export default class WalletManagerEvm extends WalletManager { async getAccount (indexOrSignerName = 0, options = {}) { if (typeof indexOrSignerName === 'string') { - const key = `${indexOrSignerName}#self` + const key = indexOrSignerName if (this._accounts[key]) { return this._accounts[key] } @@ -163,7 +172,7 @@ export default class WalletManagerEvm extends WalletManager { */ async getAccountByPath (path, options = {}) { const { signerName } = options - const key = `${signerName ?? ''}:${path}` + const key = signerName ? `${signerName}:${path}` : path if (this._accounts[key]) { return this._accounts[key] } diff --git a/tests/integration/module.test.js b/tests/integration/module.test.js index 34b8aca..c5e39c5 100644 --- a/tests/integration/module.test.js +++ b/tests/integration/module.test.js @@ -5,7 +5,7 @@ import { ContractFactory } from 'ethers' import { describe, expect, test, beforeEach, afterEach } from '@jest/globals' import WalletManagerEvm from '../../index.js' -import SeedSignerEvm from '../../src/signers/seed-signer-evm.js' +import PrivateKeySignerEvm from '../../src/signers/private-key-signer-evm.js' import TestToken from './../artifacts/TestToken.json' with { type: 'json' } @@ -70,7 +70,7 @@ describe('@tetherto/wdk-wallet-evm', () => { await sendTestTokensTo(account.address, INITIAL_TOKEN_BALANCE) } - wallet = new WalletManagerEvm(new SeedSignerEvm(SEED_PHRASE), { provider: hre.network.provider }) + wallet = new WalletManagerEvm(SEED_PHRASE, { provider: hre.network.provider }) }) afterEach(async () => { @@ -294,7 +294,7 @@ describe('@tetherto/wdk-wallet-evm', () => { }) test('should create a wallet with a low transfer max fee, derive an account, try to transfer some tokens and gracefully fail', async () => { - const wallet = new WalletManagerEvm(new SeedSignerEvm(SEED_PHRASE), { provider: hre.network.provider, transferMaxFee: 0 }) + const wallet = new WalletManagerEvm(SEED_PHRASE, { provider: hre.network.provider, transferMaxFee: 0 }) const account = await wallet.getAccount(0) @@ -308,6 +308,29 @@ describe('@tetherto/wdk-wallet-evm', () => { .rejects.toThrow('Exceeded maximum fee cost for transfer operation.') }) + test('should initialize an account with a non-derivable signer and send a transaction', async () => { + const pkSigner = new PrivateKeySignerEvm(ACCOUNT_0.keyPair.privateKey) + wallet.addSigner('imported', pkSigner) + + const account = await wallet.getAccount('imported') + + expect(await account.getAddress()).toBe(ACCOUNT_0.address) + + const TRANSACTION = { + to: '0xa460AEbce0d3A4BecAd8ccf9D6D4861296c503Bd', + value: 1_000 + } + + const { hash, fee } = await account.sendTransaction(TRANSACTION) + + const transaction = await hre.ethers.provider.getTransaction(hash) + + expect(transaction.hash).toBe(hash) + expect(transaction.to).toBe(TRANSACTION.to) + expect(transaction.value).toBe(BigInt(TRANSACTION.value)) + expect(typeof fee).toBe('bigint') + }) + test('should sign a transaction, then broadcast manually', async () => { const account = await wallet.getAccount(0) diff --git a/tests/signers.test.js b/tests/signers.test.js deleted file mode 100644 index 627b1aa..0000000 --- a/tests/signers.test.js +++ /dev/null @@ -1,197 +0,0 @@ -import * as bip39 from 'bip39' - -import { describe, expect, test } from '@jest/globals' - -import SeedSignerEvm from '../src/signers/seed-signer-evm.js' -import PrivateKeySignerEvm from '../src/signers/private-key-signer-evm.js' - -const VALID_SEED_PHRASE = 'cook voyage document eight skate token alien guide drink uncle term abuse' -const VALID_SEED = bip39.mnemonicToSeedSync(VALID_SEED_PHRASE) -const VALID_PRIVATE_KEY = '260905feebf1ec684f36f1599128b85f3a26c2b817f2065a2fc278398449c41f' -const EXPECTED_PUBLIC_KEY = '036c082582225926b9356d95b91a4acffa3511b7cc2a14ef5338c090ea2cc3d0aa' - -const MESSAGE = 'Dummy message to sign.' -const EXPECTED_SIGNATURE = '0xd130f94c52bf393206267278ac0b6009e14f11712578e5c1f7afe4a12685c5b96a77a0832692d96fc51f4bd403839572c55042ecbcc92d215879c5c8bb5778c51c' - -const EXPECTED_ADDRESS = '0x405005C7c4422390F4B334F64Cf20E0b767131d0' - -describe('SeedSignerEvm', () => { - test('should throw if the seed phrase is invalid', () => { - expect(() => { new SeedSignerEvm('invalid seed phrase') }) // eslint-disable-line no-new - .toThrow('The seed phrase is invalid.') - }) - - test('should throw if the path is invalid', async () => { - await expect(new SeedSignerEvm(VALID_SEED_PHRASE).derive("a'/b/c")) - .rejects.toThrow('invalid path component') - }) - - test('should throw if both seed and root are provided', async () => { - const root = new SeedSignerEvm(VALID_SEED_PHRASE) - const child = await root.derive("0'/0/0") - expect(() => { new SeedSignerEvm(VALID_SEED_PHRASE, { root: child }) }) // eslint-disable-line no-new - .toThrow('Provide either a seed or a root, not both.') - child.dispose() - root.dispose() - }) - - test('should create a signer with the account at index 0 by default', () => { - const signer = new SeedSignerEvm(VALID_SEED_PHRASE) - - expect(signer.isDerivable).toBe(true) - expect(signer.address).toBe(EXPECTED_ADDRESS) - expect(signer.path).toBe("m/44'/60'/0'/0/0") - expect(signer.index).toBe(0) - - signer.dispose() - }) - - test('should derive a child signer with the correct address and path', async () => { - const root = new SeedSignerEvm(VALID_SEED_PHRASE) - const child = await root.derive("0'/0/0") - - expect(child.isDerivable).toBe(false) - expect(child.address).toBe(EXPECTED_ADDRESS) - expect(child.path).toBe("m/44'/60'/0'/0/0") - expect(child.index).toBe(0) - expect(Buffer.from(child.keyPair.privateKey).toString('hex')).toBe(VALID_PRIVATE_KEY) - expect(Buffer.from(child.keyPair.publicKey).toString('hex')).toBe(EXPECTED_PUBLIC_KEY) - - child.dispose() - root.dispose() - }) - - test('should derive the same address from raw seed bytes', async () => { - const root = new SeedSignerEvm(VALID_SEED) - const child = await root.derive("0'/0/0") - - expect(child.address).toBe(EXPECTED_ADDRESS) - - child.dispose() - root.dispose() - }) - - test('should derive the same address when path is provided via constructor opts', () => { - const signer = new SeedSignerEvm(VALID_SEED_PHRASE, { path: "0'/0/0" }) - - expect(signer.address).toBe(EXPECTED_ADDRESS) - - signer.dispose() - }) - - test('should throw when deriving from a disposed signer', async () => { - const root = new SeedSignerEvm(VALID_SEED_PHRASE) - root.dispose() - - await expect(root.derive("0'/0/0")).rejects.toThrow('Cannot derive: this signer has no root') - }) - - test('should return the correct signature', async () => { - const child = await new SeedSignerEvm(VALID_SEED_PHRASE).derive("0'/0/0") - - const signature = await child.sign(MESSAGE) - expect(signature).toBe(EXPECTED_SIGNATURE) - - child.dispose() - }) - - test('should return the address via getAddress()', async () => { - const child = await new SeedSignerEvm(VALID_SEED_PHRASE).derive("0'/0/0") - - const address = await child.getAddress() - expect(address).toBe(EXPECTED_ADDRESS) - - child.dispose() - }) - - test('should clear secrets on dispose', async () => { - const root = new SeedSignerEvm(VALID_SEED_PHRASE) - const child = await root.derive("0'/0/0") - - child.dispose() - - expect(child.keyPair.privateKey).toBeNull() - }) - - test('should not neuter the shared root when a derived child is disposed', async () => { - const root = new SeedSignerEvm(VALID_SEED_PHRASE) - const a = await root.derive("0'/0/0") - const b = await root.derive("0'/0/1") - - const signature = await b.sign(MESSAGE) - - a.dispose() - - // The sibling still signs and the root can still derive new children. - await expect(b.sign(MESSAGE)).resolves.toBe(signature) - await expect(root.derive("0'/0/2")).resolves.toBeInstanceOf(SeedSignerEvm) - - b.dispose() - root.dispose() - }) - - test('should not let a derived child derive further', async () => { - const root = new SeedSignerEvm(VALID_SEED_PHRASE) - const child = await root.derive("0'/0/0") - - await expect(child.derive("0'/0/1")).rejects.toThrow('Cannot derive: this signer has no root') - - child.dispose() - root.dispose() - }) -}) - -describe('PrivateKeySignerEvm', () => { - test('should create a signer from a hex string', () => { - const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) - - expect(signer.address).toBe(EXPECTED_ADDRESS) - expect(signer.isDerivable).toBe(false) - expect(signer.index).toBeUndefined() - - signer.dispose() - }) - - test('should create a signer from a Uint8Array', () => { - const keyBytes = new Uint8Array(Buffer.from(VALID_PRIVATE_KEY, 'hex')) - const signer = new PrivateKeySignerEvm(keyBytes) - - expect(signer.address).toBe(EXPECTED_ADDRESS) - - signer.dispose() - }) - - test('should return the correct signature', async () => { - const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) - - const signature = await signer.sign(MESSAGE) - expect(signature).toBe(EXPECTED_SIGNATURE) - - signer.dispose() - }) - - test('should throw when calling derive', async () => { - const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) - - await expect(signer.derive()).rejects.toThrow('PrivateKeySignerEvm does not support derivation.') - - signer.dispose() - }) - - test('should return the address via getAddress()', async () => { - const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) - - const address = await signer.getAddress() - expect(address).toBe(EXPECTED_ADDRESS) - - signer.dispose() - }) - - test('should clear secrets on dispose', () => { - const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) - - signer.dispose() - - expect(signer.keyPair.privateKey).toBeNull() - }) -}) diff --git a/tests/signers/private-key-signer-evm.test.js b/tests/signers/private-key-signer-evm.test.js new file mode 100644 index 0000000..3415c6c --- /dev/null +++ b/tests/signers/private-key-signer-evm.test.js @@ -0,0 +1,160 @@ +import { describe, expect, test } from '@jest/globals' + +import PrivateKeySignerEvm from '../../src/signers/private-key-signer-evm.js' + +const VALID_PRIVATE_KEY = '260905feebf1ec684f36f1599128b85f3a26c2b817f2065a2fc278398449c41f' +const EXPECTED_PUBLIC_KEY = '036c082582225926b9356d95b91a4acffa3511b7cc2a14ef5338c090ea2cc3d0aa' + +const EXPECTED_ADDRESS = '0x405005C7c4422390F4B334F64Cf20E0b767131d0' + +const MESSAGE = 'Dummy message to sign.' +const EXPECTED_SIGNATURE = '0xd130f94c52bf393206267278ac0b6009e14f11712578e5c1f7afe4a12685c5b96a77a0832692d96fc51f4bd403839572c55042ecbcc92d215879c5c8bb5778c51c' + +const TRANSACTION = { + to: '0xa460AEbce0d3A4BecAd8ccf9D6D4861296c503Bd', + value: 1_000n, + gasLimit: 21_000n, + maxFeePerGas: 2_000_000_000n, + maxPriorityFeePerGas: 1_000_000_000n, + nonce: 0, + chainId: 31_337n +} +const EXPECTED_SIGNED_TRANSACTION = '0x02f86e827a6980843b9aca00847735940082520894a460aebce0d3a4becad8ccf9d6d4861296c503bd8203e880c080a0189acf1d3170de712fd346182a77b08ccaa1317cdd13daf386f1405d52148171a04a83f7c7df7f258344e1726ac5b94f53fb415f0e41a58399b5031940b293b9ec' + +const TYPED_DATA = { + domain: { + name: 'TestApp', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + }, + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' } + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' } + ] + }, + message: { + from: { + name: 'Alice', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' + }, + contents: 'Hello, Bob!' + } +} +const EXPECTED_TYPED_DATA_SIGNATURE = '0xd5d54d9a7fe501ab5dc1532a443a4f70bc8b6ad1c3f09caac9b891efa8701cac5ad1d4830c7bc7ed2688965ed6b04d25e8f55906a843689fdf79100aee3a5dc71c' + +describe('PrivateKeySignerEvm', () => { + describe('constructor', () => { + test('should create a signer from a hex string', () => { + const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) + + expect(signer.isDerivable).toBe(false) + expect(signer.address).toBe(EXPECTED_ADDRESS) + expect(signer.path).toBeNull() + + signer.dispose() + }) + + test('should create a signer from a Uint8Array', () => { + const keyBytes = new Uint8Array(Buffer.from(VALID_PRIVATE_KEY, 'hex')) + const signer = new PrivateKeySignerEvm(keyBytes) + + expect(signer.address).toBe(EXPECTED_ADDRESS) + + signer.dispose() + }) + }) + + describe('keyPair', () => { + test('should expose the expected key pair bytes', () => { + const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) + + expect(Buffer.from(signer.keyPair.privateKey).toString('hex')).toBe(VALID_PRIVATE_KEY) + expect(Buffer.from(signer.keyPair.publicKey).toString('hex')).toBe(EXPECTED_PUBLIC_KEY) + + signer.dispose() + }) + }) + + describe('derive', () => { + test('should throw when calling derive', async () => { + const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) + + await expect(signer.derive("0'/0/0")).rejects.toThrow('PrivateKeySignerEvm does not support derivation.') + + signer.dispose() + }) + }) + + describe('getAddress', () => { + test('should return the address', async () => { + const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) + + const address = await signer.getAddress() + expect(address).toBe(EXPECTED_ADDRESS) + + signer.dispose() + }) + }) + + describe('sign', () => { + test('should return the correct signature', async () => { + const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) + + const signature = await signer.sign(MESSAGE) + expect(signature).toBe(EXPECTED_SIGNATURE) + + signer.dispose() + }) + }) + + describe('signTransaction', () => { + test('should return the signed transaction as a hex string', async () => { + const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) + + const signedTx = await signer.signTransaction(TRANSACTION) + expect(signedTx).toBe(EXPECTED_SIGNED_TRANSACTION) + + signer.dispose() + }) + }) + + describe('signTypedData', () => { + test('should return the correct signature', async () => { + const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) + + const signature = await signer.signTypedData(TYPED_DATA) + expect(signature).toBe(EXPECTED_TYPED_DATA_SIGNATURE) + + signer.dispose() + }) + }) + + describe('dispose', () => { + test('should clear secrets on dispose', () => { + const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) + + signer.dispose() + + expect(signer.keyPair.privateKey).toBeNull() + }) + + test('should be safe to call dispose more than once', () => { + const signer = new PrivateKeySignerEvm(VALID_PRIVATE_KEY) + + signer.dispose() + + expect(() => signer.dispose()).not.toThrow() + }) + }) +}) diff --git a/tests/signers/seed-signer-evm.test.js b/tests/signers/seed-signer-evm.test.js new file mode 100644 index 0000000..8b6e2c0 --- /dev/null +++ b/tests/signers/seed-signer-evm.test.js @@ -0,0 +1,237 @@ +import * as bip39 from 'bip39' + +import { describe, expect, test } from '@jest/globals' + +import SeedSignerEvm from '../../src/signers/seed-signer-evm.js' + +const VALID_SEED_PHRASE = 'cook voyage document eight skate token alien guide drink uncle term abuse' +const VALID_SEED = bip39.mnemonicToSeedSync(VALID_SEED_PHRASE) +const EXPECTED_PRIVATE_KEY = '260905feebf1ec684f36f1599128b85f3a26c2b817f2065a2fc278398449c41f' +const EXPECTED_PUBLIC_KEY = '036c082582225926b9356d95b91a4acffa3511b7cc2a14ef5338c090ea2cc3d0aa' + +const EXPECTED_ADDRESS = '0x405005C7c4422390F4B334F64Cf20E0b767131d0' + +const MESSAGE = 'Dummy message to sign.' +const EXPECTED_SIGNATURE = '0xd130f94c52bf393206267278ac0b6009e14f11712578e5c1f7afe4a12685c5b96a77a0832692d96fc51f4bd403839572c55042ecbcc92d215879c5c8bb5778c51c' + +const TRANSACTION = { + to: '0xa460AEbce0d3A4BecAd8ccf9D6D4861296c503Bd', + value: 1_000n, + gasLimit: 21_000n, + maxFeePerGas: 2_000_000_000n, + maxPriorityFeePerGas: 1_000_000_000n, + nonce: 0, + chainId: 31_337n +} +const EXPECTED_SIGNED_TRANSACTION = '0x02f86e827a6980843b9aca00847735940082520894a460aebce0d3a4becad8ccf9d6d4861296c503bd8203e880c080a0189acf1d3170de712fd346182a77b08ccaa1317cdd13daf386f1405d52148171a04a83f7c7df7f258344e1726ac5b94f53fb415f0e41a58399b5031940b293b9ec' + +const TYPED_DATA = { + domain: { + name: 'TestApp', + version: '1', + chainId: 1, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + }, + types: { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' } + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' } + ] + }, + message: { + from: { + name: 'Alice', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' + }, + to: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' + }, + contents: 'Hello, Bob!' + } +} +const EXPECTED_TYPED_DATA_SIGNATURE = '0xd5d54d9a7fe501ab5dc1532a443a4f70bc8b6ad1c3f09caac9b891efa8701cac5ad1d4830c7bc7ed2688965ed6b04d25e8f55906a843689fdf79100aee3a5dc71c' + +describe('SeedSignerEvm', () => { + describe('constructor', () => { + test('should create a signer with the account at index 0 by default', () => { + const signer = new SeedSignerEvm(VALID_SEED_PHRASE) + + expect(signer.isDerivable).toBe(true) + expect(signer.address).toBe(EXPECTED_ADDRESS) + expect(signer.path).toBe("m/44'/60'/0'/0/0") + + signer.dispose() + }) + + test('should derive the same address from raw seed bytes', () => { + const signer = new SeedSignerEvm(VALID_SEED) + + expect(signer.address).toBe(EXPECTED_ADDRESS) + + signer.dispose() + }) + + test('should derive the same address when path is provided via constructor opts', () => { + const signer = new SeedSignerEvm(VALID_SEED_PHRASE, { path: "0'/0/0" }) + + expect(signer.address).toBe(EXPECTED_ADDRESS) + + signer.dispose() + }) + + test('should throw if the seed phrase is invalid', () => { + expect(() => { new SeedSignerEvm('invalid seed phrase') }) // eslint-disable-line no-new + .toThrow('The seed phrase is invalid.') + }) + + test('should throw if both seed and root are provided', async () => { + const root = new SeedSignerEvm(VALID_SEED_PHRASE) + const child = await root.derive("0'/0/0") + expect(() => { new SeedSignerEvm(VALID_SEED_PHRASE, { root: child }) }) // eslint-disable-line no-new + .toThrow('Provide either a seed or a root, not both.') + child.dispose() + root.dispose() + }) + }) + + describe('keyPair', () => { + test('should expose the expected key pair bytes', () => { + const signer = new SeedSignerEvm(VALID_SEED_PHRASE) + + expect(Buffer.from(signer.keyPair.privateKey).toString('hex')).toBe(EXPECTED_PRIVATE_KEY) + expect(Buffer.from(signer.keyPair.publicKey).toString('hex')).toBe(EXPECTED_PUBLIC_KEY) + + signer.dispose() + }) + }) + + describe('derive', () => { + test('should derive a child signer with the correct address and path', async () => { + const root = new SeedSignerEvm(VALID_SEED_PHRASE) + const child = await root.derive("0'/0/0") + + expect(child.isDerivable).toBe(false) + expect(child.address).toBe(EXPECTED_ADDRESS) + expect(child.path).toBe("m/44'/60'/0'/0/0") + expect(Buffer.from(child.keyPair.privateKey).toString('hex')).toBe(EXPECTED_PRIVATE_KEY) + expect(Buffer.from(child.keyPair.publicKey).toString('hex')).toBe(EXPECTED_PUBLIC_KEY) + + child.dispose() + root.dispose() + }) + + test('should throw if the path is invalid', async () => { + const signer = new SeedSignerEvm(VALID_SEED_PHRASE) + + await expect(signer.derive("a'/b/c")) + .rejects.toThrow('invalid path component') + + signer.dispose() + }) + + test('should throw when deriving from a disposed signer', async () => { + const root = new SeedSignerEvm(VALID_SEED_PHRASE) + root.dispose() + + await expect(root.derive("0'/0/0")).rejects.toThrow('Cannot derive: this signer has no root') + }) + + test('should not let a derived child derive further', async () => { + const root = new SeedSignerEvm(VALID_SEED_PHRASE) + const child = await root.derive("0'/0/0") + + await expect(child.derive("0'/0/1")).rejects.toThrow('Cannot derive: this signer has no root') + + child.dispose() + root.dispose() + }) + }) + + describe('getAddress', () => { + test('should return the address', async () => { + const signer = new SeedSignerEvm(VALID_SEED_PHRASE) + + const address = await signer.getAddress() + expect(address).toBe(EXPECTED_ADDRESS) + + signer.dispose() + }) + }) + + describe('sign', () => { + test('should return the correct signature', async () => { + const signer = new SeedSignerEvm(VALID_SEED_PHRASE) + + const signature = await signer.sign(MESSAGE) + expect(signature).toBe(EXPECTED_SIGNATURE) + + signer.dispose() + }) + }) + + describe('signTransaction', () => { + test('should return the signed transaction as a hex string', async () => { + const signer = new SeedSignerEvm(VALID_SEED_PHRASE) + + const signedTx = await signer.signTransaction(TRANSACTION) + expect(signedTx).toBe(EXPECTED_SIGNED_TRANSACTION) + + signer.dispose() + }) + }) + + describe('signTypedData', () => { + test('should return the correct signature', async () => { + const signer = new SeedSignerEvm(VALID_SEED_PHRASE) + + const signature = await signer.signTypedData(TYPED_DATA) + expect(signature).toBe(EXPECTED_TYPED_DATA_SIGNATURE) + + signer.dispose() + }) + }) + + describe('dispose', () => { + test('should clear secrets on dispose', async () => { + const root = new SeedSignerEvm(VALID_SEED_PHRASE) + const child = await root.derive("0'/0/0") + + child.dispose() + + expect(child.keyPair.privateKey).toBeNull() + + root.dispose() + }) + + test('should be safe to call dispose more than once', () => { + const signer = new SeedSignerEvm(VALID_SEED_PHRASE) + + signer.dispose() + + expect(() => signer.dispose()).not.toThrow() + }) + + test('should not neuter the shared root when a derived child is disposed', async () => { + const root = new SeedSignerEvm(VALID_SEED_PHRASE) + const a = await root.derive("0'/0/0") + const b = await root.derive("0'/0/1") + + const signature = await b.sign(MESSAGE) + + a.dispose() + + // The sibling still signs and the root can still derive new children. + await expect(b.sign(MESSAGE)).resolves.toBe(signature) + await expect(root.derive("0'/0/2")).resolves.toBeInstanceOf(SeedSignerEvm) + + b.dispose() + root.dispose() + }) + }) +}) diff --git a/tests/wallet-account-evm.test.js b/tests/wallet-account-evm.test.js index 51f1c30..2acf9e5 100644 --- a/tests/wallet-account-evm.test.js +++ b/tests/wallet-account-evm.test.js @@ -7,8 +7,6 @@ import { afterEach, beforeEach, describe, expect, test, jest } from '@jest/globa import * as bip39 from 'bip39' import { WalletAccountEvm, WalletAccountReadOnlyEvm } from '../index.js' -import SeedSignerEvm from '../src/signers/seed-signer-evm.js' -import PrivateKeySignerEvm from '../src/signers/private-key-signer-evm.js' import TestToken from './artifacts/TestToken.json' with { type: 'json' } @@ -25,7 +23,6 @@ const INVALID_SEED_PHRASE = 'invalid seed phrase' const SEED = bip39.mnemonicToSeedSync(SEED_PHRASE) const ACCOUNT = { - index: 0, path: "m/44'/60'/0'/0/0", address: '0x405005C7c4422390F4B334F64Cf20E0b767131d0', keyPair: { @@ -85,9 +82,7 @@ describe('WalletAccountEvm', () => { await sendTestTokensTo(ACCOUNT.address, INITIAL_TOKEN_BALANCE) - const root = new SeedSignerEvm(SEED_PHRASE) - const signer = await root.derive("0'/0/0") - account = new WalletAccountEvm(signer, { provider: hre.network.provider }) + account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0", { provider: hre.network.provider }) }) afterEach(async () => { @@ -100,8 +95,6 @@ describe('WalletAccountEvm', () => { test('should successfully initialize an account for the given seed phrase and path', async () => { const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") - expect(account.index).toBe(ACCOUNT.index) - expect(account.path).toBe(ACCOUNT.path) expect(account.keyPair).toEqual({ @@ -113,8 +106,6 @@ describe('WalletAccountEvm', () => { test('should successfully initialize an account for the given seed and path', async () => { const account = new WalletAccountEvm(SEED, "0'/0/0") - expect(account.index).toBe(ACCOUNT.index) - expect(account.path).toBe(ACCOUNT.path) expect(account.keyPair).toEqual({ @@ -135,11 +126,26 @@ describe('WalletAccountEvm', () => { .toThrow('invalid path component') }) - test('should derive the same account as a manually derived signer', async () => { - const seededAccount = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") - const signerAccount = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0")) + test('should successfully initialize an account with a signer (signer overload)', async () => { + const mockSigner = { + address: ACCOUNT.address, + path: ACCOUNT.path, + keyPair: { + privateKey: new Uint8Array(Buffer.from(ACCOUNT.keyPair.privateKey, 'hex')), + publicKey: new Uint8Array(Buffer.from(ACCOUNT.keyPair.publicKey, 'hex')) + }, + isDerivable: false, + getAddress: async () => ACCOUNT.address, + sign: async () => '0xmocksignature', + dispose: () => {} + } - expect(await seededAccount.getAddress()).toBe(await signerAccount.getAddress()) + const account = new WalletAccountEvm(mockSigner) + + expect(await account.getAddress()).toBe(ACCOUNT.address) + expect(account.path).toBe(ACCOUNT.path) + expect(account.keyPair).toEqual(mockSigner.keyPair) + expect(await account.sign('any message')).toBe('0xmocksignature') }) }) @@ -228,25 +234,15 @@ describe('WalletAccountEvm', () => { const SIGNED_TRANSACTION = '0x02f86e827a6980843b9aca00847735940082520894a460aebce0d3a4becad8ccf9d6d4861296c503bd8203e880c080a0189acf1d3170de712fd346182a77b08ccaa1317cdd13daf386f1405d52148171a04a83f7c7df7f258344e1726ac5b94f53fb415f0e41a58399b5031940b293b9ec' test('should sign a transaction and return a valid hex string', async () => { - const accountWithoutProvider = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0")) + const accountWithoutProvider = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") - const TRANSACTION = { - to: '0xa460AEbce0d3A4BecAd8ccf9D6D4861296c503Bd', - value: 1_000n, - gasLimit: 21_000n, - maxFeePerGas: 2_000_000_000n, - maxPriorityFeePerGas: 1_000_000_000n, - nonce: 0, - chainId: 31_337n - } - const SIGNED_TRANSACTION = "0x02f86e827a6980843b9aca00847735940082520894a460aebce0d3a4becad8ccf9d6d4861296c503bd8203e880c080a0189acf1d3170de712fd346182a77b08ccaa1317cdd13daf386f1405d52148171a04a83f7c7df7f258344e1726ac5b94f53fb415f0e41a58399b5031940b293b9ec" const signedTx = await accountWithoutProvider.signTransaction(TRANSACTION) expect(signedTx).toBe(SIGNED_TRANSACTION) }) test('should throw if transaction fee exceeds the transaction max fee configuration', async () => { - const account = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0"), { + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0", { provider: hre.network.provider, transactionMaxFee: 0 }) @@ -256,7 +252,7 @@ describe('WalletAccountEvm', () => { }) test('should not enforce transaction max fee without a provider', async () => { - const account = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0"), { + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0", { transactionMaxFee: 0 }) @@ -268,7 +264,7 @@ describe('WalletAccountEvm', () => { test('should allow a fee exactly equal to transactionMaxFee', async () => { const { fee } = await account.quoteSendTransaction(TRANSACTION) - const accountAtLimit = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0"), { + const accountAtLimit = new WalletAccountEvm(SEED_PHRASE, "0'/0/0", { provider: hre.network.provider, transactionMaxFee: fee }) @@ -281,7 +277,7 @@ describe('WalletAccountEvm', () => { test('should allow a fee below transactionMaxFee', async () => { const { fee } = await account.quoteSendTransaction(TRANSACTION) - const accountBelowLimit = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0"), { + const accountBelowLimit = new WalletAccountEvm(SEED_PHRASE, "0'/0/0", { provider: hre.network.provider, transactionMaxFee: fee + 1n }) @@ -311,23 +307,6 @@ describe('WalletAccountEvm', () => { expect(fee).toBe(EXPECTED_FEE) }) - test('should successfully send a transaction with PrivateKeySignerEvm', async () => { - const pkSigner = new PrivateKeySignerEvm(ACCOUNT.keyPair.privateKey) - const pkAccount = new WalletAccountEvm(pkSigner, { provider: hre.network.provider }) - const TRANSACTION = { - to: '0xa460AEbce0d3A4BecAd8ccf9D6D4861296c503Bd', - value: 1_000 - } - const EXPECTED_FEE = 46_114_898_254_972n - const { hash, fee } = await pkAccount.sendTransaction(TRANSACTION) - const transaction = await hre.ethers.provider.getTransaction(hash) - expect(transaction.hash).toBe(hash) - expect(transaction.to).toBe(TRANSACTION.to) - expect(transaction.value).toBe(BigInt(TRANSACTION.value)) - expect(fee).toBe(EXPECTED_FEE) - pkAccount.dispose() - }) - test('should successfully send a transaction with arbitrary data', async () => { const TRANSACTION_WITH_DATA = { to: testToken.target, @@ -407,7 +386,7 @@ describe('WalletAccountEvm', () => { value: 1_000 } - const account = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0"), { + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0", { provider: hre.network.provider, transactionMaxFee: 0 }) @@ -424,7 +403,7 @@ describe('WalletAccountEvm', () => { const { fee } = await account.quoteSendTransaction(TRANSACTION) - const accountAtLimit = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0"), { + const accountAtLimit = new WalletAccountEvm(SEED_PHRASE, "0'/0/0", { provider: hre.network.provider, transactionMaxFee: fee }) @@ -442,7 +421,7 @@ describe('WalletAccountEvm', () => { const { fee } = await account.quoteSendTransaction(TRANSACTION) - const accountBelowLimit = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0"), { + const accountBelowLimit = new WalletAccountEvm(SEED_PHRASE, "0'/0/0", { provider: hre.network.provider, transactionMaxFee: fee + 1n }) @@ -453,7 +432,7 @@ describe('WalletAccountEvm', () => { }) test('should throw if the account is not connected to a provider', async () => { - const account = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0")) + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") await expect(account.sendTransaction({ })) .rejects.toThrow('The wallet must be connected to a provider to send transactions.') @@ -525,17 +504,14 @@ describe('WalletAccountEvm', () => { amount: 100 } - const account = new WalletAccountEvm( - await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0"), - { provider: hre.network.provider, transferMaxFee: 0 } - ) + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0", { provider: hre.network.provider, transferMaxFee: 0 }) await expect(account.transfer(TRANSFER)) .rejects.toThrow('Exceeded maximum fee cost for transfer operation.') }) test('should throw if the account is not connected to a provider', async () => { - const account = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0")) + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") await expect(account.transfer({ })) .rejects.toThrow('The wallet must be connected to a provider to transfer tokens.') @@ -631,7 +607,7 @@ describe('WalletAccountEvm', () => { }) test('should throw if the account is not connected to a provider', async () => { - const accountWithoutProvider = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0")) + const accountWithoutProvider = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") const approveOptions = { token: testToken.target, spender: SPENDER, @@ -654,6 +630,27 @@ describe('WalletAccountEvm', () => { }) describe('signAuthorization', () => { + test('should throw when chainId and nonce are missing and the account has no provider', async () => { + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") + + await expect(account.signAuthorization({ address: DELEGATE_CONTRACT_ADDRESS })) + .rejects.toThrow('The wallet must be connected to a provider to populate the authorization chainId and nonce.') + }) + + test('should sign an authorization without a provider when chainId and nonce are explicit', async () => { + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") + + const auth = await account.signAuthorization({ + address: DELEGATE_CONTRACT_ADDRESS, + chainId: 1n, + nonce: 0 + }) + + expect(auth.address.toLowerCase()).toBe(DELEGATE_CONTRACT_ADDRESS.toLowerCase()) + expect(auth.chainId).toBe(1n) + expect(auth.nonce).toBe(0n) + }) + test('should successfully sign an authorization', async () => { const auth = await account.signAuthorization({ address: delegateContract.target @@ -699,7 +696,7 @@ describe('WalletAccountEvm', () => { }) test('should throw if the account is not connected to a provider', async () => { - const account = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0")) + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") await expect(account.delegate(delegateContract.target)) .rejects.toThrow('The wallet must be connected to a provider to delegate.') @@ -733,7 +730,7 @@ describe('WalletAccountEvm', () => { }) test('should throw if the account is not connected to a provider', async () => { - const account = new WalletAccountEvm(await new SeedSignerEvm(SEED_PHRASE).derive("0'/0/0")) + const account = new WalletAccountEvm(SEED_PHRASE, "0'/0/0") await expect(account.revokeDelegation()) .rejects.toThrow('The wallet must be connected to a provider to delegate.') diff --git a/tests/wallet-manager-evm.test.js b/tests/wallet-manager-evm.test.js index 737abe7..94cc61e 100644 --- a/tests/wallet-manager-evm.test.js +++ b/tests/wallet-manager-evm.test.js @@ -16,8 +16,7 @@ describe('WalletManagerEvm', () => { let wallet beforeEach(async () => { - const root = new SeedSignerEvm(SEED_PHRASE) - wallet = new WalletManagerEvm(root, { provider: hre.network.provider }) + wallet = new WalletManagerEvm(SEED_PHRASE, { provider: hre.network.provider }) }) afterEach(() => { @@ -158,7 +157,7 @@ describe('WalletManagerEvm', () => { }) test('should throw if the wallet is not connected to a provider', async () => { - const wallet = new WalletManagerEvm(new SeedSignerEvm(SEED_PHRASE)) + const wallet = new WalletManagerEvm(SEED_PHRASE) await expect(wallet.getFeeRates()) .rejects.toThrow('The wallet must be connected to a provider to get fee rates.') diff --git a/tsconfig.json b/tsconfig.json index 6c04934..93de5e7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,9 @@ "index.js", "src/**/*.js", ], + "exclude": [ + "src/signers/bare.js", + ], "compilerOptions": { "target": "ES2015", "module": "NodeNext", diff --git a/types/index.d.ts b/types/index.d.ts index dad3d51..a60a20e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,6 +1,7 @@ export { default } from "./src/wallet-manager-evm.js"; export { default as WalletAccountReadOnlyEvm } from "./src/wallet-account-read-only-evm.js"; export { default as WalletAccountEvm } from "./src/wallet-account-evm.js"; +export { ISigner } from "@tetherto/wdk-wallet"; export type TypedDataDomain = import("ethers").TypedDataDomain; export type TypedDataField = import("ethers").TypedDataField; export type AuthorizationRequest = import("ethers").AuthorizationRequest; @@ -17,4 +18,3 @@ export type EvmTransaction = import("./src/wallet-account-read-only-evm.js").Evm export type EvmTransferOptions = import("./src/wallet-account-read-only-evm.js").EvmTransferOptions; export type EvmWalletConfig = import("./src/wallet-account-read-only-evm.js").EvmWalletConfig; export type ApproveOptions = import("./src/wallet-account-evm.js").ApproveOptions; -export type UnsignedEvmTransaction = import("./src/utils/tx-populator-evm.js").UnsignedEvmTransaction; diff --git a/types/src/signers/index.d.ts b/types/src/signers/index.d.ts index eb2de36..c61c4c3 100644 --- a/types/src/signers/index.d.ts +++ b/types/src/signers/index.d.ts @@ -1,3 +1,3 @@ -export { default as PrivateKeySignerEvm } from "./private-key-signer-evm.js"; +export { ISignerEvm } from "./signer-evm.js"; export { default, default as SeedSignerEvm } from "./seed-signer-evm.js"; -export type UnsignedEvmTransaction = import("../utils/tx-populator-evm.js").UnsignedEvmTransaction; +export { default as PrivateKeySignerEvm } from "./private-key-signer-evm.js"; diff --git a/types/src/signers/private-key-signer-evm.d.ts b/types/src/signers/private-key-signer-evm.d.ts index 0bf20bf..b0f8298 100644 --- a/types/src/signers/private-key-signer-evm.d.ts +++ b/types/src/signers/private-key-signer-evm.d.ts @@ -1,17 +1,14 @@ -import { ISignerEvm } from "./seed-signer-evm.js"; -/** @typedef {import('./seed-signer-evm.js').UnsignedEvmTransaction} UnsignedEvmTransaction */ -/** @typedef {import('../wallet-account-read-only-evm.js').TypedData} TypedData */ -/** @typedef {import('@tetherto/wdk-wallet').KeyPair} KeyPair */ -/** @typedef {import('ethers').AuthorizationRequest} AuthorizationRequest */ -/** @typedef {import('ethers').Authorization} Authorization */ /** - * @extends {ISignerEvm} * Signer that wraps a raw private key in a memory-safe buffer, exposing a minimal * interface for signing messages, transactions and typed data. This signer does * not support derivation and always represents a single account. + * + * @implements {ISignerEvm} */ -export default class PrivateKeySignerEvm extends ISignerEvm { +export default class PrivateKeySignerEvm implements ISignerEvm { /** + * Create a signer from a raw private key. + * * @param {string|Uint8Array} privateKey - Hex string (with/without 0x) or raw key bytes. */ constructor(privateKey: string | Uint8Array); @@ -21,28 +18,44 @@ export default class PrivateKeySignerEvm extends ISignerEvm { private _wallet; /** @private */ private _address; - /** @private */ - private _path; - /** @type {boolean} */ + /** + * Whether this signer can derive child signers. Always false: a private-key signer is a + * single standalone account and is bound directly to a wallet account. + * + * @type {boolean} + */ get isDerivable(): boolean; - /** @type {number|undefined} */ - get index(): number | undefined; - /** @type {string|undefined} */ - get path(): string | undefined; - /** @type {string} */ + /** + * The BIP 0044 derivation path. + * + * @type {string | null} + */ + get path(): string | null; + /** + * The account's address. + * + * @type {string} + */ get address(): string; /** * The account's key pair (private and public key buffers). + * * @type {KeyPair} */ get keyPair(): KeyPair; /** - * PrivateKeySignerEvm is not a hierarchical signer and cannot derive. - * @returns {Promise} - * @throws {SignerError} Always — private-key signers do not support derivation. + * Derive a child signer using a relative path (e.g., "0'/0/0"). + * + * @param {string} relPath - The relative derivation path. + * @returns {Promise} The derived signer. + * @throws {SignerError} If the signer does not support derivation. + */ + derive(relPath: string): Promise; + /** + * Returns the account's address. + * + * @returns {Promise} The account's address. */ - derive(): Promise; - /** @returns {Promise} */ getAddress(): Promise; /** * Signs a message. @@ -52,30 +65,35 @@ export default class PrivateKeySignerEvm extends ISignerEvm { */ sign(message: string): Promise; /** - * Signs a transaction and returns the serialized signed transaction hex. + * Signs a transaction. * - * @param {UnsignedEvmTransaction} unsignedTx - The unsigned transaction object. - * @returns {Promise} + * @param {TransactionLike} tx - The transaction to sign. + * @returns {Promise} The signed transaction as a hex string. */ - signTransaction(unsignedTx: UnsignedEvmTransaction): Promise; + signTransaction(tx: TransactionLike): Promise; /** * Signs typed data according to EIP-712. * * @param {TypedData} typedData - The typed data to sign. * @returns {Promise} The typed data signature. */ - signTypedData({ domain, types, message }: TypedData): Promise; + signTypedData(typedData: TypedData): Promise; /** - * Sign an ERC-7702 authorization tuple. - * @param {AuthorizationRequest} auth - * @returns {Promise} + * Signs an ERC-7702 authorization tuple. + * + * @param {AuthorizationRequest} auth - The authorization request. + * @returns {Promise} The signed authorization. */ signAuthorization(auth: AuthorizationRequest): Promise; - /** Dispose secrets from memory. */ + /** + * Disposes the signer, erasing its secrets from memory. + */ dispose(): void; } -export type UnsignedEvmTransaction = import("./seed-signer-evm.js").UnsignedEvmTransaction; -export type TypedData = import("../wallet-account-read-only-evm.js").TypedData; +export type ISignerEvm = import("./signer-evm.js").ISignerEvm; export type KeyPair = import("@tetherto/wdk-wallet").KeyPair; +export type SignerError = import("@tetherto/wdk-wallet").SignerError; +export type TransactionLike = import("ethers").TransactionLike; export type AuthorizationRequest = import("ethers").AuthorizationRequest; export type Authorization = import("ethers").Authorization; +export type TypedData = import("../wallet-account-read-only-evm.js").TypedData; diff --git a/types/src/signers/seed-signer-evm.d.ts b/types/src/signers/seed-signer-evm.d.ts index a50bb27..8b2aefb 100644 --- a/types/src/signers/seed-signer-evm.d.ts +++ b/types/src/signers/seed-signer-evm.d.ts @@ -1,130 +1,13 @@ -import { ISigner } from "@tetherto/wdk-wallet"; -/** @typedef {import('../wallet-account-read-only-evm.js').TypedData} TypedData */ -/** @typedef {import('@tetherto/wdk-wallet').KeyPair} KeyPair */ -/** @typedef {import('ethers').AuthorizationRequest} AuthorizationRequest */ -/** @typedef {import('ethers').Authorization} Authorization */ -/** @typedef {import('ethers').AuthorizationLike} AuthorizationLike */ /** - * A fully-populated unsigned EVM transaction suitable for signing. - * Produced by the internal transaction populator and consumed by signer implementations. - */ -export type UnsignedEvmTransaction = { - chainId: number; - nonce: number; - from: string; - to: string | null; - data: string; - value: number | bigint; - type: number; - gasLimit: number | bigint; - gasPrice?: number | bigint; - maxFeePerGas?: number | bigint; - maxPriorityFeePerGas?: number | bigint; - accessList?: any[]; - maxFeePerBlobGas?: number | bigint; - blobs?: any[]; - blobVersionedHashes?: string[]; - authorizationList?: AuthorizationLike[]; -}; -export type SeedSignerEvmOpts = { - /** - * An existing HD node wallet root to derive from (internal; set by {@link SeedSignerEvm#derive}). - */ - root?: object; - /** - * Relative BIP-44 path segment (e.g. "0'/0/0"). Defaults to the account at index 0. - */ - path?: string; - /** - * Internal. When true, the signer is a derived child and does not retain the root (set by {@link SeedSignerEvm#derive}). - */ - isChild?: boolean; -}; -/** - * Interface for EVM signers, extending the base `ISigner` from `@tetherto/wdk-wallet`. - * - * @extends {ISigner} - * @interface - */ -export class ISignerEvm extends ISigner { - /** - * Whether this signer can derive child signers (i.e. it holds an HD root). Non-derivable - * signers (e.g. private-key signers) are bound directly to an account; derivable signers - * derive child accounts and keep the root for management only. - * @type {boolean} - */ - get isDerivable(): boolean; - /** - * The last component index for the derivation path of this signer, when applicable. - * @type {number|undefined} - */ - get index(): number | undefined; - /** - * The full derivation path if this is a child signer. - * @type {string|undefined} - */ - get path(): string | undefined; - /** - * The account's address, if available. - * @type {string|undefined} - */ - get address(): string | undefined; - /** - * The account's key pair. - * @type {KeyPair} - */ - get keyPair(): KeyPair; - /** - * Derive a child signer from this signer using a relative path (e.g. "0'/0/0"). - * - * @param {string} relPath - The relative BIP-44 path segment. - * @returns {Promise} The derived child signer. - * @throws {SignerError} If the signer does not support derivation (e.g. private-key signers). - */ - derive(relPath: string): Promise; - /** - * Returns the account's address. - * @returns {Promise} - */ - getAddress(): Promise; - /** - * Sign a plain message. - * @param {string} message - * @returns {Promise} - */ - sign(message: string): Promise; - /** - * Sign a transaction-like object compatible with ethers Transaction.from. - * @param {UnsignedEvmTransaction} unsignedTx - * @returns {Promise} The serialized signed transaction hex. - */ - signTransaction(unsignedTx: UnsignedEvmTransaction): Promise; - /** - * Signs typed data according to EIP-712. - * - * @param {TypedData} typedData - The typed data to sign. - * @returns {Promise} The typed data signature. - */ - signTypedData({ domain, types, message }: TypedData): Promise; - /** - * Sign an ERC-7702 authorization tuple. - * @param {AuthorizationRequest} auth - * @returns {Promise} - */ - signAuthorization(auth: AuthorizationRequest): Promise; - /** Clear any secret material from memory. */ - dispose(): void; -} -/** - * @extends {ISignerEvm} * Signer implementation that derives keys from a BIP-39 seed using the BIP-44 Ethereum path. * Always holds a derived account (index 0 by default). A root signer also retains the HD root * and can derive child signers; a derived child holds only its own account. + * + * @implements {ISignerEvm} */ -export default class SeedSignerEvm extends ISignerEvm { +export default class SeedSignerEvm implements ISignerEvm { /** * Create a SeedSignerEvm. - * Provide a mnemonic/seed (children built via {@link derive} pass a shared root internally). * * @param {string|Uint8Array|null} seed - BIP-39 mnemonic or seed bytes. Omit when providing `opts.root`. * @param {SeedSignerEvmOpts} [opts] - Construction options for root reuse, direct child derivation or path definition (default is index 0). @@ -140,48 +23,110 @@ export default class SeedSignerEvm extends ISignerEvm { private _path; /** @private */ private _root; + /** + * Whether this signer can derive child signers. True for a root signer (which holds the + * HD root); false for a derived child, which does not retain the root. + * + * @type {boolean} + */ get isDerivable(): boolean; - get index(): number | undefined; - get path(): string | undefined; + /** + * The BIP 0044 derivation path. + * + * @type {string} + */ + get path(): string; + /** + * The account's derived address. + * + * @type {string} + */ get address(): string; + /** + * The account's key pair (private and public key buffers). + * + * @type {KeyPair} + */ get keyPair(): KeyPair; /** * Derive a child signer using the provided relative path (e.g. "0'/0/0"). - * @param {string} relPath - * @returns {Promise} + * + * @param {string} relPath - The relative BIP-44 path segment. + * @returns {Promise} The derived child signer. * @throws {Error} If called on a derived child signer, which does not retain the root. */ derive(relPath: string): Promise; /** - * Sign a plain message string. - * @param {string} message - * @returns {Promise} + * Returns the account's derived address. + * + * @returns {Promise} The account's address. + */ + getAddress(): Promise; + /** + * Signs a message. + * + * @param {string} message - The message to sign. + * @returns {Promise} The message's signature. */ sign(message: string): Promise; /** - * Sign a transaction object and return its serialized form. - * @param {UnsignedEvmTransaction} unsignedTx - * @returns {Promise} + * Signs a transaction. + * + * @param {TransactionLike} tx - The transaction to sign. + * @returns {Promise} The signed transaction as a hex string. */ - signTransaction(unsignedTx: UnsignedEvmTransaction): Promise; + signTransaction(tx: TransactionLike): Promise; /** * Signs typed data according to EIP-712. * * @param {TypedData} typedData - The typed data to sign. * @returns {Promise} The typed data signature. */ - signTypedData({ domain, types, message }: TypedData): Promise; + signTypedData(typedData: TypedData): Promise; /** - * Sign an ERC-7702 authorization tuple. - * @param {AuthorizationRequest} auth - * @returns {Promise} + * Signs an ERC-7702 authorization tuple. + * + * @param {AuthorizationRequest} auth - The authorization request. + * @returns {Promise} The signed authorization. */ signAuthorization(auth: AuthorizationRequest): Promise; - /** Disposes secrets from memory. */ + /** + * Disposes the signer, erasing its secrets from memory. + */ dispose(): void; } -export type TypedData = import("../wallet-account-read-only-evm.js").TypedData; +export type SeedSignerEvmOpts = { + /** + * An existing HD node wallet root to derive from. + */ + root?: MemorySafeHDNodeWallet; + /** + * Relative BIP-44 path segment (e.g. "0'/0/0"). Defaults to the account at index 0. + */ + path?: string; + /** + * When true, the signer is a derived child and does not retain the root. + */ + isChild?: boolean; +}; +/** + * Memory-safe BIP-32 HD node (implemented by the internal src/memory-safe/hd-node-wallet.js) + */ +export type MemorySafeHDNodeWallet = { + readonly address: string; + readonly path: string | null; + readonly index: number; + readonly depth: number; + readonly publicKey: string; + readonly privateKeyBuffer: Uint8Array | undefined; + readonly publicKeyBuffer: Uint8Array; + deriveChild(index: number): MemorySafeHDNodeWallet; + derivePath(path: string): MemorySafeHDNodeWallet; + dispose(): void; +}; +export type ISignerEvm = import("./signer-evm.js").ISignerEvm; export type KeyPair = import("@tetherto/wdk-wallet").KeyPair; +export type TransactionLike = import("ethers").TransactionLike; export type AuthorizationRequest = import("ethers").AuthorizationRequest; export type Authorization = import("ethers").Authorization; -export type AuthorizationLike = import("ethers").AuthorizationLike; +export type TypedData = import("../wallet-account-read-only-evm.js").TypedData; diff --git a/types/src/signers/signer-evm.d.ts b/types/src/signers/signer-evm.d.ts new file mode 100644 index 0000000..d7bbc4e --- /dev/null +++ b/types/src/signers/signer-evm.d.ts @@ -0,0 +1,39 @@ +/** + * Interface for EVM signers, extending the base `ISigner` from `@tetherto/wdk-wallet`. + * + * @interface + */ +export class ISignerEvm extends ISigner { + /** + * The account's address, if available. + * + * @type {string | undefined} + */ + get address(): string | undefined; + /** + * Signs a transaction. + * + * @param {TransactionLike} tx - The transaction to sign. + * @returns {Promise} The signed transaction as a hex string. + */ + signTransaction(tx: TransactionLike): Promise; + /** + * Signs typed data according to EIP-712. + * + * @param {TypedData} typedData - The typed data to sign. + * @returns {Promise} The typed data signature. + */ + signTypedData(typedData: TypedData): Promise; + /** + * Signs an ERC-7702 authorization tuple. + * + * @param {AuthorizationRequest} auth - The authorization request. + * @returns {Promise} The signed authorization. + */ + signAuthorization(auth: AuthorizationRequest): Promise; +} +export type TransactionLike = import("ethers").TransactionLike; +export type AuthorizationRequest = import("ethers").AuthorizationRequest; +export type Authorization = import("ethers").Authorization; +export type TypedData = import("../wallet-account-read-only-evm.js").TypedData; +import { ISigner } from "@tetherto/wdk-wallet"; diff --git a/types/src/utils/tx-populator-evm.d.ts b/types/src/utils/tx-populator-evm.d.ts deleted file mode 100644 index d64f14d..0000000 --- a/types/src/utils/tx-populator-evm.d.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * A fully or partially specified EVM transaction, prior to signing. - */ -export type UnsignedEvmTransaction = { - /** - * - The id of the chain the transaction targets. - */ - chainId: number; - /** - * - The sender's transaction count, used to order transactions. - */ - nonce: number; - /** - * - The sender's address. - */ - from: string; - /** - * - The recipient's address, or null for contract creation. - */ - to: string | null; - /** - * - The transaction's calldata as a hex string. - */ - data: string; - /** - * - The amount of native currency (in wei) to transfer. - */ - value: number | bigint; - /** - * - The EIP-2718 transaction type (0/1 legacy, 2 EIP-1559, 3 EIP-4844, 4 EIP-7702). - */ - type: number; - /** - * - The maximum amount of gas the transaction may consume. - */ - gasLimit: number | bigint; - /** - * - The gas price (in wei) for legacy (type 0/1) transactions. - */ - gasPrice?: number | bigint; - /** - * - The maximum total fee (in wei) per gas for EIP-1559 transactions. - */ - maxFeePerGas?: number | bigint; - /** - * - The maximum priority fee (in wei) per gas for EIP-1559 transactions. - */ - maxPriorityFeePerGas?: number | bigint; - /** - * - The EIP-2930 access list of addresses and storage keys. - */ - accessList?: any[]; - /** - * - The maximum fee (in wei) per blob gas for EIP-4844 transactions. - */ - maxFeePerBlobGas?: number | bigint; - /** - * - The blobs to include in an EIP-4844 transaction. - */ - blobs?: any[]; - /** - * - The versioned hashes of the EIP-4844 blobs. - */ - blobVersionedHashes?: string[]; - /** - * - The EIP-7702 authorization tuples. - */ - authorizationList?: AuthorizationLike[]; -}; -export type Provider = import("ethers").Provider; -export type AuthorizationLike = import("ethers").AuthorizationLike; -/** - * Build a fully populated unsigned transaction ready for signing. - * - * Resolves chain ID, nonce, gas limit and fee fields from the provider when not - * explicitly supplied in `tx`. Supports legacy (type 0/1), EIP-1559 (type 2), - * EIP-4844 (type 3) and EIP-7702 (type 4) transaction styles. - * - * @param {Provider} provider - An ethers-compatible JSON-RPC provider. - * @param {string} from - The sender address. - * @param {UnsignedEvmTransaction} tx - The partial transaction to populate. - * @returns {Promise} The fully populated unsigned transaction. - */ -export function populateTransactionEvm(provider: Provider, from: string, tx: UnsignedEvmTransaction): Promise; diff --git a/types/src/wallet-account-evm.d.ts b/types/src/wallet-account-evm.d.ts index f5bd576..02e696b 100644 --- a/types/src/wallet-account-evm.d.ts +++ b/types/src/wallet-account-evm.d.ts @@ -34,17 +34,12 @@ export default class WalletAccountEvm extends WalletAccountReadOnlyEvm implement /** @private */ private _signer; /** - * The derivation path's index of this account. + * The derivation path of this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)), + * or null if the account's signer is not bound to a BIP-44 position (e.g. private-key signers). * - * @type {number} + * @type {string | null} */ - get index(): number; - /** - * The derivation path of this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)). - * - * @type {string} - */ - get path(): string; + get path(): string | null; /** * The account's key pair. * @@ -52,12 +47,11 @@ export default class WalletAccountEvm extends WalletAccountReadOnlyEvm implement * it's strongly recommended to treat the key pair as a read-only view of the keys. While it's still technically possible to alter their * content, client code should never do so. * - * @type {KeyPair} + * @type {KeyPair | null} */ - get keyPair(): KeyPair; + get keyPair(): KeyPair | null; /** - * Returns the account's address. If it wasn't resolved at construction time (e.g hardware signers), it asks the - * underlying signer to resolve it, then caches it locally. + * Returns the account's address. * * @returns {Promise} The account's address. */ @@ -119,8 +113,11 @@ export default class WalletAccountEvm extends WalletAccountReadOnlyEvm implement /** * Signs an ERC-7702 authorization tuple. * + * The chainId and nonce are populated from the provider when not explicitly provided. + * * @param {AuthorizationRequest} auth - The authorization request. * @returns {Promise} The signed authorization. + * @throws {Error} If the chainId or nonce are not provided and the wallet is not connected to a provider. */ signAuthorization(auth: AuthorizationRequest): Promise; /** @@ -145,7 +142,7 @@ export default class WalletAccountEvm extends WalletAccountReadOnlyEvm implement */ dispose(): void; } -export type ISignerEvm = import("./signers/seed-signer-evm.js").ISignerEvm; +export type ISignerEvm = import("./signers/signer-evm.js").ISignerEvm; export type HDNodeWallet = import("ethers").HDNodeWallet; export type AuthorizationRequest = import("ethers").AuthorizationRequest; export type Authorization = import("ethers").Authorization; diff --git a/types/src/wallet-manager-evm.d.ts b/types/src/wallet-manager-evm.d.ts index dac7f53..c15c560 100644 --- a/types/src/wallet-manager-evm.d.ts +++ b/types/src/wallet-manager-evm.d.ts @@ -1,7 +1,3 @@ -/** @typedef {import('ethers').Provider} Provider */ -/** @typedef {import("@tetherto/wdk-wallet").FeeRates} FeeRates */ -/** @typedef {import("@tetherto/wdk-wallet").ISigner} ISigner */ -/** @typedef {import('./wallet-account-evm.js').EvmWalletConfig} EvmWalletConfig */ export default class WalletManagerEvm extends WalletManager { /** * Multiplier for normal fee rate calculations (in %). @@ -18,17 +14,26 @@ export default class WalletManagerEvm extends WalletManager { */ protected static _FEE_RATE_FAST_MULTIPLIER: bigint; /** - * Creates a new wallet manager for evm blockchains. + * Creates a new wallet manager for evm blockchains from a BIP-39 seed. * - * Accepts either a BIP-39 seed (string/Uint8Array) for backwards compatibility, or a - * pre-built root signer object. The default signer must be derivable (it must be able to - * derive child accounts); non-derivable signers (e.g. private-key signers) are not allowed - * as the default but may be registered by name via {@link addSigner} - If not adding to your global account managment for using just one non derivable signer create a standalone account. + * @param {string | Uint8Array} seed - The BIP-39 seed phrase or raw seed bytes. + * @param {EvmWalletConfig} [config] - The configuration object. + * @throws {Error} If the seed phrase is invalid. + */ + constructor(seed: string | Uint8Array, config?: EvmWalletConfig); + /** + * Creates a new wallet manager for evm blockchains from a default signer. + * + * The default signer must be derivable (it must be able to derive child accounts); + * non-derivable signers (e.g. private-key signers) are not allowed as the default but + * may be registered by name via {@link addSigner}. To use a single non-derivable signer + * outside of the wallet manager, create a standalone account instead. * - * @param {string|Uint8Array|ISigner} seedOrSigner - A BIP-39 seed phrase, seed bytes, or a root signer. Root signers must be derivable — non-derivable signers (e.g. private-key signers) can only be registered by name via {@link addSigner}. + * @param {ISigner} signer - The default signer. * @param {EvmWalletConfig} [config] - The configuration object. + * @throws {SignerError} If the default signer does not support account derivation. */ - constructor(seedOrSigner: string | Uint8Array | ISigner, config?: EvmWalletConfig); + constructor(signer: ISigner, config?: EvmWalletConfig); /** * An ethers provider to interact with a node of the blockchain. * @@ -50,9 +55,7 @@ export default class WalletManagerEvm extends WalletManager { signerName?: string; }): Promise; /** - * Returns the wallet account associated with a registered signer. Non-derivable - * signers (e.g. private-key signers) return the signer's single account; derivable signers - * derive a detached child at the signer's own account (the root is never handed out). + * Returns the wallet account associated with a registered signer. * * @param {string} signerName - The signer name registered via {@link addSigner}. * @returns {Promise} The account. @@ -79,10 +82,11 @@ export default class WalletManagerEvm extends WalletManager { */ getFeeRates(): Promise; } -export type ISignerEvm = import("./signers/seed-signer-evm.js").ISignerEvm; +export type ISignerEvm = import("./signers/signer-evm.js").ISignerEvm; export type Provider = import("ethers").Provider; export type FeeRates = import("@tetherto/wdk-wallet").FeeRates; export type ISigner = import("@tetherto/wdk-wallet").ISigner; +export type SignerError = import("@tetherto/wdk-wallet").SignerError; export type EvmWalletConfig = import("./wallet-account-evm.js").EvmWalletConfig; import WalletManager from '@tetherto/wdk-wallet'; import WalletAccountEvm from './wallet-account-evm.js';