forked from LaconicNetwork/cosmos-explorer
add importing evmos address from ledger
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/* 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<string, AminoConverter | "not_supported_by_chain">;
|
||||
|
||||
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<string, AminoConverter | "not_supported_by_chain">;
|
||||
|
||||
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.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,97 @@
|
||||
|
||||
import { AccountData, AminoSignResponse, encodeSecp256k1Signature, OfflineAminoSigner, StdSignature, StdSignDoc } from "@cosmjs/amino";
|
||||
import { AminoSignResponse, OfflineAminoSigner, Pubkey, StdSignature, StdSignDoc } from "@cosmjs/amino";
|
||||
import { AddressAndPubkey } from "@cosmjs/ledger-amino";
|
||||
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 { toHex } from "@cosmjs/encoding";
|
||||
import { fromBech32, 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 eth from "@tharsis/proto/dist/proto/ethermint/crypto/v1/ethsecp256k1/keys";
|
||||
|
||||
class EthereumLedgerSigner implements OfflineAminoSigner {
|
||||
export type Algo = "secp256k1" | "ed25519" | "sr25519" | "ethsecp256k1";
|
||||
export interface AccountData {
|
||||
/** A printable address (typically bech32 encoded) */
|
||||
readonly address: string;
|
||||
readonly algo: Algo;
|
||||
readonly pubkey: Uint8Array;
|
||||
}
|
||||
|
||||
export class EthereumLedgerSigner {
|
||||
app: Eth
|
||||
hdpath: string
|
||||
constructor(trasport: Transport, hdpath: string, scrambleKey?: string, loadConfig?: LoadConfig) {
|
||||
this.app = new Eth(trasport, scrambleKey, loadConfig)
|
||||
|
||||
static async create(protocol: string, hdpath: string, scrambleKey = "w0w", loadConfig?: LoadConfig): Promise<EthereumLedgerSigner> {
|
||||
let transport: Promise<Transport> = protocol === 'ledgerBle' ? TransportWebBLE.create() : TransportWebUSB.create()
|
||||
return transport.then(t => {
|
||||
let instance = new EthereumLedgerSigner()
|
||||
instance.hdpath = hdpath
|
||||
instance.app = new Eth(t, scrambleKey, loadConfig)
|
||||
return instance
|
||||
})
|
||||
}
|
||||
async getAccounts(): Promise<readonly AccountData[]> {
|
||||
return this.app.getAddress(this.hdpath).then(x => {
|
||||
const x2: AccountData = {
|
||||
console.log('eth:', this.app, this.hdpath)
|
||||
|
||||
return this.app.getAddress("44'/60'/0'/0/0").then(x => {
|
||||
const x1: AccountData = {
|
||||
pubkey: new TextEncoder().encode(x.publicKey),
|
||||
address: x.address,
|
||||
algo: "secp256k1"
|
||||
algo: "ethsecp256k1" // should be 'ethsecp256k1'
|
||||
}
|
||||
return [x2]
|
||||
const x2: AccountData = {
|
||||
pubkey: new TextEncoder().encode(x.publicKey),
|
||||
address: ethToCosmos(x.address),
|
||||
algo: "ethsecp256k1" // // should be 'ethsecp256k1'
|
||||
}
|
||||
return [x1, x2]
|
||||
})
|
||||
};
|
||||
|
||||
async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
|
||||
return this.getAccounts().then(list => {
|
||||
const acc = list.find(x => x.address === signerAddress)
|
||||
if (acc) {
|
||||
const messageHex: string = toHex(new TextEncoder().encode(JSON.stringify(signDoc)))
|
||||
this.app.signPersonalMessage(this.hdpath, messageHex).then(r => {
|
||||
const signature: StdSignature = encodeSecp256k1Signature(acc.pubkey, new TextEncoder().encode(r['s']))
|
||||
const output: AminoSignResponse = {
|
||||
signed: signDoc,
|
||||
signature
|
||||
}
|
||||
console.log(r)
|
||||
return output
|
||||
goEthAddress(address) {
|
||||
return `0x${toHex(fromBech32(address).data)}`
|
||||
}
|
||||
|
||||
toPubkey(keyBytes: Uint8Array): Pubkey {
|
||||
return {
|
||||
"type": "ethermint.crypto.v1.ethsecp256k1.PubKey",
|
||||
value: new eth.ethermint.crypto.v1.ethsecp256k1.PubKey({
|
||||
key: keyBytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
|
||||
const ethAddr = this.goEthAddress(signerAddress)
|
||||
return this.getAccounts().then(list => {
|
||||
const acc = list.find(x => x.address === ethAddr)
|
||||
if (acc) {
|
||||
const messageHex: string = toHex(new TextEncoder().encode(JSON.stringify(signDoc)))
|
||||
this.app.signPersonalMessage(this.hdpath, messageHex).then(result => {
|
||||
const signature: StdSignature = {
|
||||
pub_key: this.toPubkey(acc.pubkey),
|
||||
signature: result['s']
|
||||
}
|
||||
const output: AminoSignResponse = {
|
||||
signed: signDoc,
|
||||
signature
|
||||
}
|
||||
console.log(result, output)
|
||||
return output
|
||||
})
|
||||
}
|
||||
throw new Error('Account Does not exists!')
|
||||
})
|
||||
};
|
||||
|
||||
signTransaction(rawTxHex: string, resolution: LedgerEthTransactionResolution) {
|
||||
return this.app.signTransaction(this.hdpath, rawTxHex, resolution )
|
||||
async showAddress(path?: HdPath): Promise<AddressAndPubkey> {
|
||||
return new Promise((r, j) => { })
|
||||
}
|
||||
|
||||
async signTransaction(rawTxHex: string, resolution: LedgerEthTransactionResolution) {
|
||||
return this.app.signTransaction(this.hdpath, rawTxHex, resolution)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { AccountData, encodeSecp256k1Pubkey, makeSignDoc as makeSignDocAmino, StdFee } from "@cosmjs/amino"
|
||||
import { EncodeObject, encodePubkey, GeneratedType, isOfflineDirectSigner, makeAuthInfoBytes, OfflineSigner, Registry, TxBodyEncodeObject } from "@cosmjs/proto-signing";
|
||||
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx'
|
||||
import Eth from "@ledgerhq/hw-app-eth";
|
||||
import Transport from '@ledgerhq/hw-transport'
|
||||
import { Int53 } from "@cosmjs/math";
|
||||
import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
|
||||
import { fromBase64 } from "@cosmjs/encoding";
|
||||
|
||||
import { SigningStargateClient } from '@cosmjs/stargate'
|
||||
import { assert } from "@cosmjs/utils";
|
||||
import { AminoTypes } from "./aminotypes";
|
||||
|
||||
export interface SignerData {
|
||||
readonly accountNumber: number;
|
||||
readonly sequence: number;
|
||||
readonly chainId: string;
|
||||
}
|
||||
export interface ClientOptions {
|
||||
readonly registry?: Registry;
|
||||
readonly aminoTypes?: AminoTypes;
|
||||
readonly prefix?: string;
|
||||
}
|
||||
|
||||
export default class PingWalletClient extends SigningStargateClient {
|
||||
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { SigningStargateClient } from '@cosmjs/stargate'
|
||||
// import { MsgTransfer } from './msg-transfer'
|
||||
// import { createMessageSend } from '@tharsis/transactions'
|
||||
import { AminoTypes } from './aminotypes'
|
||||
import { MsgSwapExactAmountIn } from '../msg'
|
||||
|
||||
@@ -25,6 +26,30 @@ export default class PingWalletClient extends SigningStargateClient {
|
||||
return instance
|
||||
}
|
||||
|
||||
// async signEthermintTransaction(signerAddress, messages, fee, memo, { accountNumber, sequence, chainId }) {
|
||||
// // const msgs = messages.map(msg => this.aminoTypes.toAmino(msg))
|
||||
// const tosign = createMessageSend(chainId, signerAddress, fee, memo, null)
|
||||
// const { signature, signed } = await this.signer.signAmino(tosign.legacyAmino)
|
||||
// // const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc)
|
||||
// const signedTxBody = {
|
||||
// messages,
|
||||
// memo: signed.memo,
|
||||
// }
|
||||
// const signedTxBodyEncodeObject = {
|
||||
// typeUrl: '/cosmos.tx.v1beta1.TxBody',
|
||||
// value: signedTxBody,
|
||||
// }
|
||||
// const signedTxBodyBytes = this.registry.encode(signedTxBodyEncodeObject)
|
||||
// const signedGasLimit = math_1.Int53.fromString(signed.fee.gas).toNumber()
|
||||
// const signedSequence = math_1.Int53.fromString(signed.sequence).toNumber()
|
||||
// const signedAuthInfoBytes = proto_signing_1.makeAuthInfoBytes([{ pubkey: signature.pub_key, sequence: signedSequence }], signed.fee.amount, signedGasLimit, signMode)
|
||||
// return tx_5.TxRaw.fromPartial({
|
||||
// bodyBytes: signedTxBodyBytes,
|
||||
// authInfoBytes: signedAuthInfoBytes,
|
||||
// signatures: [encoding_1.fromBase64(signature.signature)],
|
||||
// })
|
||||
// }
|
||||
|
||||
async signAmino2(signerAddress, messages, fee, memo, { accountNumber, sequence, chainId }) {
|
||||
const accountFromSigner = (await this.signer.getAccounts()).find(account => account.address === signerAddress)
|
||||
if (!accountFromSigner) {
|
||||
|
||||
+33
-24
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
Bech32, fromBase64, fromBech32, fromHex, toHex,
|
||||
Bech32, fromBase64, fromBech32, fromHex, toBech32, toHex,
|
||||
} from '@cosmjs/encoding'
|
||||
import { sha256, stringToPath } from '@cosmjs/crypto'
|
||||
// ledger
|
||||
@@ -7,6 +7,7 @@ import TransportWebBLE from '@ledgerhq/hw-transport-web-ble'
|
||||
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
|
||||
import CosmosApp from 'ledger-cosmos-js'
|
||||
import { LedgerSigner } from '@cosmjs/ledger-amino'
|
||||
import { ethToEvmos } from '@tharsis/address-converter'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import duration from 'dayjs/plugin/duration'
|
||||
@@ -17,6 +18,7 @@ import localeData from 'dayjs/plugin/localeData'
|
||||
import { $themeColors } from '@themeConfig'
|
||||
// import { SigningStargateClient } from '@cosmjs/stargate'
|
||||
import PingWalletClient from './data/signing'
|
||||
import { EthereumLedgerSigner } from './client/EthereumLedgerSigner.ts'
|
||||
|
||||
dayjs.extend(localeData)
|
||||
dayjs.extend(duration)
|
||||
@@ -74,12 +76,19 @@ export function pubkeyToAccountAddress(pubkey, prefix) {
|
||||
return Bech32.encode(prefix, pubkey, 40)
|
||||
}
|
||||
|
||||
export function toETHAddress(cosmosAddress) {
|
||||
return `0x${toHex(fromBech32(cosmosAddress).data)}`
|
||||
}
|
||||
|
||||
export function addressDecode(address) {
|
||||
return Bech32.decode(address)
|
||||
if (address.startsWith('0x')) {
|
||||
return fromBech32(ethToEvmos(address))
|
||||
}
|
||||
return fromBech32(address)
|
||||
}
|
||||
|
||||
export function addressEnCode(prefix, pubkey) {
|
||||
return Bech32.encode(prefix, pubkey)
|
||||
return toBech32(prefix, pubkey)
|
||||
}
|
||||
|
||||
export function getUserCurrency() {
|
||||
@@ -175,10 +184,6 @@ export function consensusPubkeyToHexAddress(consensusPubkey) {
|
||||
return address
|
||||
}
|
||||
|
||||
export function toETHAddress(cosmosAddress) {
|
||||
return `0x${toHex(fromBech32(cosmosAddress).data)}`
|
||||
}
|
||||
|
||||
function toSignAddress(addr) {
|
||||
const { data } = addressDecode(addr)
|
||||
return addressEnCode('cosmos', data)
|
||||
@@ -192,39 +197,44 @@ function getHdPath(address) {
|
||||
hdPath = curr.hdpath
|
||||
}
|
||||
})
|
||||
// return [44, 118, 0, 0, 0]
|
||||
// m/0'/1/2'/2/1000000000
|
||||
return stringToPath(hdPath)
|
||||
}
|
||||
|
||||
function getLedgerAppName(coinType) {
|
||||
async function getLedgerAppName(coinType, device, hdpath) {
|
||||
let ledgerAppName = 'Cosmos'
|
||||
switch (coinType) {
|
||||
case 60:
|
||||
return 'Ethereum'
|
||||
return EthereumLedgerSigner.create(device, hdpath) // 'Ethereum'
|
||||
case 529:
|
||||
return 'Secret'
|
||||
ledgerAppName = 'Secret' // 'Secret'
|
||||
break
|
||||
case 852:
|
||||
return 'Desmos'
|
||||
ledgerAppName = 'Desmos' // 'Desmos'
|
||||
break
|
||||
case 118:
|
||||
default:
|
||||
return 'Cosmos'
|
||||
}
|
||||
const transport = await (device === 'ledgerBle' ? TransportWebBLE.create() : TransportWebUSB.create())
|
||||
return new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName })
|
||||
}
|
||||
|
||||
export async function sign(device, chainId, signerAddress, messages, fee, memo, signerData) {
|
||||
let transport
|
||||
// let transport
|
||||
let signer
|
||||
const hdpath = getHdPath(signerAddress)
|
||||
const coinType = Number(hdpath[1])
|
||||
const ledgerName = getLedgerAppName(coinType)
|
||||
// const ledgerName = getLedgerAppName(coinType)
|
||||
switch (device) {
|
||||
case 'ledgerBle':
|
||||
transport = await TransportWebBLE.create()
|
||||
signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
|
||||
// transport = await TransportWebBLE.create()
|
||||
// signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
|
||||
signer = await getLedgerAppName(coinType, device, hdpath)
|
||||
break
|
||||
case 'ledgerUSB':
|
||||
transport = await TransportWebUSB.create()
|
||||
signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
|
||||
// transport = await TransportWebUSB.create()
|
||||
// signer = new LedgerSigner(transport, { hdPaths: [hdpath], ledgerAppName: ledgerName })
|
||||
signer = await getLedgerAppName(coinType, device, hdpath)
|
||||
break
|
||||
case 'keplr':
|
||||
default:
|
||||
@@ -236,8 +246,6 @@ export async function sign(device, chainId, signerAddress, messages, fee, memo,
|
||||
signer = window.getOfflineSignerOnlyAmino(chainId)
|
||||
}
|
||||
|
||||
// if (signer) return signAmino(signer, signerAddress, messages, fee, memo, signerData)
|
||||
|
||||
// Ensure the address has some tokens to spend
|
||||
const client = await PingWalletClient.offline(signer)
|
||||
// const client = await SigningStargateClient.offline(signer)
|
||||
@@ -246,11 +254,12 @@ export async function sign(device, chainId, signerAddress, messages, fee, memo,
|
||||
}
|
||||
|
||||
export async function getLedgerAddress(transport = 'blu', hdPath = "m/44'/118/0'/0/0") {
|
||||
const trans = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create()
|
||||
const protocol = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create()
|
||||
// extract Cointype from from HDPath
|
||||
const coinType = Number(stringToPath(hdPath)[1])
|
||||
const ledgerName = getLedgerAppName(coinType)
|
||||
const signer = new LedgerSigner(trans, { hdPaths: [stringToPath(hdPath)], ledgerAppName: ledgerName })
|
||||
// const ledgerName = getLedgerAppName(coinType)
|
||||
// const signer = new LedgerSigner(trans, { hdPaths: [stringToPath(hdPath)], ledgerAppName: ledgerName })
|
||||
const signer = await getLedgerAppName(coinType, protocol, hdPath)
|
||||
return signer.getAccounts()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user