diff --git a/package.json b/package.json index cf40908f..1ffa9526 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "@cosmjs/encoding": "^0.25.6", "@cosmjs/proto-signing": "^0.25.6", "@intlify/vue-i18n-loader": "^2.1.2", + "@ledgerhq/hw-app-cosmos": "^6.3.0", + "@ledgerhq/hw-transport-web-ble": "^6.3.0", + "@ledgerhq/hw-transport-webusb": "^6.3.0", "@vue/composition-api": "1.0.0-beta.22", "@vueuse/core": "4.0.0", "animate.css": "4.1.1", @@ -31,6 +34,7 @@ "dayjs": "^1.10.6", "echarts": "4.8.0", "leaflet": "1.6.0", + "ledger-cosmos-js": "2.1.8", "node-fetch": "^2.6.1", "portal-vue": "2.1.7", "postcss-rtl": "1.7.3", diff --git a/src/@core/scss/vue/libs/vue-wizard.scss b/src/@core/scss/vue/libs/vue-wizard.scss index 4f9530f6..4a7d1e6e 100644 --- a/src/@core/scss/vue/libs/vue-wizard.scss +++ b/src/@core/scss/vue/libs/vue-wizard.scss @@ -86,8 +86,8 @@ &:not(:first-child) { a { &::before { - content: '\e844'; - font-family: feather !important; + content: '>'; + // font-family: feather !important;/* speak: none; font-style: normal; font-weight: 400; @@ -123,8 +123,8 @@ background-color: rgba($secondary, 0.04) !important; } &::before { - content: '\e843'; - font-family: feather !important; + content: '<'; + // font-family: feather !important; speak: none; font-style: normal; font-variant: normal; @@ -143,8 +143,8 @@ box-shadow: 0 8px 25px -8px $primary; } &::after { - content: '\e844'; - font-family: feather !important; + content: '>'; + //font-family: feather !important; speak: none; font-style: normal; font-weight: 400; diff --git a/src/libs/data/data.js b/src/libs/data/data.js index 3fee231b..ee301b9b 100644 --- a/src/libs/data/data.js +++ b/src/libs/data/data.js @@ -2,6 +2,11 @@ import { Bech32, fromBase64, fromHex, toHex, } from '@cosmjs/encoding' import { sha256 } from '@cosmjs/crypto' +// ledger +import TransportWebBLE from '@ledgerhq/hw-transport-web-ble' +import TransportWebUSB from '@ledgerhq/hw-transport-webusb' +// import Cosmos from '@ledgerhq/hw-app-cosmos' +import CosmosApp from 'ledger-cosmos-js' import dayjs from 'dayjs' import duration from 'dayjs/plugin/duration' @@ -12,6 +17,20 @@ dayjs.extend(localeData) dayjs.extend(duration) dayjs.extend(relativeTime) +export async function connectLedger(transport = 'usb') { + const trans = await transport === 'usb' ? TransportWebUSB.create() : TransportWebBLE.create() + return new CosmosApp(trans) +} +const COSMOS_PATH = [44, 118, 0, 0, 0] + +export async function getLedgerAddress(transport = 'blu') { + const trans = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create() + + trans.setDebugMode(true) + const cosmos = new CosmosApp(trans) + return cosmos.getAddressAndPubKey(COSMOS_PATH, 'cosmos') +} + export function getLocalObject(name) { const text = localStorage.getItem(name) if (text) { diff --git a/src/views/FormWizardNumber.vue b/src/views/UserAccountImportAddress.vue similarity index 74% rename from src/views/FormWizardNumber.vue rename to src/views/UserAccountImportAddress.vue index c99e8f58..bb1c935c 100644 --- a/src/views/FormWizardNumber.vue +++ b/src/views/UserAccountImportAddress.vue @@ -7,6 +7,7 @@ shape="square" finish-button-text="Submit" back-button-text="Previous" + class="steps-transparent mb-3 md" @on-complete="formSubmitted" > @@ -29,12 +30,17 @@ name="device" rules="required" > -
+ + Keplr @@ -42,20 +48,34 @@ v-model="device" name="device" value="ledger" - disabled + class="mb-1" > - Ledger Nano + Ledger via WebUSB - Nmemonic + Ledger via Bluetooth -
- {{ errors[0] }} + + Address (Observe Only) + + + + {{ debug }}{{ errors[0] }} @@ -107,7 +127,9 @@ { const { logo, addr_prefix } = this.chains[x] const addr = addressEnCode(addr_prefix, data) @@ -246,10 +271,15 @@ export default { } }, methods: { + async connect() { + const transport = this.device === 'ledger' ? 'usb' : 'bluetooth' + return getLedgerAddress(transport).catch(e => { + this.debug = e + }) + }, async cennectKeplr() { if (!window.getOfflineSigner || !window.keplr) { - // eslint-disable-next-line no-alert - alert('Please install keplr extension') + this.debug = 'Please install keplr extension' return null } const chainId = 'cosmoshub' @@ -257,6 +287,20 @@ export default { const offlineSigner = window.getOfflineSigner(chainId) return offlineSigner.getAccounts() }, + localAddress() { + if (!this.address) return false + try { + const { data } = addressDecode(this.address) + if (data) { + this.accounts = { + address: this.address, + pubkey: data, + } + return true + } + } catch (e) { this.debug = e } + return false + }, formSubmitted() { const string = localStorage.getItem('accounts') const accounts = string ? JSON.parse(string) : {} @@ -268,6 +312,7 @@ export default { } localStorage.setItem('accounts', JSON.stringify(accounts)) + this.$parent.$parent.$parent.completeAdd() this.$toast({ component: ToastificationContent, props: { @@ -278,16 +323,36 @@ export default { }) }, async validationFormDevice() { - await this.cennectKeplr().then(accounts => { - if (accounts) { - this.accounts = accounts - const key = Bech32.decode(accounts[0].address) - console.log(accounts, key) - } - }) + let ok = false + switch (this.device) { + case 'keplr': + await this.cennectKeplr().then(accounts => { + if (accounts) { + // eslint-disable-next-line prefer-destructuring + this.accounts = accounts[0] + ok = true + } + }) + break + case 'ledger': + case 'ledger2': + await this.connect().then(accounts => { + if (accounts) { + this.accounts = { + address: accounts.bech32_address, + pubkey: accounts.compressed_pk, + } + ok = true + } + }) + break + default: + ok = this.localAddress() + } + return new Promise((resolve, reject) => { this.$refs.deviceRules.validate().then(success => { - if (success) { + if (ok && success) { resolve(true) } reject() @@ -310,6 +375,5 @@ export default { diff --git a/src/views/UserAccounts.vue b/src/views/UserAccounts.vue index be31804f..b6230525 100644 --- a/src/views/UserAccounts.vue +++ b/src/views/UserAccounts.vue @@ -114,15 +114,15 @@ Import Accounts - + - + @@ -147,7 +147,7 @@ import FeatherIcon from '@/@core/components/feather-icon/FeatherIcon.vue' import { formatTokenAmount, formatTokenDenom, getLocalAccounts, getLocalChains, } from '@/libs/data' -import FormWizardNumber from './FormWizardNumber.vue' +import UserAccountImportAddress from './UserAccountImportAddress.vue' // import { SigningCosmosClient } from '@cosmjs/launchpad' export default { @@ -163,7 +163,7 @@ export default { BCardTitle, BDropdown, BDropdownItem, - FormWizardNumber, + UserAccountImportAddress, FeatherIcon, }, directives: { @@ -212,6 +212,10 @@ export default { } }, methods: { + completeAdd() { + this.$set(this, 'accounts', getLocalAccounts()) + this.$bvModal.hide('add-account') + }, formatDenom(v) { const denom = (v.startsWith('ibc') ? this.ibcDenom[v] : v) return formatTokenDenom(denom) diff --git a/yarn.lock b/yarn.lock index 00398a92..0eb4cf77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -871,6 +871,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.11.2": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" + integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.0.0", "@babel/template@^7.14.5": version "7.14.5" resolved "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz" @@ -1109,6 +1116,94 @@ "@intlify/shared" "^9.1.6" loader-utils "^2.0.0" +"@ledgerhq/devices@^5.51.1": + version "5.51.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.51.1.tgz#d741a4a5d8f17c2f9d282fd27147e6fe1999edb7" + integrity sha512-4w+P0VkbjzEXC7kv8T1GJ/9AVaP9I6uasMZ/JcdwZBS3qwvKo5A5z9uGhP5c7TvItzcmPb44b5Mw2kT+WjUuAA== + dependencies: + "@ledgerhq/errors" "^5.50.0" + "@ledgerhq/logs" "^5.50.0" + rxjs "6" + semver "^7.3.5" + +"@ledgerhq/devices@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-6.3.0.tgz#7ee59614198882311d1805912e368451527d05b2" + integrity sha512-DmVxqMAf3FhkpKjkbBCFVJ5DmesfplujeCLzFwO/zF5VGuwY7xxPqeSxlpusXJkqhEq+DbFzIDRWJYDf7rtXqg== + dependencies: + "@ledgerhq/errors" "^6.2.0" + "@ledgerhq/logs" "^6.2.0" + rxjs "6" + semver "^7.3.5" + +"@ledgerhq/errors@^5.50.0": + version "5.50.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.50.0.tgz#e3a6834cb8c19346efca214c1af84ed28e69dad9" + integrity sha512-gu6aJ/BHuRlpU7kgVpy2vcYk6atjB4iauP2ymF7Gk0ez0Y/6VSMVSJvubeEQN+IV60+OBK0JgeIZG7OiHaw8ow== + +"@ledgerhq/errors@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.2.0.tgz#7dc2b3bf6bdedccdaa1b97dccacfa912c4fc22f8" + integrity sha512-eO03x8HJmG60WtlrMuahigW/rwywFdcGzCnihta/MjkM8BD9A660cKVkyIuheCcpaB7UV/r+QsRl9abHbjjaag== + +"@ledgerhq/hw-app-cosmos@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-cosmos/-/hw-app-cosmos-6.3.0.tgz#8ef432ffa42b158841d0b12f13d5dd2134669b15" + integrity sha512-NGKgIiI6bR3w8cWniNuGbUG2e7C5aAXk5Vhiur3SYtEZ/fKu3Cvp+0nIke6oNzvO9Cl5G3lRP8D7SB4cyunfqw== + dependencies: + "@ledgerhq/errors" "^6.2.0" + "@ledgerhq/hw-transport" "^6.3.0" + bip32-path "^0.4.2" + +"@ledgerhq/hw-transport-web-ble@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-web-ble/-/hw-transport-web-ble-6.3.0.tgz#f6684831de97178cbb9f1d33a27f2cee1523ce8c" + integrity sha512-nWQzVataZhpn67vi1y0WLHIekLM8xArPLRGS6YU7udzxfWkoaEJOfj0Jd2dZqY7NeVzeABK/xcre08mU/61BzQ== + dependencies: + "@ledgerhq/devices" "^6.3.0" + "@ledgerhq/errors" "^6.2.0" + "@ledgerhq/hw-transport" "^6.3.0" + "@ledgerhq/logs" "^6.2.0" + rxjs "6" + +"@ledgerhq/hw-transport-webusb@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.3.0.tgz#6cf4f89190e520aeefcf99349806eac136d6f3f1" + integrity sha512-zDwQ6JZOvZYdxxPvwqbG21A4JWEV5XGmhwBIGA/DmZ50mdZ/Tq4q+Eo9GJxoU3+j0UHidKhfmPzbnJfOjfYADQ== + dependencies: + "@ledgerhq/devices" "^6.3.0" + "@ledgerhq/errors" "^6.2.0" + "@ledgerhq/hw-transport" "^6.3.0" + "@ledgerhq/logs" "^6.2.0" + +"@ledgerhq/hw-transport@^5.25.0": + version "5.51.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.51.1.tgz#8dd14a8e58cbee4df0c29eaeef983a79f5f22578" + integrity sha512-6wDYdbWrw9VwHIcoDnqWBaDFyviyjZWv6H9vz9Vyhe4Qd7TIFmbTl/eWs6hZvtZBza9K8y7zD8ChHwRI4s9tSw== + dependencies: + "@ledgerhq/devices" "^5.51.1" + "@ledgerhq/errors" "^5.50.0" + events "^3.3.0" + +"@ledgerhq/hw-transport@^6.3.0": + version "6.3.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.3.0.tgz#4fc966b1a68c991c0a6b5384841f99c4f8304ce9" + integrity sha512-kdnVrgmxrFtKaRdkoaQBEa02RXgLzEBiooYbxA65BGSJig3PGWDS9LrqNpzLTZM1RQlivd9NLBmfwU2ze4chWA== + dependencies: + "@ledgerhq/devices" "^6.3.0" + "@ledgerhq/errors" "^6.2.0" + events "^3.3.0" + +"@ledgerhq/logs@^5.50.0": + version "5.50.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.50.0.tgz#29c6419e8379d496ab6d0426eadf3c4d100cd186" + integrity sha512-swKHYCOZUGyVt4ge0u8a7AwNcA//h4nx5wIi0sruGye1IJ5Cva0GyK9L2/WdX+kWVTKp92ZiEo1df31lrWGPgA== + +"@ledgerhq/logs@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.2.0.tgz#9fb2d6f1811316697f7b3cc14607f6c608912419" + integrity sha512-SLyFyD7ElMhgKWPYedFGCT/ilcbGPgL5hXXYHxOM79Fs5fWi0zaUpt5oGqGMsOAAFaMa9/rbun0pokzPhEFz8A== + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz" @@ -2507,6 +2602,11 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bip32-path@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/bip32-path/-/bip32-path-0.4.2.tgz#5db0416ad6822712f077836e2557b8697c0c7c99" + integrity sha1-XbBBataCJxLwd4NuJVe4aXwMfJk= + bip39@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.0.4.tgz#5b11fed966840b5e1b8539f0f54ab6392969b2a0" @@ -4684,7 +4784,7 @@ eventemitter3@^4.0.0: resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.0.0: +events@^3.0.0, events@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -6850,6 +6950,16 @@ leaflet@1.6.0: resolved "https://registry.npmjs.org/leaflet/-/leaflet-1.6.0.tgz" integrity sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ== +ledger-cosmos-js@2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/ledger-cosmos-js/-/ledger-cosmos-js-2.1.8.tgz#b409ecd1e77f630e6fb212a9f602fe5c6e8f054b" + integrity sha512-Gl7SWMq+3R9OTkF1hLlg5+1geGOmcHX9OdS+INDsGNxSiKRWlsWCvQipGoDnRIQ6CPo2i/Ze58Dw0Mt/l3UYyA== + dependencies: + "@babel/runtime" "^7.11.2" + "@ledgerhq/hw-transport" "^5.25.0" + bech32 "^1.1.4" + ripemd160 "^2.0.2" + levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" @@ -9414,7 +9524,7 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.6.0, rxjs@^6.6.6: +rxjs@6, rxjs@^6.6.0, rxjs@^6.6.6: version "6.6.7" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==