From 927ffcbfe5a272503f0eacabc29070c30c1bb81e Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 25 Aug 2021 15:45:40 +0800 Subject: [PATCH] finish withdraw and transfer --- package.json | 6 +- .../app-navbar/components/SearchBar.vue | 1 + src/libs/data/data.js | 97 +++- src/libs/fetch.js | 49 +- src/libs/stargate-client.js | 32 ++ src/views/OperationTransferComponent.vue | 456 ++++++++++++++++++ src/views/OperationWithdrawComponent.vue | 358 ++++++++++++++ src/views/UserAccountDetail.vue | 17 +- src/views/UserAccounts.vue | 93 ++-- yarn.lock | 340 ++++++++++++- 10 files changed, 1392 insertions(+), 57 deletions(-) create mode 100644 src/libs/stargate-client.js create mode 100644 src/views/OperationTransferComponent.vue create mode 100644 src/views/OperationWithdrawComponent.vue diff --git a/package.json b/package.json index 403f09ec..7f81763b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,12 @@ "@cosmjs/amino": "^0.25.6", "@cosmjs/crypto": "^0.25.6", "@cosmjs/encoding": "^0.25.6", + "@cosmjs/launchpad": "^0.25.6", + "@cosmjs/ledger-amino": "^0.25.6", + "@cosmjs/math": "^0.25.6", "@cosmjs/proto-signing": "^0.25.6", + "@cosmjs/stargate": "^0.25.6", + "@cosmostation/cosmosjs": "^0.10.6", "@intlify/vue-i18n-loader": "^2.1.2", "@ledgerhq/hw-app-cosmos": "^6.3.0", "@ledgerhq/hw-transport-web-ble": "^6.3.0", @@ -73,7 +78,6 @@ "vuex": "3.6.0" }, "devDependencies": { - "@cosmjs/launchpad": "^0.25.6", "@vue/cli-plugin-babel": "~4.5.9", "@vue/cli-plugin-eslint": "~4.5.9", "@vue/cli-plugin-router": "~4.5.9", diff --git a/src/@core/layouts/components/app-navbar/components/SearchBar.vue b/src/@core/layouts/components/app-navbar/components/SearchBar.vue index 64aff251..ecffb571 100644 --- a/src/@core/layouts/components/app-navbar/components/SearchBar.vue +++ b/src/@core/layouts/components/app-navbar/components/SearchBar.vue @@ -84,6 +84,7 @@ export default { } else if (txhash.test(key)) { this.$router.push({ name: 'transaction', params: { chain: c.chain_name, hash: key } }) } else if (addr.test(key)) { + this.$router.push({ name: 'chain-account', params: { chain: c.chain_name, address: key } }) // console.log('address', key) } } diff --git a/src/libs/data/data.js b/src/libs/data/data.js index 49fb4f1a..797a2c7a 100644 --- a/src/libs/data/data.js +++ b/src/libs/data/data.js @@ -5,14 +5,24 @@ import { sha256 } from '@cosmjs/crypto' // ledger import TransportWebBLE from '@ledgerhq/hw-transport-web-ble' import TransportWebUSB from '@ledgerhq/hw-transport-webusb' +import { SigningStargateClient } from '@cosmjs/stargate' // import Cosmos from '@ledgerhq/hw-app-cosmos' import CosmosApp from 'ledger-cosmos-js' +import { LedgerSigner } from '@cosmjs/ledger-amino' +import { + makeAuthInfoBytes, makeSignBytes, Registry, +} from '@cosmjs/proto-signing' +import { encodeSecp256k1Signature } from '@cosmjs/amino' +import { Uint53 } from '@cosmjs/math' +import { TxRaw } from '@cosmjs/stargate/build/codec/cosmos/tx/v1beta1/tx' import dayjs from 'dayjs' import duration from 'dayjs/plugin/duration' import relativeTime from 'dayjs/plugin/relativeTime' import localeData from 'dayjs/plugin/localeData' +const COSMOS_PATH = [44, 118, 0, 0, 0] + dayjs.extend(localeData) dayjs.extend(duration) dayjs.extend(relativeTime) @@ -21,7 +31,87 @@ 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] + +/* eslint-enable */ +function unharden(hdPath) { + return hdPath.map(n => (n.isHardened() ? n.toNumber() - 2 ** 31 : n.toNumber())) +} + +function makeSignDoc(bodyBytes, authInfoBytes, chainId, accountNumber) { + return { + bodyBytes, + authInfoBytes, + chainId, + accountNumber: Uint53.fromString(String(accountNumber)), + } +} + +export async function signDirect(signer, signerAddress, messages, fee, memo, { accountNumber, sequence, chainId }) { + // const accountFromSigner = (await this.signer.getAccounts()).find(account => account.address === signerAddress) + // if (!accountFromSigner) { + // throw new Error("Failed to retrieve account from signer"); + // } + const accountFromSigner = await signer.getAddressAndPubKey(COSMOS_PATH, 'cosmos') + if (!accountFromSigner) { + throw new Error('Failed to retrieve account from signer') + } + const { pubkey } = accountFromSigner + console.log('accout:', accountFromSigner, pubkey) + const txBodyEncodeObject = { + typeUrl: '/cosmos.tx.v1beta1.TxBody', + value: { + messages, + memo, + }, + } + const txBodyBytes = new Registry().encode(txBodyEncodeObject) + const gasLimit = Uint53.fromString(String(fee.gas)) + console.log('account from signer: ', txBodyBytes, gasLimit) + const authInfoBytes = makeAuthInfoBytes([pubkey], fee.amount, gasLimit, sequence) + console.log('authinfo: ', authInfoBytes, chainId, accountNumber) + const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber) + console.log('signdoc: ', signDoc, signer) + // const { signature, signed } = await signer.signDirect(signerAddress, signDoc) + const signBytes = makeSignBytes(signDoc) + const hashedMessage = sha256(signBytes) + const signature = await signer.sign(unharden(COSMOS_PATH), hashedMessage) + const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]) + const stdSignature = encodeSecp256k1Signature(pubkey, signatureBytes) + + console.log('custom sign:', txBodyBytes, authInfoBytes, signDoc) + return TxRaw.fromPartial({ + bodyBytes: txBodyBytes, + authInfoBytes, + signatures: [fromBase64(stdSignature)], + }) +} + +export async function sign(device, chainId, signerAddress, messages, fee, memo, signerData) { + let transport + let signer + switch (device) { + case 'ledgerBle': + transport = await TransportWebBLE.create() + signer = new LedgerSigner(transport) + break + case 'ledgerUSB': + transport = await TransportWebUSB.create() + signer = new LedgerSigner(transport) + break + case 'keplr': + default: + if (!window.getOfflineSigner || !window.keplr) { + throw new Error('Please install keplr extension') + } + await window.keplr.enable(chainId) + signer = window.getOfflineSignerOnlyAmino(chainId) + } + + // Ensure the address has some tokens to spend + const client = await SigningStargateClient.offline(signer) + return client.signAmino(signerAddress, messages, fee, memo, signerData) + // return signDirect(signer, signerAddress, messages, fee, memo, signerData) +} export async function getLedgerAddress(transport = 'blu') { const trans = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create() @@ -51,6 +141,11 @@ export function toDuration(value) { return dayjs.duration(value).humanize() } +// unit(y M d h m s ms) +export function timeIn(time, amount, unit = 's') { + return dayjs().isAfter(dayjs(time).add(amount, unit)) +} + export function toDay(time, format = 'long') { if (format === 'long') { return dayjs(time).format('YYYY-MM-DD HH:mm') diff --git a/src/libs/fetch.js b/src/libs/fetch.js index 79b15390..d0223c90 100644 --- a/src/libs/fetch.js +++ b/src/libs/fetch.js @@ -1,6 +1,9 @@ import fetch from 'node-fetch' +// import axios from 'axios' import store from '@/store' import compareVersions from 'compare-versions' +import { TxRaw } from '@cosmjs/stargate/build/codec/cosmos/tx/v1beta1/tx' +import { toBase64 } from '@cosmjs/encoding' import { Proposal, ProposalTally, Proposer, StakingPool, Votes, Deposit, Validator, StakingParameters, Block, ValidatorDistribution, StakingDelegation, WrapStdTx, @@ -40,8 +43,8 @@ const chainAPI = class ChainFetch { return true } - async getLatestBlock() { - return this.get('/blocks/latest').then(data => Block.create(data)) + async getLatestBlock(config = null) { + return this.get('/blocks/latest', config).then(data => Block.create(data)) } async getBlockByHeight(height) { @@ -183,9 +186,11 @@ const chainAPI = class ChainFetch { }) } - async get(url) { - this.getSelectedConfig() - const ret = await fetch(this.config.api + url).then(response => response.json()) + async get(url, config = null) { + if (!config) { + this.getSelectedConfig() + } + const ret = await fetch((config ? config.api : this.config.api) + url).then(response => response.json()) return ret } @@ -194,8 +199,8 @@ const chainAPI = class ChainFetch { return ret } - async getAuthAccount(address) { - return this.get('/auth/accounts/'.concat(address)).then(data => commonProcess(data)) + async getAuthAccount(address, config = null) { + return this.get('/auth/accounts/'.concat(address), config).then(data => commonProcess(data)) } async getBankAccountBalance(address) { @@ -245,6 +250,36 @@ const chainAPI = class ChainFetch { static async fetchTokenQuote(symbol) { return ChainFetch.fetchCoinMarketCap(`/quote/${symbol}`) } + + async broadcastTx(bodyBytes, config = null) { + const txString = toBase64(TxRaw.encode(bodyBytes).finish()) + const txRaw = { + tx_bytes: txString, + mode: 'BROADCAST_MODE_SYNC', + } + return this.post('/cosmos/tx/v1beta1/txs', txRaw, config) + } + + async post(url = '', data = {}, config = null) { + if (!config) { + this.getSelectedConfig() + } + // Default options are marked with * + const response = await fetch((config ? config.api : this.config.api) + url, { + method: 'POST', // *GET, POST, PUT, DELETE, etc. + // mode: 'cors', // no-cors, *cors, same-origin + // credentials: 'same-origin', // redirect: 'follow', // manual, *follow, error + // referrerPolicy: 'origin', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + headers: { + 'Content-Type': 'text/plain', + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + }, + body: JSON.stringify(data), // body data type must match "Content-Type" header + }) + // const response = axios.post((config ? config.api : this.config.api) + url, data) + return response.json() // parses JSON response into native JavaScript objects + } } export default chainAPI diff --git a/src/libs/stargate-client.js b/src/libs/stargate-client.js new file mode 100644 index 00000000..324adc02 --- /dev/null +++ b/src/libs/stargate-client.js @@ -0,0 +1,32 @@ +import { + makeAuthInfoBytes, makeSignDoc, Registry, + // makeSignBytes, +} from '@cosmjs/proto-signing' + +export default class Client { + async signDirect(signerAddress, messages, fee, memo, { accountNumber, sequence, chainId }) { + // const accountFromSigner = (await this.signer.getAccounts()).find(account => account.address === signerAddress) + // if (!accountFromSigner) { + // throw new Error('Failed to retrieve account from signer') + // } + const pubkey = '' + const txBodyEncodeObject = { + typeUrl: '/cosmos.tx.v1beta1.TxBody', + value: { + messages, + memo, + }, + } + const txBodyBytes = new Registry().encode(txBodyEncodeObject) + const gasLimit = 1 + const authInfoBytes = makeAuthInfoBytes([pubkey], fee.amount, gasLimit, sequence) + const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber) + + const { signature, signed } = await this.signer.signDirect(signerAddress, signDoc) + return tx_4.TxRaw.fromPartial({ + bodyBytes: signed.bodyBytes, + authInfoBytes: signed.authInfoBytes, + signatures: [encoding_1.fromBase64(signature.signature)], + }) + } +} diff --git a/src/views/OperationTransferComponent.vue b/src/views/OperationTransferComponent.vue new file mode 100644 index 00000000..91ba865b --- /dev/null +++ b/src/views/OperationTransferComponent.vue @@ -0,0 +1,456 @@ + + + diff --git a/src/views/OperationWithdrawComponent.vue b/src/views/OperationWithdrawComponent.vue new file mode 100644 index 00000000..420357fa --- /dev/null +++ b/src/views/OperationWithdrawComponent.vue @@ -0,0 +1,358 @@ + + + diff --git a/src/views/UserAccountDetail.vue b/src/views/UserAccountDetail.vue index cc733200..34b960f2 100644 --- a/src/views/UserAccountDetail.vue +++ b/src/views/UserAccountDetail.vue @@ -38,6 +38,7 @@ Assets @@ -103,6 +104,7 @@ Delegation @@ -169,7 +171,7 @@ v-for="p, index in account.value.vesting_periods" :key="index" > - {{ p.length }} - {{ formatLength(p.length) }} {{ formatToken(p.amount) }} + {{ p.length }}
{{ formatLength(p.length) }}
{{ formatToken(p.amount) }} @@ -210,12 +212,16 @@ placement="bottom" > - + + + + +