forked from cerc-io/cosmos-explorer
518 lines
15 KiB
Vue
518 lines
15 KiB
Vue
<template>
|
|
<div>
|
|
<form-wizard
|
|
ref="wizard"
|
|
color="#7367F0"
|
|
:title="null"
|
|
:subtitle="null"
|
|
shape="square"
|
|
finish-button-text="Save"
|
|
back-button-text="Previous"
|
|
class="steps-transparent mb-3 md"
|
|
@on-complete="formSubmitted"
|
|
>
|
|
<!-- Device tab -->
|
|
<tab-content
|
|
title="Device"
|
|
:before-change="validationFormDevice"
|
|
>
|
|
<validation-observer
|
|
ref="deviceRules"
|
|
tag="form"
|
|
>
|
|
<b-row>
|
|
<b-col md="12">
|
|
<b-form-group
|
|
label="Select a device to import accounts"
|
|
label-for="device"
|
|
>
|
|
<validation-provider
|
|
#default="{ errors }"
|
|
name="device"
|
|
rules="required"
|
|
>
|
|
<b-form-radio-group
|
|
v-model="device"
|
|
stacked
|
|
>
|
|
|
|
<b-form-radio
|
|
v-model="device"
|
|
name="device"
|
|
value="keplr"
|
|
checked
|
|
class="mb-1 mt-1"
|
|
>
|
|
Keplr
|
|
</b-form-radio>
|
|
<b-form-radio
|
|
v-model="device"
|
|
name="device"
|
|
value="ledger"
|
|
class="mb-1"
|
|
>
|
|
Ledger via WebUSB
|
|
</b-form-radio>
|
|
<b-form-radio
|
|
v-model="device"
|
|
name="device"
|
|
value="ledger2"
|
|
class="mb-1"
|
|
>
|
|
Ledger via Bluetooth
|
|
</b-form-radio>
|
|
<b-form-radio
|
|
v-model="device"
|
|
name="device"
|
|
value="address"
|
|
>
|
|
Address (Observe Only)
|
|
</b-form-radio>
|
|
</b-form-radio-group>
|
|
<b-form-input
|
|
v-if="device === 'address'"
|
|
v-model="address"
|
|
class="mt-1"
|
|
name="address"
|
|
placeholder="cosmos1ev0vtddkl7jlwfawlk06yzncapw2x9quyxx75u"
|
|
/>
|
|
<small class="text-danger">{{ debug }}{{ errors[0] }}</small>
|
|
</validation-provider>
|
|
</b-form-group>
|
|
</b-col>
|
|
<b-col
|
|
v-if="device.startsWith('ledger')"
|
|
md="12"
|
|
>
|
|
<b-form-group
|
|
label="HD Path"
|
|
label-for="hdpath"
|
|
>
|
|
<validation-provider
|
|
#default="{ errors }"
|
|
name="HD Path"
|
|
rules="required"
|
|
>
|
|
<b-form-input
|
|
v-model="hdpath"
|
|
class="mt-1"
|
|
name="hdpath"
|
|
placeholder="m/44'/118/0'/0/0"
|
|
/>
|
|
<small class="text-danger">{{ errors[0] }}</small>
|
|
</validation-provider>
|
|
</b-form-group>
|
|
</b-col>
|
|
</b-row>
|
|
</validation-observer>
|
|
</tab-content>
|
|
|
|
<!-- address -->
|
|
<tab-content
|
|
title="Accounts"
|
|
:before-change="validationFormAddress"
|
|
>
|
|
<validation-observer
|
|
ref="accountRules"
|
|
tag="form"
|
|
>
|
|
<b-row>
|
|
<b-col md="12">
|
|
<b-form-group
|
|
label="Account Name"
|
|
label-for="account_name"
|
|
>
|
|
<validation-provider
|
|
#default="{ errors }"
|
|
name="Account Name"
|
|
rules="required"
|
|
>
|
|
<b-form-input
|
|
id="account_name"
|
|
v-model="name"
|
|
:state="errors.length > 0 ? false:null"
|
|
placeholder="Ping Nano X"
|
|
:readonly="edit"
|
|
/>
|
|
<small class="text-danger">{{ errors[0] }}</small>
|
|
</validation-provider>
|
|
</b-form-group>
|
|
</b-col>
|
|
<b-col
|
|
v-if="hdpath"
|
|
md="12"
|
|
>
|
|
<b-form-group
|
|
label="HD Path"
|
|
label-for="ir"
|
|
>
|
|
<b-form-input
|
|
id="ir"
|
|
:value="hdpath"
|
|
readonly
|
|
/>
|
|
</b-form-group>
|
|
</b-col>
|
|
<b-col
|
|
v-if="accounts"
|
|
md="12"
|
|
>
|
|
<b-form-group
|
|
label="Public Key"
|
|
label-for="ir"
|
|
>
|
|
<validation-provider
|
|
#default="{ errors }"
|
|
name="Public Key"
|
|
rules="required"
|
|
>
|
|
<b-form-input
|
|
id="ir"
|
|
:value="formatPubkey(accounts.pubkey)"
|
|
readonly
|
|
:state="errors.length > 0 ? false:null"
|
|
/>
|
|
<small class="text-danger">{{ errors[0] }}</small>
|
|
</validation-provider>
|
|
</b-form-group>
|
|
</b-col>
|
|
<b-col md="12">
|
|
<b-form-group
|
|
label="Import Address For Chains:"
|
|
>
|
|
<validation-provider
|
|
#default="{ errors }"
|
|
name="addrs"
|
|
rules="required"
|
|
>
|
|
<div class="demo-inline-spacing text-uppercase">
|
|
<b-row>
|
|
<b-col
|
|
v-for="item, key in chains"
|
|
:key="key"
|
|
xs="12"
|
|
md="4"
|
|
lg="3"
|
|
class="mb-25"
|
|
>
|
|
<b-form-checkbox
|
|
v-model="selected"
|
|
name="addrs"
|
|
:value="key"
|
|
>
|
|
<b-avatar
|
|
:src="item.logo"
|
|
size="18"
|
|
variant="light-primary"
|
|
rounded=""
|
|
/>
|
|
<span
|
|
v-b-tooltip.hover.v-primary
|
|
:title="`Coin Type: ${item.coin_type}`"
|
|
:class="hdpath.startsWith(`m/44'/${item.coin_type}`)?'text-success':'text-danger'"
|
|
> {{ item.chain_name }}</span>
|
|
</b-form-checkbox>
|
|
</b-col>
|
|
</b-row>
|
|
</div>
|
|
<small class="text-success">{{ errors[0] }}</small>
|
|
</validation-provider>
|
|
</b-form-group>
|
|
<b-alert
|
|
show
|
|
variant="info"
|
|
>
|
|
<div class="alert-heading">
|
|
IMPORTANT
|
|
</div>
|
|
<div class="alert-body">
|
|
<div>
|
|
If you don't have Ledger, Do not import those addresses in <b class="text-danger">RED</b>. Because these addresses are derived from different coin_type which may not as same as the offical one
|
|
</div>
|
|
</div>
|
|
</b-alert>
|
|
</b-col>
|
|
</b-row>
|
|
</validation-observer>
|
|
</tab-content>
|
|
|
|
<tab-content
|
|
title="Confirmation"
|
|
>
|
|
<div class="d-flex border-bottom mb-2">
|
|
<feather-icon
|
|
icon="UserIcon"
|
|
size="19"
|
|
class="mb-50"
|
|
/>
|
|
<h4 class="mb-0 ml-50">
|
|
{{ name }} <small> {{ hdpath }}</small>
|
|
</h4>
|
|
</div>
|
|
|
|
<b-row class="mb-2">
|
|
<b-col
|
|
v-for="i in addresses"
|
|
:key="i.addr"
|
|
cols="12"
|
|
>
|
|
<b-input-group class="mb-25">
|
|
<b-input-group-prepend is-text>
|
|
<b-avatar
|
|
:src="i.logo"
|
|
size="18"
|
|
variant="light-primary"
|
|
rounded
|
|
/>
|
|
</b-input-group-prepend>
|
|
<b-form-input :value="i.addr" />
|
|
</b-input-group>
|
|
</b-col>
|
|
</b-row>
|
|
</tab-content>
|
|
</form-wizard>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { FormWizard, TabContent } from 'vue-form-wizard'
|
|
import { ValidationProvider, ValidationObserver } from 'vee-validate'
|
|
import ToastificationContent from '@core/components/toastification/ToastificationContent.vue'
|
|
// import 'vue-form-wizard/dist/vue-form-wizard.min.css'
|
|
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
|
|
import {
|
|
BAlert,
|
|
BRow,
|
|
BCol,
|
|
BFormGroup,
|
|
BFormInput,
|
|
BFormRadio,
|
|
BFormCheckbox,
|
|
BAvatar,
|
|
BInputGroup,
|
|
BInputGroupPrepend,
|
|
BFormRadioGroup,
|
|
VBTooltip,
|
|
} from 'bootstrap-vue'
|
|
import { required } from '@validations'
|
|
import store from '@/store'
|
|
import {
|
|
addressDecode, addressEnCode, getLedgerAddress, getLocalAccounts,
|
|
} from '@/libs/utils'
|
|
import { toHex } from '@cosmjs/encoding'
|
|
|
|
export default {
|
|
components: {
|
|
BAlert,
|
|
ValidationProvider,
|
|
ValidationObserver,
|
|
FormWizard,
|
|
TabContent,
|
|
BAvatar,
|
|
BRow,
|
|
BCol,
|
|
BFormGroup,
|
|
BFormInput,
|
|
BFormRadio,
|
|
BFormCheckbox,
|
|
BInputGroup,
|
|
BInputGroupPrepend,
|
|
BFormRadioGroup,
|
|
// eslint-disable-next-line vue/no-unused-components
|
|
ToastificationContent,
|
|
},
|
|
directives: {
|
|
'b-tooltip': VBTooltip,
|
|
},
|
|
data() {
|
|
return {
|
|
debug: '',
|
|
device: 'keplr',
|
|
address: '',
|
|
hdpath: "m/44'/118/0'/0/0",
|
|
name: '',
|
|
options: {},
|
|
required,
|
|
selected: [],
|
|
accounts: null,
|
|
exludes: [], // HD Path is NOT supported,
|
|
edit: false,
|
|
}
|
|
},
|
|
computed: {
|
|
chains() {
|
|
const config = JSON.parse(localStorage.getItem('chains'))
|
|
this.exludes.forEach(x => {
|
|
delete config[x]
|
|
})
|
|
return config
|
|
},
|
|
addresses() {
|
|
if (this.accounts && this.accounts.address) {
|
|
const { data } = addressDecode(this.accounts.address)
|
|
return this.selected.map(x => {
|
|
if (this.chains[x]) {
|
|
const { logo, addr_prefix } = this.chains[x]
|
|
const addr = addressEnCode(addr_prefix, data)
|
|
return {
|
|
chain: x, addr, logo, hdpath: this.hdpath,
|
|
}
|
|
}
|
|
return null
|
|
}).filter(x => x != null)
|
|
}
|
|
return []
|
|
},
|
|
},
|
|
mounted() {
|
|
const { selected } = store.state.chains
|
|
if (selected && selected.chain_name && !this.exludes.includes(selected.chain_name)) {
|
|
this.selected.push(selected.chain_name)
|
|
}
|
|
const name = new URLSearchParams(window.location.search).get('name')
|
|
const wallets = getLocalAccounts()
|
|
if (name && wallets && wallets[name]) {
|
|
const wallet = wallets[name]
|
|
this.device = wallet.device
|
|
this.name = wallet.name
|
|
this.edit = true
|
|
if (wallet.address) {
|
|
wallet.address.forEach(a => {
|
|
if (!this.selected.includes(a.chain)) {
|
|
this.selected.push(a.chain)
|
|
}
|
|
})
|
|
this.address = wallet.address[0].addr
|
|
this.hdpath = wallet.address[0].hdpath
|
|
if (this.localAddress()) {
|
|
this.$refs.wizard.nextTab()
|
|
}
|
|
}
|
|
} else {
|
|
this.hdpath = `m/44'/${selected.coin_type}/0'/0/0`
|
|
}
|
|
},
|
|
methods: {
|
|
formatPubkey(v) {
|
|
if (typeof (v) === 'string') {
|
|
return v
|
|
}
|
|
if (v) {
|
|
return toHex(v)
|
|
}
|
|
return ''
|
|
},
|
|
async connect() {
|
|
const transport = this.device === 'ledger' ? 'usb' : 'bluetooth'
|
|
return getLedgerAddress(transport, this.hdpath)
|
|
},
|
|
async cennectKeplr() {
|
|
if (!window.getOfflineSigner || !window.keplr) {
|
|
this.debug = 'Please install keplr extension'
|
|
return null
|
|
}
|
|
// const chainId = 'cosmoshub'
|
|
const chainId = await this.$http.getLatestBlock().then(ret => ret.block.header.chain_id)
|
|
await window.keplr.enable(chainId)
|
|
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) : {}
|
|
|
|
accounts[this.name] = {
|
|
name: this.name,
|
|
device: this.device,
|
|
address: this.addresses,
|
|
}
|
|
localStorage.setItem('accounts', JSON.stringify(accounts))
|
|
if (!this.$store.state.chains.defaultWallet) {
|
|
this.$store.commit('setDefaultWallet', this.name)
|
|
}
|
|
|
|
this.$toast({
|
|
component: ToastificationContent,
|
|
props: {
|
|
title: 'Address Saved!',
|
|
icon: 'EditIcon',
|
|
variant: 'success',
|
|
},
|
|
})
|
|
|
|
this.$router.push('./accounts')
|
|
},
|
|
async validationFormDevice() {
|
|
let ok = String(this.name).length > 0
|
|
|
|
if (!ok) { // new import, otherwise it's edit mode.
|
|
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) {
|
|
// eslint-disable-next-line prefer-destructuring
|
|
this.accounts = accounts[0]
|
|
ok = true
|
|
}
|
|
}).catch(e => {
|
|
this.debug = e
|
|
})
|
|
break
|
|
default:
|
|
ok = this.localAddress()
|
|
}
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this.$refs.deviceRules.validate().then(success => {
|
|
if (ok && success) {
|
|
resolve(true)
|
|
}
|
|
reject()
|
|
})
|
|
})
|
|
},
|
|
validationFormAddress() {
|
|
return new Promise((resolve, reject) => {
|
|
this.$refs.accountRules.validate().then(success => {
|
|
if (success) {
|
|
resolve(true)
|
|
} else {
|
|
reject()
|
|
}
|
|
})
|
|
})
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
// @import '@core/assets/fonts/feather/iconfont.css';
|
|
@import '@core/scss/vue/libs/vue-wizard.scss';
|
|
</style>
|