From 1a9aafbd603a0e0ce2d63c8dd0cda789ac83b10b Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Tue, 14 Jun 2022 15:30:39 +0800 Subject: [PATCH] support signning evmos transactions with ledger --- package.json | 8 +- src/libs/client/AminoTypes.ts | 86 -- src/libs/client/EthereumLedgerSigner.ts | 68 +- src/libs/client/MessageAdapter.ts | 134 +++ src/libs/client/MetaMaskSigner.js | 29 + src/libs/client/PingWalletClient.ts | 266 ++++- src/libs/fetch.js | 3 +- src/views/WalletAccountImportAddress.vue | 29 + .../components/OperationModal/WalletInput.vue | 7 + yarn.lock | 999 +++++++++++++++++- 10 files changed, 1432 insertions(+), 197 deletions(-) delete mode 100644 src/libs/client/AminoTypes.ts create mode 100644 src/libs/client/MessageAdapter.ts create mode 100644 src/libs/client/MetaMaskSigner.js diff --git a/package.json b/package.json index 6bc4f96d..1ecc245c 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,14 @@ "@cosmjs/math": "^0.28.4", "@cosmjs/proto-signing": "^0.28.4", "@cosmjs/stargate": "0.28.4", + "@hanchon/signature-to-pubkey": "^1.0.0", "@intlify/vue-i18n-loader": "^2.1.2", "@ledgerhq/hw-app-eth": "^6.28.2", "@ledgerhq/hw-transport-web-ble": "^6.27.1", "@ledgerhq/hw-transport-webusb": "^6.27.1", - "@tharsis/address-converter": "^0.1.7", - "@tharsis/transactions": "^0.2.2", + "@metamask/eth-sig-util": "^4.0.1", + "@tharsis/address-converter": "^0.1.8", + "@tharsis/transactions": "^0.2.3", "@vue/composition-api": "^1.4.9", "@vueuse/core": "4.0.0", "animate.css": "4.1.1", @@ -41,6 +43,8 @@ "cosmjs-types": "^0.2.0", "dayjs": "^1.10.6", "echarts": "5.3.0", + "eth-crypto": "^2.3.0", + "ethers": "^5.6.8", "leaflet": "1.6.0", "ledger-cosmos-js": "2.1.8", "long": "^5.2.0", diff --git a/src/libs/client/AminoTypes.ts b/src/libs/client/AminoTypes.ts deleted file mode 100644 index ac94740c..00000000 --- a/src/libs/client/AminoTypes.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { AminoMsg } from "@cosmjs/amino"; -import { EncodeObject } from "@cosmjs/proto-signing"; - -export interface AminoConverter { - readonly aminoType: string; - readonly toAmino: (value: any) => any; - readonly fromAmino: (value: any) => any; -} - -/** A map from protobuf type URL to the AminoConverter implementation if supported on chain */ -export type AminoConverters = Record; - -function isAminoConverter( - converter: [string, AminoConverter | "not_supported_by_chain"], -): converter is [string, AminoConverter] { - return typeof converter[1] !== "string"; -} - -/** - * A map from Stargate message types as used in the messages's `Any` type - * to Amino types. - */ -export class AminoTypes { - // The map type here ensures uniqueness of the protobuf type URL in the key. - // There is no uniqueness guarantee of the Amino type identifier in the type - // system or constructor. Instead it's the user's responsibility to ensure - // there is no overlap when fromAmino is called. - private readonly register: Record; - - public constructor(types: AminoConverters) { - this.register = types; - } - - public toAmino({ typeUrl, value }: EncodeObject): AminoMsg { - const converter = this.register[typeUrl]; - if (converter === "not_supported_by_chain") { - throw new Error( - `The message type '${typeUrl}' cannot be signed using the Amino JSON sign mode because this is not supported by chain.`, - ); - } - if (!converter) { - throw new Error( - `Type URL '${typeUrl}' does not exist in the Amino message type register. ` + - "If you need support for this message type, you can pass in additional entries to the AminoTypes constructor. " + - "If you think this message type should be included by default, please open an issue at https://github.com/cosmos/cosmjs/issues.", - ); - } - return { - type: converter.aminoType, - value: converter.toAmino(value), - }; - } - - public fromAmino({ type, value }: AminoMsg): EncodeObject { - const matches = Object.entries(this.register) - .filter(isAminoConverter) - .filter(([_typeUrl, { aminoType }]) => aminoType === type); - - switch (matches.length) { - case 0: { - throw new Error( - `Amino type identifier '${type}' does not exist in the Amino message type register. ` + - "If you need support for this message type, you can pass in additional entries to the AminoTypes constructor. " + - "If you think this message type should be included by default, please open an issue at https://github.com/cosmos/cosmjs/issues.", - ); - } - case 1: { - const [typeUrl, converter] = matches[0]; - return { - typeUrl: typeUrl, - value: converter.fromAmino(value), - }; - } - default: - throw new Error( - `Multiple types are registered with Amino type identifier '${type}': '` + - matches - .map(([key, _value]) => key) - .sort() - .join("', '") + - "'. Thus fromAmino cannot be performed.", - ); - } - } -} \ No newline at end of file diff --git a/src/libs/client/EthereumLedgerSigner.ts b/src/libs/client/EthereumLedgerSigner.ts index 8faff5e4..f8218f8f 100644 --- a/src/libs/client/EthereumLedgerSigner.ts +++ b/src/libs/client/EthereumLedgerSigner.ts @@ -5,13 +5,18 @@ import Transport from "@ledgerhq/hw-transport"; import Eth from "@ledgerhq/hw-app-eth"; import { LedgerEthTransactionResolution, LoadConfig } from "@ledgerhq/hw-app-eth/lib/services/types"; -import { fromBech32, toHex } from "@cosmjs/encoding"; +import { fromBech32, fromHex, toBech32, toHex } from "@cosmjs/encoding"; import TransportWebBLE from '@ledgerhq/hw-transport-web-ble' import TransportWebUSB from '@ledgerhq/hw-transport-webusb' -import { HdPath } from "@cosmjs/crypto"; -import { ethToCosmos } from '@tharsis/address-converter' +import { HdPath, keccak256, pathToString } from "@cosmjs/crypto"; +import { ethToEvmos } from '@tharsis/address-converter' import eth from "@tharsis/proto/dist/proto/ethermint/crypto/v1/ethsecp256k1/keys"; +import { Secp256k1 } from "@cosmjs/crypto" +import EthCrypto from "eth-crypto" +import { MessageTypeProperty, SignTypedDataVersion, TypedDataUtils, TypedMessage, MessageTypes } from "@metamask/eth-sig-util"; +import { EIPToSign } from "@tharsis/transactions"; +// import { recoverPublicKey } from "@metamask/eth-sig-util/dist/utils"; // export type Algo = "secp256k1" | "ed25519" | "sr25519" | "ethsecp256k1"; // export interface AccountData { // /** A printable address (typically bech32 encoded) */ @@ -20,34 +25,35 @@ import eth from "@tharsis/proto/dist/proto/ethermint/crypto/v1/ethsecp256k1/keys // readonly pubkey: Uint8Array; // } + export class EthereumLedgerSigner implements OfflineAminoSigner{ app: Eth - hdpath: string + hdpath: string = "44'/60'/0'/0/0" + prefix: string = "evmos" - static async create(protocol: string, hdpath: string, scrambleKey = "w0w", loadConfig?: LoadConfig): Promise { + static async create(protocol: string, hdpath: HdPath, scrambleKey = "w0w", loadConfig?: LoadConfig): Promise { let transport: Promise = protocol === 'ledgerBle' ? TransportWebBLE.create() : TransportWebUSB.create() return transport.then(t => { let instance = new EthereumLedgerSigner() - instance.hdpath = hdpath + instance.hdpath = pathToString(hdpath).replace('m/', '').replace('/60/', "/60'/") instance.app = new Eth(t, scrambleKey, loadConfig) return instance }) } - async getAccounts(): Promise { - console.log('eth:', this.app, this.hdpath) - - return this.app.getAddress("44'/60'/0'/0/0").then(x => { + + public async getAccounts(): Promise { + return this.app.getAddress(this.hdpath).then(x => { const x1: AccountData = { - pubkey: new TextEncoder().encode(x.publicKey), + pubkey: fromHex(EthCrypto.publicKey.compress(x.publicKey)), address: x.address, algo: "secp256k1" // should be 'ethsecp256k1' } const x2: AccountData = { - pubkey: new TextEncoder().encode(x.publicKey), - address: ethToCosmos(x.address), - algo: "secp256k1" // // should be 'ethsecp256k1' + pubkey: fromHex(EthCrypto.publicKey.compress(x.publicKey)), + address: toBech32(this.prefix, fromBech32(ethToEvmos(x.address)).data), + algo: "secp256k1" // should be 'ethsecp256k1' } - return [x1, x2] + return [x2, x1] }) }; @@ -64,7 +70,7 @@ export class EthereumLedgerSigner implements OfflineAminoSigner{ } } - async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { + public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { const ethAddr = this.goEthAddress(signerAddress) return this.getAccounts().then(list => { const acc = list.find(x => x.address === ethAddr) @@ -87,15 +93,29 @@ export class EthereumLedgerSigner implements OfflineAminoSigner{ }) }; - async showAddress(path?: HdPath): Promise { - return new Promise((r, j) => { }) - } - - // async sign712Transaction(rawTxHex: string, resolution: LedgerEthTransactionResolution) { - // return this.app.signEIP712HashedMessage(this.hdpath, rawTxHex, resolution) + // async showAddress(path?: HdPath): Promise { + // return new Promise((r, j) => { }) // } - async signTransaction(rawTxHex: string, resolution?: LedgerEthTransactionResolution) { - return this.app.signTransaction(this.hdpath, rawTxHex, resolution) + // async signTransaction(rawTxHex: string, resolution?: LedgerEthTransactionResolution) { + // return this.app.signPersonalMessage(this.hdpath, rawTxHex) + // } + + async sign712( eipToSign: EIPToSign ) { + + /// sign typed struct + const types = eipToSign.types as Record + const domainSeparatorHex = TypedDataUtils.hashStruct('EIP712Domain', eipToSign.domain, types, SignTypedDataVersion.V4).toString('hex') + // console.log('hex1: ', domainSeparatorHex) + const hashStructMessageHex = toHex(keccak256(TypedDataUtils.encodeData('Tx', eipToSign.message as Record, types, SignTypedDataVersion.V4))) + // console.log('hex2:', hashStructMessageHex) + const signature = await this.app.signEIP712HashedMessage(this.hdpath, domainSeparatorHex, hashStructMessageHex) + let v: string = (signature.v - 27).toString(16); + if (v.length < 2) { + v = "0" + v; + } + const sig = "0x"+signature.r + signature.s + v + + return sig } } diff --git a/src/libs/client/MessageAdapter.ts b/src/libs/client/MessageAdapter.ts new file mode 100644 index 00000000..c83f2df7 --- /dev/null +++ b/src/libs/client/MessageAdapter.ts @@ -0,0 +1,134 @@ +import { EncodeObject } from "@cosmjs/proto-signing" +import { + createMsgWithdrawDelegatorReward, + createIBCMsgTransfer, + createMsgSend, + createMsgBeginRedelegate, + createMsgDelegate, + createMsgUndelegate, + createMsgVote, + // createMsgWithdrawValidatorCommission, +} from '@tharsis/proto' +import { + MSG_WITHDRAW_DELEGATOR_REWARD_TYPES, + MSG_BEGIN_REDELEGATE_TYPES, + MSG_DELEGATE_TYPES, + MSG_SEND_TYPES, + MSG_UNDELEGATE_TYPES, + MSG_VOTE_TYPES, + IBC_MSG_TRANSFER_TYPES, + MSG_WITHDRAW_VALIDATOR_COMMISSION_TYPES, +} from "@tharsis/eip712" + + +export interface MessageAdapter { + toProto(message: EncodeObject): object + getTypes: ()=> object +} + +export class WithdrawMessageAdapter implements MessageAdapter { + toProto(message: EncodeObject) { + const param = message.value + return createMsgWithdrawDelegatorReward(param.delegatorAddress, param.validatorAddress) + } + getTypes() { + return MSG_WITHDRAW_DELEGATOR_REWARD_TYPES + } +} + +export class WithdrawCommissionMessageAdapter implements MessageAdapter { + toProto(message: EncodeObject) { + const param = message.value + return createMsgWithdrawDelegatorReward(param.delegatorAddress, param.validatorAddress) + } + getTypes() { + return MSG_WITHDRAW_VALIDATOR_COMMISSION_TYPES + } +} + +export class BeginRedelegateMessageAdapter implements MessageAdapter { + toProto(message: EncodeObject) { + const param = message.value + return createMsgBeginRedelegate( + param.delegatorAddress, + param.validatorSrcAddress, + param.validatorDstAddress, + param.amount.amount, + param.amount.denom) + } + getTypes() { + return MSG_BEGIN_REDELEGATE_TYPES + } +} + +export class DelegateMessageAdapter implements MessageAdapter { + toProto(message: EncodeObject) { + const param = message.value + return createMsgDelegate(param.delegatorAddress, param.validatorAddress, param.amount[0].amount, param.amount[0].denom) + } + getTypes() { + return MSG_DELEGATE_TYPES + } +} + +export class SendMessageAdapter implements MessageAdapter { + toProto(message: EncodeObject) { + const param = message.value + return createMsgSend(param.fromAddress, param.toAddress, param.amount[0].amount, param.amount[0].denom) + } + getTypes() { + return MSG_SEND_TYPES + } +} + +export class UndelegateMessageAdapter implements MessageAdapter { + toProto(message: EncodeObject) { + const param = message.value + return createMsgUndelegate(param.delegatorAddress, param.validatorAddress, param.amount[0].amount, param.amount[0].denom) + } + getTypes() { + return MSG_UNDELEGATE_TYPES + } +} + +export class VoteMessageAdapter implements MessageAdapter { + toProto(message: EncodeObject) { + const param = message.value + return createMsgVote(param.proposalId, param.option, param.voter) + } + getTypes() { + return MSG_VOTE_TYPES + } +} + +export class IBCMessageAdapter implements MessageAdapter { + toProto(message: EncodeObject) { + const param = message.value + return createIBCMsgTransfer( + param.sourcePort, + param.sourceChannel, + param.amount.amount, + param.amount.denom, + param.sender, + param.receiver, + param.revisionNumber, + param.revisionHeight, + param.timeoutTimestamp + ) + } + getTypes() { + return IBC_MSG_TRANSFER_TYPES + } +} + + +export const defaultMessageAdapter: Record = { + "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward": new WithdrawMessageAdapter(), + "/cosmos.staking.v1beta1.MsgDelegate": new DelegateMessageAdapter(), + "/cosmos.staking.v1beta1.MsgBeginRedelegate": new BeginRedelegateMessageAdapter(), + "/cosmos.staking.v1beta1.MsgUndelegate": new UndelegateMessageAdapter(), + "/cosmos.bank.v1beta1.MsgSend": new SendMessageAdapter(), + "/cosmos.gov.v1beta1.MsgVote": new VoteMessageAdapter(), + "/ibc.applications.transfer.v1.MsgTransfer": new IBCMessageAdapter(), + "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission": new WithdrawCommissionMessageAdapter() +} \ No newline at end of file diff --git a/src/libs/client/MetaMaskSigner.js b/src/libs/client/MetaMaskSigner.js new file mode 100644 index 00000000..5f77aa66 --- /dev/null +++ b/src/libs/client/MetaMaskSigner.js @@ -0,0 +1,29 @@ +import { pathToString } from '@cosmjs/crypto' +import { fromHex } from '@cosmjs/encoding' +import { ethToEvmos } from '@tharsis/address-converter' + +export default class MetaMaskSigner { + static create(hdpath) { + const signer = new MetaMaskSigner() + signer.hdpath = pathToString(hdpath).replace('m/', '').replace('/60/', "/60'/") + signer.ethereum = window.ethereum + return signer + } + + async getAccounts() { + // eth_accounts, eth_requestAccounts + return this.ethereum.request({ method: 'eth_accounts' }).then(data => data.map(x => ({ + pubkey: fromHex(x.substring(2)), // should set to public key + address: ethToEvmos(x), + algo: 'ethsecp256k1', + }))) + } + + async sign(signer, eipToSign) { + return this.ethereum.request({ + method: 'eth_signTypedData_v4', + params: [signer, JSON.stringify(eipToSign)], + }) + } + // signAmino: (signerAddress: string, signDoc: StdSignDoc) => Promise; +} diff --git a/src/libs/client/PingWalletClient.ts b/src/libs/client/PingWalletClient.ts index 330a6f02..e267a6a4 100644 --- a/src/libs/client/PingWalletClient.ts +++ b/src/libs/client/PingWalletClient.ts @@ -2,45 +2,63 @@ import { AminoTypes, SignerData, SigningStargateClient, - SigningStargateClientOptions, - StargateClient + defaultRegistryTypes, } from '@cosmjs/stargate'; -import { Registry, OfflineSigner, EncodeObject } from '@cosmjs/proto-signing'; -import { defaultRegistryTypes } from '@cosmjs/stargate'; +import { Registry, EncodeObject, TxBodyEncodeObject, makeAuthInfoBytes, GeneratedType } from '@cosmjs/proto-signing'; import { LedgerSigner } from '@cosmjs/ledger-amino'; import { EthereumLedgerSigner } from './EthereumLedgerSigner'; import TransportWebUSB from '@ledgerhq/hw-transport-webusb'; import TransportWebBLE from '@ledgerhq/hw-transport-web-ble' -import { HdPath, stringToPath } from '@cosmjs/crypto'; -import { StdFee } from "@cosmjs/amino"; -import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; - -// export async function sign(device, chainId, signerAddress, messages, fee, memo, signerData) { -// // let transport -// let signer -// const hdpath = getHdPath(signerAddress) -// const coinType = Number(hdpath[1]) -// switch (device) { -// case 'ledgerBle': -// signer = await getLedgerAppName(coinType, device, hdpath) -// break -// case 'ledgerUSB': -// signer = await getLedgerAppName(coinType, device, hdpath) -// 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 PingWalletClient.offline(signer) -// return client.signAmino(device.startsWith('ledger') ? toSignAddress(signerAddress) : signerAddress, messages, fee, memo, signerData) -// } +import { makeSignDoc, OfflineAminoSigner, Pubkey, pubkeyType, StdFee } from "@cosmjs/amino"; +import { TxBody, TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; +import { + generateMessageWithMultipleTransactions, + MSG_WITHDRAW_DELEGATOR_REWARD_TYPES, + IBC_MSG_TRANSFER_TYPES, + generateTypes, + generateFee, + createEIP712, +} from "@tharsis/eip712" +import { + createTxRawEIP712, signatureToWeb3Extension, Chain, + createMessageSend, createTxMsgWithdrawDelegatorReward, Fee, MsgWithdrawDelegatorRewardParams, + createTxMsgMultipleWithdrawDelegatorReward, + MsgMultipleWithdrawDelegatorRewardParams, + EIPToSign, +} from '@tharsis/transactions' +import { + createTransactionWithMultipleMessages, + createMsgWithdrawDelegatorReward, + createIBCMsgTransfer +} from '@tharsis/proto' +import { fromBase64, fromBech32, fromHex, toBase64, toHex } from '@cosmjs/encoding'; +//import { generateEndpointBroadcast, generatePostBodyBroadcast } from '@tharsis/provider' +import { keccak256, Secp256k1 } from "@cosmjs/crypto" +import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing'; +import { MessageTypeProperty, MessageTypes, SignTypedDataVersion, TypedDataUtils, TypedMessage, } from '@metamask/eth-sig-util' +import MetaMaskSigner from './MetaMaskSigner.js' +import * as eth from '@tharsis/proto/dist/proto/ethermint/crypto/v1/ethsecp256k1/keys' // /ethermint/crypto/v1/ethsecp256k1/keys' +import { + AminoConverters, + createAuthzAminoConverters, + createBankAminoConverters, + createDistributionAminoConverters, + createFreegrantAminoConverters, + createGovAminoConverters, + createIbcAminoConverters, + createStakingAminoConverters, +} from "@cosmjs/stargate" +import { Int53 } from '@cosmjs/math'; +import { Any } from 'cosmjs-types/google/protobuf/any'; +import { legacyToBuffer, recoverPublicKey } from '@metamask/eth-sig-util/dist/utils'; +import { utils } from 'ethers' +import { signatureToPubkey } from '@hanchon/signature-to-pubkey' +import { defaultMessageAdapter } from './MessageAdapter'; +export interface TypedDataField { + name: string; + type: string; +}; export declare type SigningClient = SigningStargateClient | SigningEthermintClient; @@ -64,14 +82,176 @@ export async function getSigningClient(device, hdpath): Promise { return SigningStargateClient.offline(signer) } -export class SigningEthermintClient { - readonly signer: OfflineSigner - constructor(signer: OfflineSigner) { - this.signer = signer - } - - sign(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo: string, explicitSignerData?: SignerData): Promise { - return new Promise(() => { return TxRaw.decode(null) }) - } - +function createDefaultTypes(prefix: string): AminoConverters { + return { + ...createAuthzAminoConverters(), + ...createBankAminoConverters(), + ...createDistributionAminoConverters(), + ...createGovAminoConverters(), + ...createStakingAminoConverters(prefix), + ...createIbcAminoConverters(), + ...createFreegrantAminoConverters(), + // ...createVestingAminoConverters(), + }; } + +function extractChainId(chainId: string) { + const start = chainId.indexOf('_') + const end = chainId.indexOf('-') + if (end > start && start > 0) { + return Number(chainId.substring(start + 1, end)) + } + return 0 +} +export class SigningEthermintClient { + readonly signer: EthereumLedgerSigner + aminoTypes: AminoTypes + constructor(signer: EthereumLedgerSigner) { + this.signer = signer + this.aminoTypes = new AminoTypes(createDefaultTypes("")) + } + + // async signEVM(signerAddress: string, messages: readonly EncodeObject[], stdfee: StdFee, memo: string, explicitSignerData?: SignerData): Promise { + // const chain: Chain = { + // chainId: extractChainId(explicitSignerData.chainId), + // cosmosChainId: explicitSignerData.chainId + // } + // this.signer.prefix = fromBech32(signerAddress).prefix + // const account = await this.signer.getAccounts() + + // const sender = { + // accountAddress: signerAddress, + // sequence: explicitSignerData.sequence, + // accountNumber: explicitSignerData.accountNumber, + // pubkey: toBase64(account[0].pubkey), + // } + // const fee = { + // amount: stdfee.amount[0].amount, + // denom: stdfee.amount[0].denom, + // gas: stdfee.gas, + // } + // const validatorAddresses = messages.map(x => x.value.validatorAddress) + + // let param: MsgMultipleWithdrawDelegatorRewardParams = { + // validatorAddresses + // } + + // const msgs = createTxMsgMultipleWithdrawDelegatorReward(chain, sender, fee, memo, param) + // const sig = await this.signer.sign712(msgs.eipToSign) + + // let extension = signatureToWeb3Extension(chain, sender, sig) + + // // Create the txRaw + // let prototx = createTxRawEIP712(msgs.legacyAmino.body, msgs.legacyAmino.authInfo, extension) + // return Promise.resolve(prototx.message.serializeBinary()) + // } + + async sign(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo: string, explicitSignerData?: SignerData): Promise { + + const chain: Chain = { + chainId: extractChainId(explicitSignerData.chainId), + cosmosChainId: explicitSignerData.chainId + } + + this.signer.prefix = fromBech32(signerAddress).prefix + const account = await this.signer.getAccounts() + + const sender = { + accountAddress: signerAddress, + sequence: explicitSignerData.sequence, + accountNumber: explicitSignerData.accountNumber, + pubkey: toBase64(account[0].pubkey), + } + + let fees = generateFee(fee.amount[0].amount, fee.amount[0].denom, fee.gas, signerAddress) + + const msgs = messages.map(x => this.aminoTypes.toAmino(x)) + const tx = generateMessageWithMultipleTransactions( + sender.accountNumber.toString(), + sender.sequence.toString(), + explicitSignerData.chainId, + memo, + fees, + msgs, + ) + + const types = generateTypes(defaultMessageAdapter[messages[0].typeUrl].getTypes()) + const eip = createEIP712(types, chain.chainId, tx ) + const sig = await this.signer.sign712(eip) + + const rawTx = makeRawTxEvmos(sender, messages, memo, fee, sig, chain) + + return Promise.resolve(rawTx) + } +} + +function makeRawTxEvmos(sender, messages, memo, fee, signature, chain): Uint8Array { + /// evmos style + ///* + const protoMsgs = messages.map(x => { + const adapter = defaultMessageAdapter[x.typeUrl] + return adapter.toProto(x) + }) + + const evmos = createTransactionWithMultipleMessages( + protoMsgs, + memo, + fee.amount[0].amount, + fee.amount[0].denom, + Number(fee.gas), + 'ethsecp256', + sender.pubkey, + sender.sequence, + sender.accountNumber, + chain.cosmosChainId + ) + + let extension = signatureToWeb3Extension(chain, sender, signature) + + // Create the txRaw + let prototx = createTxRawEIP712(evmos.legacyAmino.body, evmos.legacyAmino.authInfo, extension) + return prototx.message.serializeBinary() + /// end of EVMOS style */ +} + +function makeRawTx(sender, messages, memo, fee, signature, registry): TxRaw { + const pubkey = encodePubkey(sender.pubkey); + + const signedTxBody = { + messages, + memo, + }; + const signedTxBodyEncodeObject: TxBodyEncodeObject = { + typeUrl: "/cosmos.tx.v1beta1.TxBody", + value: signedTxBody, + }; + const signedTxBodyBytes = registry.encode(signedTxBodyEncodeObject); + const signedGasLimit = Int53.fromString(fee.gas).toNumber(); + const signedSequence = sender.sequence; + const signedAuthInfoBytes = makeAuthInfoBytes( + [{ pubkey, sequence: sender.sequence }], + fee.amount, + signedGasLimit, + SignMode.SIGN_MODE_LEGACY_AMINO_JSON, + ); + const rawTx = TxRaw.fromPartial({ + bodyBytes: signedTxBodyBytes, + authInfoBytes: signedAuthInfoBytes, + signatures: [fromHex(signature)], + }); + + console.log("rawTx", rawTx) + return rawTx +} + +export function encodePubkey(pubkey: string): Any { + const value = new eth.ethermint.crypto.v1.ethsecp256k1.PubKey({ key: fromBase64(pubkey) }) + return Any.fromPartial({ + typeUrl: "/ethermint.crypto.v1.ethsecp256k1.PubKey", + value: value.serializeBinary(), + }); +} + +// export function createAnyMessage(messages: readonly EncodeObject[]) { +// return messages.map(x => x.) +// } diff --git a/src/libs/fetch.js b/src/libs/fetch.js index 42fe0b20..d8d61184 100644 --- a/src/libs/fetch.js +++ b/src/libs/fetch.js @@ -491,7 +491,8 @@ export default class ChainFetch { // Tx Submit async broadcastTx(bodyBytes, config = null) { - const txString = toBase64(TxRaw.encode(bodyBytes).finish()) + const txbytes = bodyBytes.authInfoBytes ? TxRaw.encode(bodyBytes).finish() : bodyBytes + const txString = toBase64(txbytes) const txRaw = { tx_bytes: txString, mode: 'BROADCAST_MODE_SYNC', // BROADCAST_MODE_SYNC, BROADCAST_MODE_BLOCK, BROADCAST_MODE_ASYNC diff --git a/src/views/WalletAccountImportAddress.vue b/src/views/WalletAccountImportAddress.vue index e83fd30e..c30c94e6 100644 --- a/src/views/WalletAccountImportAddress.vue +++ b/src/views/WalletAccountImportAddress.vue @@ -73,6 +73,14 @@ > Ledger via Bluetooth + + Metamask + { + if (accounts) { + // eslint-disable-next-line prefer-destructuring + this.accounts = accounts[0] + ok = true + } + }).catch(e => { + this.debug = e + }) + break case 'ledger': case 'ledger2': await this.connect().then(accounts => { diff --git a/src/views/components/OperationModal/WalletInput.vue b/src/views/components/OperationModal/WalletInput.vue index bbaf301c..34609ccd 100644 --- a/src/views/components/OperationModal/WalletInput.vue +++ b/src/views/components/OperationModal/WalletInput.vue @@ -20,6 +20,13 @@ > Keplr + + Metamask +