From e3428598c19b2d5ebcdb36167ed415abfe1abd30 Mon Sep 17 00:00:00 2001 From: Sergey Kambalin Date: Wed, 29 May 2024 14:41:24 +0600 Subject: [PATCH 1/8] feat: Add support for solana wallet address (#197) --- package-lock.json | 328 +++++++++++++++++- packages/embed-wallet/src/types.ts | 2 +- packages/ui/src/icons/SolanaIcon.tsx | 36 ++ packages/ui/src/icons/index.ts | 1 + packages/wallet-engine/package.json | 9 +- packages/wallet-engine/src/accounts.ts | 26 +- packages/wallet-engine/src/engine/accounts.ts | 11 +- packages/wallet-engine/src/engine/approve.ts | 10 + packages/wallet-engine/src/engine/provider.ts | 8 + packages/wallet-engine/src/engine/solana.ts | 47 +++ packages/wallet-engine/src/types.ts | 8 +- playground/Wallet.tsx | 31 +- .../AddressDropdown/AddressDropdown.tsx | 16 +- src/components/CoinIcon/coinIconsMap.ts | 3 +- .../Permissions/knownPermissions.ts | 5 + src/constants.ts | 2 +- src/stores/AccountStore/AccountStore.ts | 3 +- .../ApprovalStore/PersonalSignHandler.ts | 11 +- 18 files changed, 535 insertions(+), 22 deletions(-) create mode 100644 packages/ui/src/icons/SolanaIcon.tsx create mode 100644 packages/wallet-engine/src/engine/solana.ts diff --git a/package-lock.json b/package-lock.json index 98b68364..69697b3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9600,6 +9600,120 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", + "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.91.8", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.91.8.tgz", + "integrity": "sha512-USa6OS1jbh8zOapRJ/CBZImZ8Xb7AJjROZl5adql9TpOoBN9BUzyyouS5oPuZHft7S7eB8uJPuXWYjMi6BHgOw==", + "dependencies": { + "@babel/runtime": "^7.24.5", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0", + "@solana/buffer-layout": "^4.0.1", + "agentkeepalive": "^4.5.0", + "bigint-buffer": "^1.1.5", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.0", + "node-fetch": "^2.7.0", + "rpc-websockets": "^7.11.0", + "superstruct": "^0.14.2" + } + }, + "node_modules/@solana/web3.js/node_modules/@babel/runtime": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@solana/web3.js/node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/web3.js/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/web3.js/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@solana/web3.js/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/@solana/web3.js/node_modules/superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + }, + "node_modules/@solana/web3.js/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/@solana/web3.js/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@solana/web3.js/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/@stablelib/aead": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stablelib/aead/-/aead-1.0.1.tgz", @@ -11027,7 +11141,6 @@ "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -13505,6 +13618,17 @@ "node": ">= 6.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -14527,6 +14651,18 @@ "node": "*" } }, + "node_modules/bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.3.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/bignumber.js": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", @@ -14555,6 +14691,14 @@ "node": ">=8" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bitsyntax": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", @@ -14712,6 +14856,16 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, "node_modules/bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", @@ -17407,6 +17561,17 @@ "node": ">= 14" } }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -18254,6 +18419,14 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, "node_modules/es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", @@ -20512,6 +20685,14 @@ "node >=0.6.0" ] }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, "node_modules/fake-merkle-patricia-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz", @@ -20588,6 +20769,11 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==" + }, "node_modules/fast-xml-parser": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz", @@ -20741,6 +20927,11 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -22270,6 +22461,14 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -23335,6 +23534,77 @@ "node": ">=10" } }, + "node_modules/jayson": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz", + "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "JSONStream": "^1.3.5", + "uuid": "^8.3.2", + "ws": "^7.4.5" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jayson/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + }, + "node_modules/jayson/node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/jayson/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/jayson/node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jest": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", @@ -24468,6 +24738,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ] + }, "node_modules/jsonpointer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", @@ -24485,6 +24763,21 @@ "node": "*" } }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -30968,6 +31261,29 @@ "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", "dev": true }, + "node_modules/rpc-websockets": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.11.0.tgz", + "integrity": "sha512-IkLYjayPv6Io8C/TdCL5gwgzd1hFz2vmBZrjMw/SPEXo51ETOhnzgS4Qy5GWi2JQN7HKHa66J3+2mv0fgNh/7w==", + "dependencies": { + "eventemitter3": "^4.0.7", + "uuid": "^8.3.2", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + } + }, + "node_modules/rpc-websockets/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/run-async": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", @@ -33103,6 +33419,11 @@ "node": ">=8" } }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -36730,11 +37051,14 @@ "@polkadot/types": "^10.2.1", "@polkadot/util": "^11.1.2", "@polkadot/util-crypto": "^11.1.2", + "@solana/web3.js": "^1.91.8", "@toruslabs/openlogin-ed25519": "^7.0.0", "@web3auth/ethereum-provider": "^7.3.2", "eth-json-rpc-middleware": "^9.0.1", "ethereumjs-wallet": "^1.0.2", - "json-rpc-engine": "^6.1.0" + "json-rpc-engine": "^6.1.0", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" } }, "packages/wallet-engine/node_modules/@biconomy/mexa": { diff --git a/packages/embed-wallet/src/types.ts b/packages/embed-wallet/src/types.ts index 1b74bade..1db6141e 100644 --- a/packages/embed-wallet/src/types.ts +++ b/packages/embed-wallet/src/types.ts @@ -45,7 +45,7 @@ export type UserInfo = { export type WalletAccount = { address: string; - type: 'ethereum' | 'ed25519'; + type: 'ethereum' | 'ed25519' | 'solana'; name: string; }; diff --git a/packages/ui/src/icons/SolanaIcon.tsx b/packages/ui/src/icons/SolanaIcon.tsx new file mode 100644 index 00000000..04a73091 --- /dev/null +++ b/packages/ui/src/icons/SolanaIcon.tsx @@ -0,0 +1,36 @@ +import { memo } from 'react'; +import { SvgIcon, SvgIconProps } from '@mui/material'; + +export const SolanaIcon = memo((props: SvgIconProps) => ( + + + + + + + + + + +)); diff --git a/packages/ui/src/icons/index.ts b/packages/ui/src/icons/index.ts index dbdf6570..4d5dce53 100644 --- a/packages/ui/src/icons/index.ts +++ b/packages/ui/src/icons/index.ts @@ -44,6 +44,7 @@ export * from './ComingSoonIcon'; export * from './EthIcon'; export * from './UsdcIcon'; export * from './UsdtIcon'; +export * from './SolanaIcon'; export * from './TransactionInIcon'; export * from './TransactionOutIcon'; export * from './TopUpIcon'; diff --git a/packages/wallet-engine/package.json b/packages/wallet-engine/package.json index e89fe996..bd8e24a7 100644 --- a/packages/wallet-engine/package.json +++ b/packages/wallet-engine/package.json @@ -10,16 +10,19 @@ "dependencies": { "@biconomy/mexa": "^3.0.6", "@cere/freeport-sc-sdk": "0.23.0", - "@polkadot/keyring": "^11.1.2", "@polkadot/api": "^10.2.1", + "@polkadot/keyring": "^11.1.2", + "@polkadot/types": "^10.2.1", "@polkadot/util": "^11.1.2", "@polkadot/util-crypto": "^11.1.2", - "@polkadot/types": "^10.2.1", + "@solana/web3.js": "^1.91.8", "@toruslabs/openlogin-ed25519": "^7.0.0", "@web3auth/ethereum-provider": "^7.3.2", "eth-json-rpc-middleware": "^9.0.1", "ethereumjs-wallet": "^1.0.2", - "json-rpc-engine": "^6.1.0" + "json-rpc-engine": "^6.1.0", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" }, "scripts": {} } diff --git a/packages/wallet-engine/src/accounts.ts b/packages/wallet-engine/src/accounts.ts index feb1ac4c..b95e4639 100644 --- a/packages/wallet-engine/src/accounts.ts +++ b/packages/wallet-engine/src/accounts.ts @@ -3,6 +3,7 @@ import { getED25519Key } from '@toruslabs/openlogin-ed25519'; import { decodeAddress, encodeAddress, isEthereumAddress } from '@polkadot/util-crypto'; import { hexToU8a, isHex } from '@polkadot/util'; import { Keyring } from '@polkadot/keyring'; +import { Keypair as SolKeypair } from '@solana/web3.js'; import { KeyPair, KeyType, Account } from './types'; import { CERE_SS58_PREFIX } from './constants'; @@ -29,6 +30,18 @@ const pairFactoryMap: Record KeyPair> = { address: encodeAddress(publicKey, CERE_SS58_PREFIX), }; }, + + solana: (privateKey) => { + const { sk: ed25519Key } = getED25519Key(privateKey); + const { publicKey, secretKey } = SolKeypair.fromSecretKey(ed25519Key); + + return { + type: 'solana', + publicKey: publicKey.toBuffer(), + secretKey: Buffer.from(secretKey), + address: publicKey.toBase58(), + }; + }, }; export type KeyPairOptions = { @@ -45,6 +58,10 @@ export const getKeyPair = ({ privateKey, type }: KeyPairOptions): KeyPair => { }; export const exportAccountToJson = ({ privateKey, type, passphrase }: KeyPairOptions & { passphrase?: string }) => { + if (type === 'solana') { + throw new Error('Not implemented'); + } + const { publicKey, secretKey } = getKeyPair({ type, privateKey }); const keyring = new Keyring({ type }); @@ -67,5 +84,10 @@ const isValidPolkadotAddress = (address: string) => { } }; -export const isValidAddress = (address: string, type: KeyType) => - type === 'ethereum' ? isEthereumAddress(address) : isValidPolkadotAddress(address); +export const isValidAddress = (address: string, type: KeyType) => { + if (type === 'solana') { + throw new Error('Not implemented'); + } + + return type === 'ethereum' ? isEthereumAddress(address) : isValidPolkadotAddress(address); +}; diff --git a/packages/wallet-engine/src/engine/accounts.ts b/packages/wallet-engine/src/engine/accounts.ts index 343425b6..71b7e54a 100644 --- a/packages/wallet-engine/src/engine/accounts.ts +++ b/packages/wallet-engine/src/engine/accounts.ts @@ -22,7 +22,7 @@ export const createAccountsEngine = ({ getPrivateKey, getAccounts, onUpdateAccou engine.push( createScaffoldMiddleware({ wallet_accounts: createAsyncMiddleware(async (req, res) => { - res.result = createAccounts(['ethereum', 'ed25519']); + res.result = createAccounts(['ethereum', 'ed25519', 'solana']); }), ed25519_accounts: createAsyncMiddleware(async (req, res) => { @@ -33,13 +33,17 @@ export const createAccountsEngine = ({ getPrivateKey, getAccounts, onUpdateAccou res.result = createAccounts(['ethereum']).map((account) => account.address); }), + solana_accounts: createAsyncMiddleware(async (req, res) => { + res.result = createAccounts(['solana']).map((account) => account.address); + }), + eth_requestAccounts: createAsyncMiddleware(async (req, res) => { res.result = createAccounts(['ethereum']).map((account) => account.address); }), wallet_updateAccounts: createAsyncMiddleware(async (req, res) => { - const accounts = createAccounts(['ethereum', 'ed25519']); - const [eth, ed255519] = accounts; + const accounts = createAccounts(['ethereum', 'ed25519', 'solana']); + const [eth, ed255519, solana] = accounts; onUpdateAccounts(accounts); @@ -49,6 +53,7 @@ export const createAccountsEngine = ({ getPrivateKey, getAccounts, onUpdateAccou engine.emit('message', { type: 'wallet_accountsChanged', data: accounts }); engine.emit('message', { type: 'eth_accountChanged', data: eth }); engine.emit('message', { type: 'ed25519_accountChanged', data: ed255519 }); + engine.emit('message', { type: 'solana_accountChanged', data: solana }); /** * Standard eip-1193 event diff --git a/packages/wallet-engine/src/engine/approve.ts b/packages/wallet-engine/src/engine/approve.ts index 7929b242..d42b1918 100644 --- a/packages/wallet-engine/src/engine/approve.ts +++ b/packages/wallet-engine/src/engine/approve.ts @@ -127,6 +127,16 @@ export const createApproveEngine = ({ }); }), + solana_signMessage: createRequestMiddleware<[string, string]>(async (req, proceed) => { + const [account, message] = req.params!; + + await onPersonalSign({ + preopenInstanceId: req.preopenInstanceId, + params: [message, account, 'solana' as KeyType], + proceed, + }); + }), + eth_sendTransaction: createRequestMiddleware<[IncomingTransaction]>(async (req, proceed) => { await onSendTransaction({ preopenInstanceId: req.preopenInstanceId, diff --git a/packages/wallet-engine/src/engine/provider.ts b/packages/wallet-engine/src/engine/provider.ts index 9302ff69..6f09763b 100644 --- a/packages/wallet-engine/src/engine/provider.ts +++ b/packages/wallet-engine/src/engine/provider.ts @@ -10,12 +10,14 @@ import { createPermissionsEngine, PermissionsEngineOptions } from './permissions import type { EthereumEngineOptions } from './ethereum'; import type { PolkadotEngineOptions } from './polkadot'; import type { AccountsEngineOptions } from './accounts'; +import type { SolanaEngineOptions } from './solana'; export type ProviderEngineOptions = WalletEngineOptions & AccountsEngineOptions & ApproveEngineOptions & EthereumEngineOptions & PolkadotEngineOptions & + SolanaEngineOptions & PermissionsEngineOptions; class EngineProvider extends EventEmitter implements Provider { @@ -53,6 +55,12 @@ class UnsafeEngine extends Engine { return createPolkadotEngine(options); }); + this.pushEngine( + import(/* webpackChunkName: "accountsEngine" */ './solana').then(({ createSolanaEngine }) => + createSolanaEngine(options), + ), + ); + /** * Should always be the last one since it is currently handles real RPC requests * TODO: Replace with fetch middleware in future diff --git a/packages/wallet-engine/src/engine/solana.ts b/packages/wallet-engine/src/engine/solana.ts new file mode 100644 index 00000000..efba43fd --- /dev/null +++ b/packages/wallet-engine/src/engine/solana.ts @@ -0,0 +1,47 @@ +import { createAsyncMiddleware, createScaffoldMiddleware } from 'json-rpc-engine'; +import { u8aToHex } from '@polkadot/util'; +import nacl from 'tweetnacl'; +import { decodeUTF8 } from 'tweetnacl-util'; + +import { Engine } from './engine'; +import { getKeyPair } from '../accounts'; + +export type SolanaEngineOptions = { + getPrivateKey: () => string | undefined; +}; + +export const createSolanaEngine = ({ getPrivateKey }: SolanaEngineOptions) => { + const engine = new Engine(); + + const getPair = (address: string) => { + const privateKey = getPrivateKey(); + + if (!privateKey) { + throw new Error('No private key was provided!'); + } + + return getKeyPair({ type: 'solana', privateKey }); + }; + + engine.push( + createScaffoldMiddleware({ + /** + * Sign a message with solana keypair + * https://solana.com/developers/cookbook/wallets/sign-message + * + * TODO: Rethink the implementation later after the solana blockchain hackathon + */ + solana_signMessage: createAsyncMiddleware(async (req, res) => { + const [address, message] = req.params as string[]; + const pair = getPair(address); + + const messageBytes = decodeUTF8(message); + const signature = nacl.sign.detached(messageBytes, pair.secretKey); + + res.result = u8aToHex(signature); + }), + }), + ); + + return engine; +}; diff --git a/packages/wallet-engine/src/types.ts b/packages/wallet-engine/src/types.ts index db1ed20c..d6e39647 100644 --- a/packages/wallet-engine/src/types.ts +++ b/packages/wallet-engine/src/types.ts @@ -9,7 +9,13 @@ export declare type ChainConfig = { tickerName: string; }; -export type KeyType = 'ethereum' | 'ed25519'; +/** + * TODO: Solana key type was added as a temparary solution. + * Solana uses `ed25519` so it would be better to add another key property eg. `chainNamespace` instead of extending`type`. + * + * For simplification, we can use `type` for now. + */ +export type KeyType = 'ethereum' | 'ed25519' | 'solana'; export type KeyPair = { type: KeyType; address: string; diff --git a/playground/Wallet.tsx b/playground/Wallet.tsx index 8db19c9d..5a2035a4 100644 --- a/playground/Wallet.tsx +++ b/playground/Wallet.tsx @@ -11,6 +11,7 @@ export const Wallet = () => { const [ethBalance, setEthBalance] = useState(); const [cereAddress, setCereAddress] = useState(); const [cereBalance, setCereBalance] = useState(); + const [solanaAddress, setSolanaAddress] = useState(); const [isNewUser, setIsNewUser] = useState(false); const wallet = useWallet(); @@ -41,10 +42,11 @@ export const Wallet = () => { accounts.map((account) => account.address), ); - const [ethAccount, cereAccount] = accounts; + const [ethAccount, cereAccount, solanaAccount] = accounts; setCereAddress(cereAccount?.address); setEthAddress(ethAccount?.address); + setSolanaAddress(solanaAccount?.address); }); window.addEventListener('focus', () => { @@ -60,6 +62,7 @@ export const Wallet = () => { permissions: { personal_sign: {}, ed25519_signRaw: {}, + solana_signMessage: {}, }, }, @@ -219,6 +222,16 @@ export const Wallet = () => { console.log(`Signed message: ${signed}`); }, [wallet]); + const handleSolanaSign = useCallback(async () => { + const [, , solanaAccount] = await wallet.getAccounts(); + const signed = await wallet.provider.request({ + method: 'solana_signMessage', + params: [solanaAccount.address, 'Hello!!!'], + }); + + console.log(`Signed message: ${signed}`); + }, [wallet]); + const handleGetAccounts = useCallback(async () => { const accounts = await wallet.getAccounts(); @@ -264,6 +277,7 @@ export const Wallet = () => { const permissions = await wallet.requestPermissions({ personal_sign: {}, ed25519_signRaw: {}, + solana_signMessage: {}, }); console.log('Approved permissions', permissions); @@ -308,6 +322,17 @@ export const Wallet = () => { )} + {solanaAddress && ( + + + Solana Address + + + {solanaAddress} + + + )} + {ethBalance && ( @@ -374,6 +399,10 @@ export const Wallet = () => { Sign payload (ed25519) + + diff --git a/src/components/AddressDropdown/AddressDropdown.tsx b/src/components/AddressDropdown/AddressDropdown.tsx index eba25d30..b1743717 100644 --- a/src/components/AddressDropdown/AddressDropdown.tsx +++ b/src/components/AddressDropdown/AddressDropdown.tsx @@ -7,6 +7,18 @@ import { CoinIcon } from '../CoinIcon'; export type AddressDropdownProps = Pick; +const labelByType = { + ethereum: 'Polygon', + ed25519: 'Cere Network', + solana: 'Solana', +}; + +const iconByType = { + ethereum: 'matic', + ed25519: 'cere', + solana: 'solana', +}; + const AddressDropdown = (props: AddressDropdownProps) => { const store = useAccountStore(); const { selectedAccount, accounts } = store; @@ -19,8 +31,8 @@ const AddressDropdown = (props: AddressDropdownProps) => { () => accounts.map((account) => ({ address: account.address, - label: account.type === 'ethereum' ? 'Polygon' : 'Cere Network', - icon: account.type === 'ethereum' ? : , + label: labelByType[account.type], + icon: , })), [accounts], ); diff --git a/src/components/CoinIcon/coinIconsMap.ts b/src/components/CoinIcon/coinIconsMap.ts index f6ddd9a3..cb29fae6 100644 --- a/src/components/CoinIcon/coinIconsMap.ts +++ b/src/components/CoinIcon/coinIconsMap.ts @@ -1,4 +1,4 @@ -import { CereIcon, MaticIcon, UsdcIcon, EthIcon, UsdtIcon } from '@cere-wallet/ui'; +import { CereIcon, MaticIcon, UsdcIcon, EthIcon, UsdtIcon, SolanaIcon } from '@cere-wallet/ui'; import { ComponentType } from 'react'; export const coinIconsMap: Record = { @@ -7,4 +7,5 @@ export const coinIconsMap: Record = { matic: MaticIcon, usdc: UsdcIcon, usdt: UsdtIcon, + solana: SolanaIcon, }; diff --git a/src/components/Permissions/knownPermissions.ts b/src/components/Permissions/knownPermissions.ts index df16377a..aee3c3ae 100644 --- a/src/components/Permissions/knownPermissions.ts +++ b/src/components/Permissions/knownPermissions.ts @@ -16,6 +16,11 @@ export const knownPermissions: Record = { title: 'Sign message (Cere Network)', description: 'Allow the app to sign data on your behalf in background.', }, + + solana_signMessage: { + title: 'Sign message (Solana)', + description: 'Allow the app to sign data on your behalf in background.', + }, }; export const defaultDescription = 'No description available'; diff --git a/src/constants.ts b/src/constants.ts index dacc5099..6546f68e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -42,4 +42,4 @@ export const RPC_POLLING_INTERVAL = 10000; export const AUTH_SESSION_TIMEOUT = 604800; export const AUTH_TOKEN_ISSUER = 'cere-wallet'; -export const ALLOWED_WALLET_PERMISSIONS = ['personal_sign', 'ed25519_signRaw'] as const; +export const ALLOWED_WALLET_PERMISSIONS = ['personal_sign', 'ed25519_signRaw', 'solana_signMessage'] as const; diff --git a/src/stores/AccountStore/AccountStore.ts b/src/stores/AccountStore/AccountStore.ts index 1af075f6..d4f187dd 100644 --- a/src/stores/AccountStore/AccountStore.ts +++ b/src/stores/AccountStore/AccountStore.ts @@ -95,8 +95,9 @@ export class AccountStore { get selectedAccount() { const selectedAddress = this.shared.state.selectedAddress; + const defaultAccount = this.accounts.at(0); - return this.accounts.find((account) => account.address === selectedAddress) || this.accounts.at(0); + return this.accounts.find((account) => account.address === selectedAddress) || defaultAccount; } selectAccount(address: string) { diff --git a/src/stores/ApprovalStore/PersonalSignHandler.ts b/src/stores/ApprovalStore/PersonalSignHandler.ts index 7a4ae62c..d5daa296 100644 --- a/src/stores/ApprovalStore/PersonalSignHandler.ts +++ b/src/stores/ApprovalStore/PersonalSignHandler.ts @@ -1,5 +1,5 @@ import { when } from 'mobx'; -import { PersonalSignRequest } from '@cere-wallet/wallet-engine'; +import { KeyType, PersonalSignRequest } from '@cere-wallet/wallet-engine'; import { PopupManagerStore } from '../PopupManagerStore'; import { NetworkStore } from '../NetworkStore'; @@ -15,12 +15,15 @@ export class PersonalSignHandler { async handle({ preopenInstanceId, params: [content, address, keyType] }: PersonalSignRequest) { const instanceId = preopenInstanceId || this.popupManagerStore.createModal(); - const network: ConfirmPopupState['network'] = - keyType === 'ed25519' ? { displayName: 'Cere Network', icon: 'cere' } : this.networkStore.network; + const staticNetworks: Record = { + ed25519: { displayName: 'Cere Network', icon: 'cere' }, + solana: { displayName: 'Solana', icon: 'solana' }, + ethereum: this.networkStore.network, + }; const popup = await this.popupManagerStore.proceedTo(instanceId, '/confirm', { - network, content, + network: staticNetworks[keyType], app: this.contextStore.app, status: 'pending', }); From 07d7e81d25cd68a6dc8c2fa142f6490f736401d5 Mon Sep 17 00:00:00 2001 From: Sergey Kambalin Date: Mon, 3 Jun 2024 17:16:54 +0600 Subject: [PATCH 2/8] feat: Add universal signer to make multi-chain signatures easier (#198) --- CHANGELOG.md | 2 +- package-lock.json | 4 +- packages/embed-wallet-inject/package.json | 2 +- packages/embed-wallet/CHANGELOG.md | 5 +++ packages/embed-wallet/README.md | 19 +++++++++ packages/embed-wallet/package.json | 2 +- packages/embed-wallet/src/EmbedWallet.ts | 24 ++++++++++- packages/embed-wallet/src/Provider.ts | 33 +++++++++++++++ packages/embed-wallet/src/Signer.ts | 40 +++++++++++++++++++ packages/embed-wallet/src/index.ts | 4 +- packages/embed-wallet/src/types.ts | 3 +- packages/wallet-engine/src/accounts.ts | 15 ++++++- packages/wallet-engine/src/engine/accounts.ts | 30 +++++++------- packages/wallet-engine/src/engine/approve.ts | 2 +- packages/wallet-engine/src/engine/engine.ts | 2 +- packages/wallet-engine/src/engine/provider.ts | 23 ++++++++--- packages/wallet-engine/src/engine/wallet.ts | 32 ++++++++++++++- packages/wallet-engine/src/types.ts | 1 + playground/Wallet.tsx | 21 +++++----- src/stores/AccountStore/AccountStore.ts | 16 +++----- .../EmbededWalletStore/EmbeddedWalletStore.ts | 4 +- src/stores/WalletStore/WalletStore.ts | 6 +-- 22 files changed, 228 insertions(+), 62 deletions(-) create mode 100644 packages/embed-wallet/src/Signer.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 722d1127..568fd125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### vNext -- +- Added multi-chain signature API to the wallet client and SDK ### v1.37.0 diff --git a/package-lock.json b/package-lock.json index 69697b3a..b4d5ab28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37002,7 +37002,7 @@ }, "packages/embed-wallet": { "name": "@cere/embed-wallet", - "version": "0.17.1", + "version": "0.18.0", "license": "Apache-2.0", "dependencies": { "@cere/torus-embed": "0.2.8", @@ -37013,7 +37013,7 @@ }, "packages/embed-wallet-inject": { "name": "@cere/embed-wallet-inject", - "version": "0.17.1", + "version": "0.18.0", "license": "Apache-2.0", "dependencies": { "@polkadot/extension-inject": "^0.46.6" diff --git a/packages/embed-wallet-inject/package.json b/packages/embed-wallet-inject/package.json index 79ae321f..210586bb 100644 --- a/packages/embed-wallet-inject/package.json +++ b/packages/embed-wallet-inject/package.json @@ -1,6 +1,6 @@ { "name": "@cere/embed-wallet-inject", - "version": "0.17.1", + "version": "0.18.0", "sideEffects": false, "type": "module", "types": "./dist/types/index.d.ts", diff --git a/packages/embed-wallet/CHANGELOG.md b/packages/embed-wallet/CHANGELOG.md index 6453239b..b61bef05 100644 --- a/packages/embed-wallet/CHANGELOG.md +++ b/packages/embed-wallet/CHANGELOG.md @@ -1,5 +1,10 @@ ## Release Notes: +### v0.18.0 + +- Add new RPC method to sign messages by any Cere Wallet managed account (`wallet_signMessage`) +- Added new method to the SDK to create a multi-chain signer instance (`getSigner()`) + ### v0.17.0 - Add `loginHint` option to the wallet connect configuration diff --git a/packages/embed-wallet/README.md b/packages/embed-wallet/README.md index 3526f246..fcd2552b 100644 --- a/packages/embed-wallet/README.md +++ b/packages/embed-wallet/README.md @@ -28,6 +28,7 @@ yarn add @cere/embed-wallet - [connect()](#connect) - [disconnect()](#disconnect) - [subscribe()](#subscribe) + - [getSigner()](#getsigner) - [getUserInfo()](#getuserinfo) - [getAccounts()](#getaccounts) - [showWallet()](#showwallet) @@ -174,6 +175,24 @@ const unsubscribe = wallet.subscribe('status-update', () => { unsubscribe(); // Stop listening for events ``` +### getSigner() + +This methods returns a universal signer instance for the given wallet account + +#### Parameters + +- `address` - Optional account address (Optional) +- `type` - Optional type of an account (eg, `ethereum`, `solana`) +- `accountIndex` - Optional account index (Default: `0`) + + +```ts +const signer = await wallet.getSigner({ type: 'solana' }); +const signature = await signer.signMessage('Hello, world!'); + +console.log(signature); +``` + ### getUserInfo() This methods returns information about currently connected user. The following user info is returned: diff --git a/packages/embed-wallet/package.json b/packages/embed-wallet/package.json index ef568418..b7fc7116 100644 --- a/packages/embed-wallet/package.json +++ b/packages/embed-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@cere/embed-wallet", - "version": "0.17.1", + "version": "0.18.0", "description": "Cere Wallet SDK to integrate the wallet into a web application.", "sideEffects": false, "main": "dist/bundle.umd.js", diff --git a/packages/embed-wallet/src/EmbedWallet.ts b/packages/embed-wallet/src/EmbedWallet.ts index 3667c353..2d8442eb 100644 --- a/packages/embed-wallet/src/EmbedWallet.ts +++ b/packages/embed-wallet/src/EmbedWallet.ts @@ -4,7 +4,8 @@ import BN from 'bn.js'; import { createContext } from './createContext'; import { getAuthRedirectResult } from './getAuthRedirectResult'; -import { ProxyProvider, ProviderInterface } from './Provider'; +import { ProxyProvider, ProviderInterface, SignerInterface } from './Provider'; +import { Signer, SignerOptions } from './Signer'; import { WALLET_CLIENT_VERSION } from './constants'; import { @@ -264,6 +265,27 @@ export class EmbedWallet { return this.provider.request({ method: 'wallet_accounts' }); } + /** + * Returns universal signer for the requested wallet account + * + * @param addressOrOptions - Account address or options to get the signer for + * @returns `Signer` instance + * + * @example + * + * ```typescript + * const signer = wallet.getSigner({ type: 'solana' }); + * const signature = await signer.signMessage('Hello, world!'); + * + * console.log(signature); + * ``` + */ + getSigner(addressOrOptions?: SignerOptions | string): SignerInterface { + const options = typeof addressOrOptions !== 'string' ? addressOrOptions : { address: addressOrOptions }; + + return new Signer(this.provider, options); + } + /** * Currently only CERE transfer supported */ diff --git a/packages/embed-wallet/src/Provider.ts b/packages/embed-wallet/src/Provider.ts index d2f4621e..adf88c18 100644 --- a/packages/embed-wallet/src/Provider.ts +++ b/packages/embed-wallet/src/Provider.ts @@ -1,11 +1,19 @@ import { EventEmitter } from 'events'; import { TorusInpageProvider } from '@cere/torus-embed'; +import { Signer, SignerOptions } from './Signer'; +import { WalletAccount } from './types'; interface RequestArguments { readonly method: string; readonly params?: unknown[] | Record; } +export interface SignerInterface { + getAccount(): Promise; + getAddress(): Promise; + signMessage(message: string): Promise; +} + /** * EIP-1193 compatible provider with some Cere specific extensions */ @@ -13,6 +21,15 @@ export interface ProviderInterface extends EventEmitter { readonly isConnected: boolean; request({ method, params }: RequestArguments): Promise; + + /** + * Get a signer for a specific account + * + * @param addressOrIndex - Account address or index + * + * @returns Signer instance + */ + getSigner(addressOrIndex: string | number): SignerInterface; } export class ProxyProvider extends EventEmitter implements ProviderInterface { @@ -42,4 +59,20 @@ export class ProxyProvider extends EventEmitter implements ProviderInterface { this.on('newListener', provider.on.bind(provider)); this.on('removeListener', provider.off.bind(provider)); } + + getSigner(addressOrIndex?: string | number): SignerInterface { + const options: SignerOptions = { + accountIndex: 0, + }; + + if (typeof addressOrIndex === 'string') { + options.address = addressOrIndex; + } + + if (typeof addressOrIndex === 'number') { + options.accountIndex = addressOrIndex; + } + + return new Signer(this, options); + } } diff --git a/packages/embed-wallet/src/Signer.ts b/packages/embed-wallet/src/Signer.ts new file mode 100644 index 00000000..2cdba769 --- /dev/null +++ b/packages/embed-wallet/src/Signer.ts @@ -0,0 +1,40 @@ +import { ProviderInterface, SignerInterface } from './Provider'; +import type { WalletAccount, WalletAccountType } from './types'; + +export type SignerOptions = { + accountIndex?: number; + address?: string; + type?: WalletAccountType; +}; + +export class Signer implements SignerInterface { + constructor(readonly provider: ProviderInterface, readonly options: SignerOptions = {}) {} + + async getAccount(): Promise { + const { address, type, accountIndex = 0 } = this.options; + const allAccounts = await this.provider.request({ method: 'wallet_accounts' }); + const accounts = type ? allAccounts.filter((account: WalletAccount) => account.type === type) : allAccounts; + + const account = address + ? accounts.find((account: WalletAccount) => account.address === address) + : accounts[accountIndex]; + + if (!account) { + throw new Error('Account not found'); + } + + return account; + } + + async getAddress(): Promise { + const { address } = await this.getAccount(); + + return address; + } + + async signMessage(message: string): Promise { + const account = await this.getAddress(); + + return this.provider.request({ method: 'wallet_signMessage', params: [account, message] }); + } +} diff --git a/packages/embed-wallet/src/index.ts b/packages/embed-wallet/src/index.ts index 2a6fbef8..20b8035e 100644 --- a/packages/embed-wallet/src/index.ts +++ b/packages/embed-wallet/src/index.ts @@ -4,4 +4,6 @@ export { preloadIframe as prefetchWalletIframe } from '@cere/torus-embed'; export * from './EmbedWallet'; export * from './types'; export { WALLET_CLIENT_VERSION } from './constants'; -export type { ProviderInterface } from './Provider'; + +export type { SignerOptions } from './Signer'; +export type { ProviderInterface, SignerInterface } from './Provider'; diff --git a/packages/embed-wallet/src/types.ts b/packages/embed-wallet/src/types.ts index 1db6141e..86fcb11a 100644 --- a/packages/embed-wallet/src/types.ts +++ b/packages/embed-wallet/src/types.ts @@ -43,9 +43,10 @@ export type UserInfo = { isNewUser: boolean; }; +export type WalletAccountType = 'ethereum' | 'ed25519' | 'solana'; export type WalletAccount = { address: string; - type: 'ethereum' | 'ed25519' | 'solana'; + type: WalletAccountType; name: string; }; diff --git a/packages/wallet-engine/src/accounts.ts b/packages/wallet-engine/src/accounts.ts index b95e4639..a88402c2 100644 --- a/packages/wallet-engine/src/accounts.ts +++ b/packages/wallet-engine/src/accounts.ts @@ -57,6 +57,10 @@ export const getKeyPair = ({ privateKey, type }: KeyPairOptions): KeyPair => { return pairFactoryMap[type](privateKey); }; +export const getKeyPairs = (privateKey: string, types = Object.keys(pairFactoryMap) as KeyType[]) => { + return types.map((type) => getKeyPair({ privateKey, type })); +}; + export const exportAccountToJson = ({ privateKey, type, passphrase }: KeyPairOptions & { passphrase?: string }) => { if (type === 'solana') { throw new Error('Not implemented'); @@ -68,10 +72,17 @@ export const exportAccountToJson = ({ privateKey, type, passphrase }: KeyPairOpt return keyring.addFromPair({ publicKey, secretKey }).toJson(passphrase); }; -export const getAccount = ({ privateKey, type, name }: AccountOptions): Account => ({ +export const getAccount = ({ privateKey, type, name }: AccountOptions): Account => { + const pair = getKeyPair({ privateKey, type }); + + return createAccountFromPair(pair, name); +}; + +export const createAccountFromPair = ({ type, address, publicKey }: KeyPair, name: string): Account => ({ type, name, - address: getKeyPair({ privateKey, type }).address, + address, + publicKey: publicKey.toString('hex'), }); const isValidPolkadotAddress = (address: string) => { diff --git a/packages/wallet-engine/src/engine/accounts.ts b/packages/wallet-engine/src/engine/accounts.ts index 71b7e54a..9379cea3 100644 --- a/packages/wallet-engine/src/engine/accounts.ts +++ b/packages/wallet-engine/src/engine/accounts.ts @@ -1,48 +1,48 @@ import { createScaffoldMiddleware, createAsyncMiddleware } from 'json-rpc-engine'; import { Engine } from './engine'; -import { getKeyPair } from '../accounts'; +import { getKeyPairs } from '../accounts'; import { Account, KeyPair, KeyType } from '../types'; export type AccountsEngineOptions = { - getAccounts: (pairs: KeyPair[]) => Account[]; + getAccounts: () => Account[]; getPrivateKey: () => string | undefined; - onUpdateAccounts: (accounts: Account[]) => void; + onUpdateAccounts: (accounts: KeyPair[]) => void; }; export const createAccountsEngine = ({ getPrivateKey, getAccounts, onUpdateAccounts }: AccountsEngineOptions) => { const engine = new Engine(); - const createAccounts = (types: KeyType[]) => { - const privateKey = getPrivateKey(); - - return !privateKey ? [] : getAccounts(types.map((type) => getKeyPair({ type, privateKey }))); - }; + const getAddresses = (type: KeyType) => + getAccounts() + .filter((account) => account.type === type) + .map((account) => account.address); engine.push( createScaffoldMiddleware({ wallet_accounts: createAsyncMiddleware(async (req, res) => { - res.result = createAccounts(['ethereum', 'ed25519', 'solana']); + res.result = getAccounts(); }), ed25519_accounts: createAsyncMiddleware(async (req, res) => { - res.result = createAccounts(['ed25519']); + res.result = getAddresses('ed25519'); }), eth_accounts: createAsyncMiddleware(async (req, res) => { - res.result = createAccounts(['ethereum']).map((account) => account.address); + res.result = getAddresses('ethereum'); }), solana_accounts: createAsyncMiddleware(async (req, res) => { - res.result = createAccounts(['solana']).map((account) => account.address); + res.result = getAddresses('solana'); }), eth_requestAccounts: createAsyncMiddleware(async (req, res) => { - res.result = createAccounts(['ethereum']).map((account) => account.address); + res.result = getAddresses('ethereum'); }), wallet_updateAccounts: createAsyncMiddleware(async (req, res) => { - const accounts = createAccounts(['ethereum', 'ed25519', 'solana']); + const privateKey = getPrivateKey(); + const accounts = privateKey ? getKeyPairs(privateKey, ['ethereum', 'ed25519', 'solana']) : []; const [eth, ed255519, solana] = accounts; onUpdateAccounts(accounts); @@ -66,7 +66,7 @@ export const createAccountsEngine = ({ getPrivateKey, getAccounts, onUpdateAccou wallet_getProviderState: createAsyncMiddleware(async (req, res, next) => { res.result = { ...(res.result as {}), - accounts: createAccounts(['ethereum']).map((account) => account.address), + accounts: getAddresses('ethereum'), }; }), }), diff --git a/packages/wallet-engine/src/engine/approve.ts b/packages/wallet-engine/src/engine/approve.ts index d42b1918..690cf561 100644 --- a/packages/wallet-engine/src/engine/approve.ts +++ b/packages/wallet-engine/src/engine/approve.ts @@ -7,7 +7,7 @@ import { } from 'json-rpc-engine'; import { Engine } from './engine'; -import { KeyType } from '../types'; +import type { KeyType } from '../types'; type WithPreopenedInstanceId = { preopenInstanceId?: string; diff --git a/packages/wallet-engine/src/engine/engine.ts b/packages/wallet-engine/src/engine/engine.ts index 110cb189..e521f965 100644 --- a/packages/wallet-engine/src/engine/engine.ts +++ b/packages/wallet-engine/src/engine/engine.ts @@ -35,7 +35,7 @@ export class Engine extends JsonRpcEngine { }); } - protected pushEngine(engine: AsyncEngine | (() => AsyncEngine)) { + pushEngine(engine: AsyncEngine | (() => AsyncEngine)) { const factory = typeof engine === 'function' ? engine : () => engine; const resultEngine = createAsyncEngine(factory); diff --git a/packages/wallet-engine/src/engine/provider.ts b/packages/wallet-engine/src/engine/provider.ts index 6f09763b..d56feb59 100644 --- a/packages/wallet-engine/src/engine/provider.ts +++ b/packages/wallet-engine/src/engine/provider.ts @@ -35,14 +35,12 @@ class EngineProvider extends EventEmitter implements Provider { } } -class UnsafeEngine extends Engine { +class ChainEngine extends Engine { readonly provider: Provider = new EngineProvider(this); constructor(options: ProviderEngineOptions) { super(); - this.pushEngine(createWalletEngine(options)); - this.pushEngine( import(/* webpackChunkName: "accountsEngine" */ './accounts').then(({ createAccountsEngine }) => createAccountsEngine(options), @@ -80,13 +78,26 @@ export class ProviderEngine extends Engine { constructor(options: ProviderEngineOptions) { super(); - const unsafeEngine = new UnsafeEngine(options); + const unsafeEngine = new Engine(); + const walletEngine = createWalletEngine(options); + const chainEngine = new ChainEngine(options); + + /** + * Setup unsafe provider + */ + unsafeEngine.pushEngine(walletEngine); + unsafeEngine.pushEngine(chainEngine); - this.provider = new EngineProvider(this); this.unsafeProvider = new EngineProvider(unsafeEngine); + /** + * Setup safe provider + */ + this.pushEngine(walletEngine); this.pushEngine(createPermissionsEngine(options, createApproveEngine(options))); - this.pushEngine(unsafeEngine); + this.pushEngine(chainEngine); + + this.provider = new EngineProvider(this); } async updateAccounts() { diff --git a/packages/wallet-engine/src/engine/wallet.ts b/packages/wallet-engine/src/engine/wallet.ts index 2574a5eb..5479851a 100644 --- a/packages/wallet-engine/src/engine/wallet.ts +++ b/packages/wallet-engine/src/engine/wallet.ts @@ -1,14 +1,15 @@ import { createScaffoldMiddleware, createAsyncMiddleware } from 'json-rpc-engine'; import { Engine } from './engine'; -import { ChainConfig } from '../types'; +import { Account, ChainConfig } from '../types'; export type WalletEngineOptions = { + getAccounts: () => Account[]; getPrivateKey: () => string | undefined; chainConfig: ChainConfig; }; -export const createWalletEngine = ({ chainConfig, getPrivateKey }: WalletEngineOptions) => { +export const createWalletEngine = ({ chainConfig, getAccounts, getPrivateKey }: WalletEngineOptions) => { const engine = new Engine(); engine.push( @@ -47,6 +48,33 @@ export const createWalletEngine = ({ chainConfig, getPrivateKey }: WalletEngineO return getPrivateKey() ? next() : undefined; }), + + /** + * Routes the universal sign message method to the correct provider + */ + wallet_signMessage: createAsyncMiddleware(async (req, res, next) => { + const [address, message] = req.params as [string, string]; + const account = getAccounts().find((account) => account.address.toUpperCase() === address.toUpperCase()); + + if (!account) { + throw new Error(`Account with address ${address} not found!`); + } + + if (account.type === 'ethereum') { + req.method = 'personal_sign'; + req.params = [message, address]; + } + + if (account.type === 'ed25519') { + req.method = 'ed25519_signRaw'; + } + + if (account.type === 'solana') { + req.method = 'solana_signMessage'; + } + + next(); + }), }), ); diff --git a/packages/wallet-engine/src/types.ts b/packages/wallet-engine/src/types.ts index d6e39647..def48d73 100644 --- a/packages/wallet-engine/src/types.ts +++ b/packages/wallet-engine/src/types.ts @@ -25,6 +25,7 @@ export type KeyPair = { export type Account = Omit & { name: string; + publicKey: string; }; export type ProviderRequestArguments = { diff --git a/playground/Wallet.tsx b/playground/Wallet.tsx index 5a2035a4..3d2e1eca 100644 --- a/playground/Wallet.tsx +++ b/playground/Wallet.tsx @@ -184,10 +184,8 @@ export const Wallet = () => { const handleEd25519Sign = useCallback(async () => { const [, cereAccount] = await wallet.getAccounts(); - const signed = await wallet.provider.request({ - method: 'ed25519_signRaw', - params: [cereAccount.address, 'Hello!!!'], - }); + const signer = wallet.getSigner({ address: cereAccount.address }); + const signed = await signer.signMessage('Hello!!!'); console.log(`Signed message: ${signed}`); }, [wallet]); @@ -214,20 +212,21 @@ export const Wallet = () => { }, [wallet]); const handlePersonalSign = useCallback(async () => { - const provider = new providers.Web3Provider(wallet.provider); - const signer = provider.getSigner(); + /** + * Alternative way of creating signer instance using Ethers.js + */ + // const provider = new providers.Web3Provider(wallet.provider); + // const signer = provider.getSigner(); + const signer = wallet.getSigner(); const signed = await signer.signMessage('Hello!!!'); console.log(`Signed message: ${signed}`); }, [wallet]); const handleSolanaSign = useCallback(async () => { - const [, , solanaAccount] = await wallet.getAccounts(); - const signed = await wallet.provider.request({ - method: 'solana_signMessage', - params: [solanaAccount.address, 'Hello!!!'], - }); + const signer = wallet.getSigner({ type: 'solana' }); + const signed = await signer.signMessage('Hello!!!'); console.log(`Signed message: ${signed}`); }, [wallet]); diff --git a/src/stores/AccountStore/AccountStore.ts b/src/stores/AccountStore/AccountStore.ts index d4f187dd..09f9cb93 100644 --- a/src/stores/AccountStore/AccountStore.ts +++ b/src/stores/AccountStore/AccountStore.ts @@ -1,6 +1,6 @@ import { makeAutoObservable, when } from 'mobx'; import { UserInfo } from '@cere-wallet/communication'; -import { Account, KeyPair, KeyType, exportAccountToJson } from '@cere-wallet/wallet-engine'; +import { Account, KeyPair, KeyType, exportAccountToJson, createAccountFromPair } from '@cere-wallet/wallet-engine'; import { User, Wallet } from '../types'; import { createSharedState } from '../sharedState'; @@ -52,16 +52,10 @@ export class AccountStore { return this.currentAccounts; } - updateAccounts(accounts: Account[]) { - this.currentAccounts = accounts; - } - - mapAccounts(pairs: KeyPair[]) { - return pairs.map(({ address, type }, index) => ({ - address, - type, - name: this.user?.name || `Account #${index}`, - })); + updateAccounts(keyPairs: KeyPair[]) { + this.currentAccounts = keyPairs.map((pair, index) => + createAccountFromPair(pair, this.user?.name || `Account #${index}`), + ); } exportAccount(type: KeyType, passphrase?: string) { diff --git a/src/stores/EmbededWalletStore/EmbeddedWalletStore.ts b/src/stores/EmbededWalletStore/EmbeddedWalletStore.ts index f97a6542..4faef6be 100644 --- a/src/stores/EmbededWalletStore/EmbeddedWalletStore.ts +++ b/src/stores/EmbededWalletStore/EmbeddedWalletStore.ts @@ -276,8 +276,8 @@ export class EmbeddedWalletStore implements Wallet { polkadotRpc: CERE_NETWORK_RPC, biconomy: this.options.biconomy, getPrivateKey: () => this.accountStore.privateKey, - getAccounts: (pairs) => this.accountStore.mapAccounts(pairs), - onUpdateAccounts: (accounts) => this.accountStore.updateAccounts(accounts), + getAccounts: () => toJS(this.accountStore.accounts), + onUpdateAccounts: (keyPairs) => this.accountStore.updateAccounts(keyPairs), onPersonalSign: (request) => this.approvalStore.approvePersonalSign(request), onPayloadSign: (request) => this.approvalStore.approvePayloadSign(request), onSendTransaction: (request) => this.approvalStore.approveSendTransaction(request), diff --git a/src/stores/WalletStore/WalletStore.ts b/src/stores/WalletStore/WalletStore.ts index 9eab9556..0a68b413 100644 --- a/src/stores/WalletStore/WalletStore.ts +++ b/src/stores/WalletStore/WalletStore.ts @@ -1,6 +1,6 @@ import { randomBytes } from 'crypto'; import { providers } from 'ethers'; -import { makeAutoObservable, reaction, runInAction, when } from 'mobx'; +import { makeAutoObservable, reaction, runInAction, toJS, when } from 'mobx'; import { createWalletEngine, WalletEngine } from '@cere-wallet/wallet-engine'; import { DEFAULT_NETWORK, getChainConfig } from '@cere-wallet/communication'; @@ -155,8 +155,8 @@ export class WalletStore implements Wallet { pollingInterval: RPC_POLLING_INTERVAL, chainConfig: this.network!, polkadotRpc: CERE_NETWORK_RPC, - getAccounts: (pairs) => this.accountStore.mapAccounts(pairs), - onUpdateAccounts: (accounts) => this.accountStore.updateAccounts(accounts), + getAccounts: () => toJS(this.accountStore.accounts), + onUpdateAccounts: (keyPairs) => this.accountStore.updateAccounts(keyPairs), getPrivateKey: () => this.accountStore.privateKey, onPersonalSign: (request) => this.approvalStore.approvePersonalSign(request), onSendTransaction: (request) => this.approvalStore.approveSendTransaction(request, { showDetails: true }), From d2177e0174cf5b2bc9808d59dc71c03c1a8e4c76 Mon Sep 17 00:00:00 2001 From: Sergey Kambalin Date: Tue, 4 Jun 2024 14:16:26 +0600 Subject: [PATCH 3/8] fix: Wallet address comparison host-fix (#199) --- package-lock.json | 4 ++-- packages/embed-wallet-inject/package.json | 2 +- packages/embed-wallet/package.json | 2 +- packages/embed-wallet/src/Signer.ts | 8 +++++++- packages/embed-wallet/src/constants.ts | 2 +- packages/wallet-engine/src/engine/wallet.ts | 7 +++++++ 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index b4d5ab28..bd39264e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37002,7 +37002,7 @@ }, "packages/embed-wallet": { "name": "@cere/embed-wallet", - "version": "0.18.0", + "version": "0.18.1", "license": "Apache-2.0", "dependencies": { "@cere/torus-embed": "0.2.8", @@ -37013,7 +37013,7 @@ }, "packages/embed-wallet-inject": { "name": "@cere/embed-wallet-inject", - "version": "0.18.0", + "version": "0.18.1", "license": "Apache-2.0", "dependencies": { "@polkadot/extension-inject": "^0.46.6" diff --git a/packages/embed-wallet-inject/package.json b/packages/embed-wallet-inject/package.json index 210586bb..56210a22 100644 --- a/packages/embed-wallet-inject/package.json +++ b/packages/embed-wallet-inject/package.json @@ -1,6 +1,6 @@ { "name": "@cere/embed-wallet-inject", - "version": "0.18.0", + "version": "0.18.1", "sideEffects": false, "type": "module", "types": "./dist/types/index.d.ts", diff --git a/packages/embed-wallet/package.json b/packages/embed-wallet/package.json index b7fc7116..882dd757 100644 --- a/packages/embed-wallet/package.json +++ b/packages/embed-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@cere/embed-wallet", - "version": "0.18.0", + "version": "0.18.1", "description": "Cere Wallet SDK to integrate the wallet into a web application.", "sideEffects": false, "main": "dist/bundle.umd.js", diff --git a/packages/embed-wallet/src/Signer.ts b/packages/embed-wallet/src/Signer.ts index 2cdba769..1608f011 100644 --- a/packages/embed-wallet/src/Signer.ts +++ b/packages/embed-wallet/src/Signer.ts @@ -15,8 +15,14 @@ export class Signer implements SignerInterface { const allAccounts = await this.provider.request({ method: 'wallet_accounts' }); const accounts = type ? allAccounts.filter((account: WalletAccount) => account.type === type) : allAccounts; + /** + * TODO: Think about a better approach to compare addresses. + * Addresses for some chains are case-sensitive, so we need think about a beeter way of comparing it. + * For now, we just convert both to uppercase to make it work. But this is not a perfect solution. + * We can keep it for now since there is a very low probability of two addresses that are the same but with different cases. + */ const account = address - ? accounts.find((account: WalletAccount) => account.address === address) + ? accounts.find((account: WalletAccount) => account.address.toUpperCase() === address.toUpperCase()) : accounts[accountIndex]; if (!account) { diff --git a/packages/embed-wallet/src/constants.ts b/packages/embed-wallet/src/constants.ts index 0db34dcb..dd61b4b3 100644 --- a/packages/embed-wallet/src/constants.ts +++ b/packages/embed-wallet/src/constants.ts @@ -1 +1 @@ -export const WALLET_CLIENT_VERSION = '1.34.0'; +export const WALLET_CLIENT_VERSION = '1.38.0'; diff --git a/packages/wallet-engine/src/engine/wallet.ts b/packages/wallet-engine/src/engine/wallet.ts index 5479851a..4fda22db 100644 --- a/packages/wallet-engine/src/engine/wallet.ts +++ b/packages/wallet-engine/src/engine/wallet.ts @@ -54,6 +54,13 @@ export const createWalletEngine = ({ chainConfig, getAccounts, getPrivateKey }: */ wallet_signMessage: createAsyncMiddleware(async (req, res, next) => { const [address, message] = req.params as [string, string]; + + /** + * TODO: Think about a better approach to compare addresses. + * Addresses for some chains are case-sensitive, so we need think about a beeter way of comparing it. + * For now, we just convert both to uppercase to make it work. But this is not a perfect solution. + * We can keep it for now since there is a very low probability of two addresses that are the same but with different cases. + */ const account = getAccounts().find((account) => account.address.toUpperCase() === address.toUpperCase()); if (!account) { From dddba224601837b6eab7837464b5365a62c62ff8 Mon Sep 17 00:00:00 2001 From: Sergey Kambalin Date: Wed, 5 Jun 2024 18:50:20 +0600 Subject: [PATCH 4/8] fix: Proper account detection in Cere Wallet injector --- .../src/polkadot/PolkadotInjector.ts | 18 ++++++++++------ packages/wallet-engine/src/engine/accounts.ts | 21 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/embed-wallet-inject/src/polkadot/PolkadotInjector.ts b/packages/embed-wallet-inject/src/polkadot/PolkadotInjector.ts index 2ce8e4d3..46e0ecdc 100644 --- a/packages/embed-wallet-inject/src/polkadot/PolkadotInjector.ts +++ b/packages/embed-wallet-inject/src/polkadot/PolkadotInjector.ts @@ -1,4 +1,4 @@ -import { EmbedWallet, PermissionRequest } from '@cere/embed-wallet'; +import { EmbedWallet, PermissionRequest, WalletAccount } from '@cere/embed-wallet'; import { injectExtension } from '@polkadot/extension-inject'; import type { Injected, InjectedAccount, InjectedAccounts } from '@polkadot/extension-inject/types'; import type { SignerPayloadRaw, SignerResult, Signer, SignerPayloadJSON } from '@polkadot/types/types'; @@ -48,18 +48,24 @@ export class PolkadotInjector { }); }); + private filterAccounts = (accounts: WalletAccount[]) => { + return accounts.filter((account: WalletAccount) => account.type === 'ed25519') as InjectedAccount[]; + }; + private getAccounts = async () => { - return await this.wallet.provider.request({ - method: 'ed25519_accounts', + const allAccounts = await this.wallet.provider.request({ + method: 'wallet_accounts', }); + + return this.filterAccounts(allAccounts); }; private subscribeAccounts = (onReceive: (accounts: InjectedAccount[]) => void) => { - const listener = (accounts: InjectedAccount[]) => onReceive(accounts); + const listener = (accounts: WalletAccount[]) => onReceive(this.filterAccounts(accounts)); - this.wallet.provider.on('ed25519_accountsChanged', listener); + this.wallet.provider.on('wallet_accountsChanged', listener); - return () => this.wallet.provider.off('ed25519_accountsChanged', listener); + return () => this.wallet.provider.off('wallet_accountsChanged', listener); }; private signRaw = async (raw: SignerPayloadRaw): Promise => { diff --git a/packages/wallet-engine/src/engine/accounts.ts b/packages/wallet-engine/src/engine/accounts.ts index 9379cea3..398462a8 100644 --- a/packages/wallet-engine/src/engine/accounts.ts +++ b/packages/wallet-engine/src/engine/accounts.ts @@ -42,23 +42,30 @@ export const createAccountsEngine = ({ getPrivateKey, getAccounts, onUpdateAccou wallet_updateAccounts: createAsyncMiddleware(async (req, res) => { const privateKey = getPrivateKey(); - const accounts = privateKey ? getKeyPairs(privateKey, ['ethereum', 'ed25519', 'solana']) : []; - const [eth, ed255519, solana] = accounts; + const keyPairs = privateKey ? getKeyPairs(privateKey, ['ethereum', 'ed25519', 'solana']) : []; - onUpdateAccounts(accounts); + onUpdateAccounts(keyPairs); + + const accounts = getAccounts(); + const ethAccounts = accounts.filter((account) => account.type === 'ethereum'); + const ed25519Accounts = accounts.filter((account) => account.type === 'ed25519'); + const solanaAccounts = accounts.filter((account) => account.type === 'solana'); /** * Custom wallet messages */ engine.emit('message', { type: 'wallet_accountsChanged', data: accounts }); - engine.emit('message', { type: 'eth_accountChanged', data: eth }); - engine.emit('message', { type: 'ed25519_accountChanged', data: ed255519 }); - engine.emit('message', { type: 'solana_accountChanged', data: solana }); + engine.emit('message', { type: 'eth_accountsChanged', data: ethAccounts }); + engine.emit('message', { type: 'ed25519_accountsChanged', data: ed25519Accounts }); + engine.emit('message', { type: 'solana_accountsChanged', data: solanaAccounts }); /** * Standard eip-1193 event */ - engine.emit('accountsChanged', eth ? [eth.address] : []); + engine.emit( + 'accountsChanged', + ethAccounts.map((account) => account.address), + ); res.result = accounts; }), From 606acd3a96b4819de6946b325de4a4f859349014 Mon Sep 17 00:00:00 2001 From: Sergey Kambalin Date: Wed, 5 Jun 2024 18:52:50 +0600 Subject: [PATCH 5/8] chore: Bump SDK version --- package-lock.json | 4 ++-- packages/embed-wallet-inject/package.json | 2 +- packages/embed-wallet/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index bd39264e..687b9a9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37002,7 +37002,7 @@ }, "packages/embed-wallet": { "name": "@cere/embed-wallet", - "version": "0.18.1", + "version": "0.18.2", "license": "Apache-2.0", "dependencies": { "@cere/torus-embed": "0.2.8", @@ -37013,7 +37013,7 @@ }, "packages/embed-wallet-inject": { "name": "@cere/embed-wallet-inject", - "version": "0.18.1", + "version": "0.18.2", "license": "Apache-2.0", "dependencies": { "@polkadot/extension-inject": "^0.46.6" diff --git a/packages/embed-wallet-inject/package.json b/packages/embed-wallet-inject/package.json index 56210a22..f7571652 100644 --- a/packages/embed-wallet-inject/package.json +++ b/packages/embed-wallet-inject/package.json @@ -1,6 +1,6 @@ { "name": "@cere/embed-wallet-inject", - "version": "0.18.1", + "version": "0.18.2", "sideEffects": false, "type": "module", "types": "./dist/types/index.d.ts", diff --git a/packages/embed-wallet/package.json b/packages/embed-wallet/package.json index 882dd757..d9384ca1 100644 --- a/packages/embed-wallet/package.json +++ b/packages/embed-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@cere/embed-wallet", - "version": "0.18.1", + "version": "0.18.2", "description": "Cere Wallet SDK to integrate the wallet into a web application.", "sideEffects": false, "main": "dist/bundle.umd.js", From b593be3d6728e8dc935c4bc7577d51e18b446edd Mon Sep 17 00:00:00 2001 From: Sergey Kambalin Date: Thu, 27 Jun 2024 14:20:37 +0600 Subject: [PATCH 6/8] feature: Let users enter an encryption password during account export (#200) --- CHANGELOG.md | 1 + packages/ui/src/theme/index.ts | 16 ++++++ src/routes/Settings/Settings.tsx | 67 +++++++++++++++++++------ src/stores/AccountStore/AccountStore.ts | 3 -- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 568fd125..03dc171e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### vNext - Added multi-chain signature API to the wallet client and SDK +- Let users enter an encryption password during account export ### v1.37.0 diff --git a/packages/ui/src/theme/index.ts b/packages/ui/src/theme/index.ts index f336b3d6..73cd22ce 100644 --- a/packages/ui/src/theme/index.ts +++ b/packages/ui/src/theme/index.ts @@ -203,6 +203,22 @@ export const createTheme = ({ whiteLabel, isGame }: any = {}): Theme => { }, styleOverrides: { + root: ({ theme, ownerState: props }) => { + const color = props.color === 'inherit' ? undefined : theme.palette[props.color || 'primary']; + const disabledColor = color && alpha(color.main, theme.palette.action.disabledOpacity); + const textColor = color && color.contrastText; + + return ( + color && { + '&.Mui-disabled': { + borderColor: disabledColor, + backgroundColor: props.variant === 'contained' ? disabledColor : undefined, + color: props.variant === 'contained' ? textColor : color?.main, + }, + } + ); + }, + contained: { backgroundColor: isGame && '#F32758', borderRadius: isGame ? 4 : 30, diff --git a/src/routes/Settings/Settings.tsx b/src/routes/Settings/Settings.tsx index d55749ca..9537f468 100644 --- a/src/routes/Settings/Settings.tsx +++ b/src/routes/Settings/Settings.tsx @@ -10,6 +10,7 @@ import { Typography, styled, useIsMobile, + TextField, } from '@cere-wallet/ui'; import { useEffect, useState } from 'react'; @@ -24,17 +25,37 @@ const SectionHeader = styled(CardHeader)({ const SectionButton = styled(Button)({ minWidth: 250, - height: 44, + whiteSpace: 'nowrap', + height: 42, }) as typeof Button; +const downloadFile = (url: string, filename: string) => { + const link = document.createElement('a'); + + link.href = url; + link.download = filename; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + // Clean up by revoking the Blob URL + URL.revokeObjectURL(url); +}; + export const Settings = () => { const isMobile = useIsMobile(); const accountStore = useAccountStore(); const authenticationStore = useAuthenticationStore(); const { accountUrl } = useOpenLoginStore(); const [accountLink, setAccountLink] = useState(); - const walletDownloadUrl = accountStore.exportAccount('ed25519'); const cereAddress = accountStore.getAccount('ed25519')?.address; + const [exportPassword, setExportPassword] = useState(''); + + const handleExportAccount = () => { + downloadFile(accountStore.exportAccount('ed25519', exportPassword), `${cereAddress}.json`); + setExportPassword(''); + }; useEffect(() => { authenticationStore @@ -74,7 +95,7 @@ export const Settings = () => { @@ -82,21 +103,37 @@ export const Settings = () => { } /> - + + + This downloadable file lets you restore your account or use it with Cere Tools, even if you can't + connect directly to Cere Wallet. To ensure maximum security, the file will be encrypted with a password + you create. + + - JSON backup file lets you restore your account or use it with Cere Tools, even if a direct connection to - Cere Wallet isn't available. Please keep it confidential and don't share it with third parties. + Please keep your password confidential and do not share it with anyone. Sharing your password could + compromise your account security. - - Download - + + setExportPassword(event.target.value)} + /> + + + Export Account + + diff --git a/src/stores/AccountStore/AccountStore.ts b/src/stores/AccountStore/AccountStore.ts index 09f9cb93..a77bd7b1 100644 --- a/src/stores/AccountStore/AccountStore.ts +++ b/src/stores/AccountStore/AccountStore.ts @@ -63,9 +63,6 @@ export class AccountStore { throw new Error('No private key found!'); } - /** - * TODO: Implement passphrase UI to not hardcode it here to be empty string ('') - */ const keyData = exportAccountToJson({ privateKey: this.privateKey, type, passphrase: passphrase || '' }); const accountBlob = new Blob([JSON.stringify(keyData)], { type: 'application/json', From 762f8bdf9e0939e4d50d0cd7f66688d85205b8f3 Mon Sep 17 00:00:00 2001 From: Anton Mazhuto <125869076+mazhutoanton@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:39:32 +0300 Subject: [PATCH 7/8] Updated freeport-sc-sdk package (#201) --- package-lock.json | 8 +++---- packages/communication/src/getChainConfig.ts | 24 ++++++++++++++++++++ packages/wallet-engine/package.json | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 687b9a9b..22cf2934 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3273,9 +3273,9 @@ "link": true }, "node_modules/@cere/freeport-sc-sdk": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@cere/freeport-sc-sdk/-/freeport-sc-sdk-0.23.0.tgz", - "integrity": "sha512-hg3cUUiTapnAJPSZD+1wX9XYzRTt+TFsHBajxyghdLpRmWusSJDMqPZ0w39jMT11N44t7fvrk+T14l9rPK25DA==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@cere/freeport-sc-sdk/-/freeport-sc-sdk-0.26.0.tgz", + "integrity": "sha512-457zXc2auAYttUQvFsSkcyafPUwRX0qfYbm+uhF+uilRXN39F6CSbxxGNDmiYuuwsBMGbaQpZIeHiV8AZRxLIQ==", "dependencies": { "@biconomy/mexa": "^2.0.38", "@ethersproject/providers": "^5.7.2", @@ -37045,7 +37045,7 @@ "version": "0.0.0", "dependencies": { "@biconomy/mexa": "^3.0.6", - "@cere/freeport-sc-sdk": "0.23.0", + "@cere/freeport-sc-sdk": "0.26.0", "@polkadot/api": "^10.2.1", "@polkadot/keyring": "^11.1.2", "@polkadot/types": "^10.2.1", diff --git a/packages/communication/src/getChainConfig.ts b/packages/communication/src/getChainConfig.ts index 35350c73..c991bcad 100644 --- a/packages/communication/src/getChainConfig.ts +++ b/packages/communication/src/getChainConfig.ts @@ -19,6 +19,22 @@ const presets = { ticker: 'MATIC', tickerName: 'Matic', }, + baseSepolia: { + chainId: '84532', + rpcTarget: 'https://sepolia.base.org', + displayName: 'Base Sepolia Testnet', + blockExplorer: 'https://sepolia-explorer.base.org', + ticker: 'ETH', + tickerName: 'ETH', + }, + base: { + chainId: '8453', + rpcTarget: 'https://mainnet.base.org', + displayName: 'Base Mainnet', + blockExplorer: 'https://base.blockscout.com/', + ticker: 'ETH', + tickerName: 'ETH', + }, }; const isConfigReady = (config: Partial): config is ChainConfig => @@ -50,6 +66,14 @@ export const getChainConfig = (network: NetworkConfig): ChainConfig => { chainConfig = createChainConfig(network, 'amoy'); } + if (network.chainId === 8453 || network.host === 'base') { + chainConfig = createChainConfig(network, 'base'); + } + + if (network.chainId === 84532 || network.host === 'baseSepolia') { + chainConfig = createChainConfig(network, 'baseSepolia'); + } + if (!isConfigReady(chainConfig)) { throw new Error('Incorrect network configuration'); } diff --git a/packages/wallet-engine/package.json b/packages/wallet-engine/package.json index bd8e24a7..13323486 100644 --- a/packages/wallet-engine/package.json +++ b/packages/wallet-engine/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@biconomy/mexa": "^3.0.6", - "@cere/freeport-sc-sdk": "0.23.0", + "@cere/freeport-sc-sdk": "0.26.0", "@polkadot/api": "^10.2.1", "@polkadot/keyring": "^11.1.2", "@polkadot/types": "^10.2.1", From 563097fdb480ecd4c00d7f6069be715c11281167 Mon Sep 17 00:00:00 2001 From: antonmazhuto Date: Thu, 4 Jul 2024 00:07:42 +0300 Subject: [PATCH 8/8] Updated version and release notes --- CHANGELOG.md | 4 ++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03dc171e..c968899e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ### vNext +- + +### v1.38.0 + - Added multi-chain signature API to the wallet client and SDK - Let users enter an encryption password during account export diff --git a/package-lock.json b/package-lock.json index 22cf2934..40b5e213 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cere-wallet-client", - "version": "1.37.0", + "version": "1.38.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cere-wallet-client", - "version": "1.37.0", + "version": "1.38.0", "workspaces": [ "packages/*", "tests/*" diff --git a/package.json b/package.json index cb28a58b..f96042c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cere-wallet-client", - "version": "1.37.0", + "version": "1.38.0", "private": true, "sideEffects": false, "dependencies": {