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 {
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user