diff --git a/.github/workflows/publish-beta.workflow.yml b/.github/workflows/publish-beta.workflow.yml index 4215720..4b4dc70 100644 --- a/.github/workflows/publish-beta.workflow.yml +++ b/.github/workflows/publish-beta.workflow.yml @@ -1,6 +1,7 @@ on: push: branches: [staging] +jobs: jobs: lint: runs-on: ubuntu-latest diff --git a/src/key/eth-wallet.ts b/src/key/eth-wallet.ts index 4140289..1bebb8c 100644 --- a/src/key/eth-wallet.ts +++ b/src/key/eth-wallet.ts @@ -11,6 +11,7 @@ import { encodeSecp256k1Signature } from '../utils/encode-signature'; import { HDNode } from '@ethersproject/hdnode'; import { bip39Token, getBip39 } from '../crypto/bip39/bip39-token'; import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; +import { TransactionRequest, Provider } from '@ethersproject/abstract-provider'; import Container from 'typedi'; export class EthWallet { @@ -19,6 +20,7 @@ export class EthWallet { private pvtKey: string, private walletType: 'mnemonic' | 'pvtKey', private options: WalletOptions, + private provider?: Provider, ) {} /** @@ -32,6 +34,10 @@ export class EthWallet { return new EthWallet(mnemonic, '', 'mnemonic', options); } + setProvider(provider: Provider) { + this.provider = provider; + } + /** * Generates a wallet from a private key. * @param {string} pvtKey - The private key to generate the wallet from. @@ -67,7 +73,7 @@ export class EthWallet { const ethAddr = EthereumUtilsAddress.fromString(hdWallet.address).toBuffer(); const bech32Address = bech32.encode(this.options.addressPrefix, bech32.toWords(ethAddr)); - const ethWallet = new Wallet(hdWallet.privateKey); + const ethWallet = new Wallet(hdWallet.privateKey, this.provider); return { algo: 'ethsecp256k1', address: bech32Address, @@ -87,6 +93,17 @@ export class EthWallet { return ethWallet._signingKey().signDigest(signBytes); } + public async sendTransaction(transaction: TransactionRequest) { + const accounts = this.getAccountsWithPrivKey(); + const account = accounts[0]; + if (!account) throw new Error('Account not found'); + // if (account === undefined) { + // throw new Error(`Address ${signerAddress} not found in wallet`); + // } + const { ethWallet } = account; + return await ethWallet.sendTransaction(transaction); + } + signMessage(signerAddress: string, message: Uint8Array) { const accounts = this.getAccountsWithPrivKey(); const account = accounts.find(({ address }) => address === signerAddress); @@ -104,6 +121,7 @@ export class EthWallet { throw new Error(`Address ${signerAddress} not found in wallet`); } const { ethWallet } = account; + return ethWallet.signTransaction(transaction); } @@ -117,6 +135,7 @@ export class EthWallet { const rawSignature = this.sign(signerAddress, keccak256(Buffer.from(hash))); const splitSignature = bytes.splitSignature(rawSignature); const signature = bytes.arrayify(bytes.concat([splitSignature.r, splitSignature.s])); + return { signed: signDoc, signature: encodeSecp256k1Signature(account.pubkey, signature), diff --git a/src/key/wallet-utils.ts b/src/key/wallet-utils.ts index 5262964..6bd8375 100644 --- a/src/key/wallet-utils.ts +++ b/src/key/wallet-utils.ts @@ -5,12 +5,17 @@ import { Wallet } from './wallet'; import * as base64js from 'base64-js'; import { bip39Token } from '../crypto/bip39/bip39-token'; -export function generateWalletFromMnemonic(mnemonic: string, hdPath: string, addressPrefix: string) { +export function generateWalletFromMnemonic( + mnemonic: string, + hdPath: string, + addressPrefix: string, + ethWallet?: boolean, +) { const bip39 = Container.get(bip39Token); bip39.mnemonicToEntropy(mnemonic); const hdPathParams = hdPath.split('/'); const coinType = hdPathParams[2]; - if (coinType?.replace("'", '') === '60') { + if (coinType?.replace("'", '') === '60' || ethWallet) { return EthWallet.generateWalletFromMnemonic(mnemonic, { paths: [hdPath], addressPrefix }); } return Wallet.generateWallet(mnemonic, { paths: [hdPath], addressPrefix }); diff --git a/src/keychain/keychain.ts b/src/keychain/keychain.ts index c9e72f0..fbb17ef 100644 --- a/src/keychain/keychain.ts +++ b/src/keychain/keychain.ts @@ -208,6 +208,7 @@ export class KeyChain { password: string, addressPrefix: string, coinType: string, + ethWallet?: boolean, ) { const storage = Container.get(storageToken); const keychain = (await storage.get(KEYCHAIN)) as unknown as Keystore; @@ -222,14 +223,14 @@ export class KeyChain { throw new Error('Wallet type not supported'); } if (walletData.walletType === WALLETTYPE.PRIVATE_KEY) { - if (coinType === '60') { + if (coinType === '60' || ethWallet) { const hdPath = getHDPath(coinType, walletData.addressIndex.toString()); return EthWallet.generateWalletFromPvtKey(secret, { paths: [hdPath], addressPrefix }); } return PvtKeyWallet.generateWallet(secret, addressPrefix); } else { const hdPath = getHDPath(coinType, walletData.addressIndex.toString()); - return generateWalletFromMnemonic(secret, hdPath, addressPrefix); + return generateWalletFromMnemonic(secret, hdPath, addressPrefix, ethWallet); } } diff --git a/tests/wallet.test.ts b/tests/wallet.test.ts index 3fb70c8..8b41091 100644 --- a/tests/wallet.test.ts +++ b/tests/wallet.test.ts @@ -76,12 +76,23 @@ describe('generateMnemonic', () => { const accounts = wallet.getAccounts(); expect(accounts[0]?.address).toBe(referenceWallets.ref2.addresses.cosmos); }); + test('generateWalletFromMnemonic for cointype=60', () => { + const wallet = generateWalletFromMnemonic(mnemonic, "m/44'/60'/0'/0/1", 'evmos'); + const accounts = wallet.getAccounts(); + expect(accounts[0]?.address).toBe(referenceWallets.ref2.addresses.evmos); + }); test('generateWalletsFromMnemonic', async () => { const wallet = generateWalletsFromMnemonic(mnemonic, ["m/44'/118'/0'/0/0", "m/44'/118'/0'/0/1"], 'cosmos'); const accounts = wallet.getAccounts(); expect(accounts[0]?.address).toBe(referenceWallets.ref1.addresses.cosmos); expect(accounts[1]?.address).toBe(referenceWallets.ref2.addresses.cosmos); }); + test('generateWalletsFromMnemonic for cointype=60', async () => { + const wallet = generateWalletsFromMnemonic(mnemonic, ["m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1"], 'evmos'); + const accounts = wallet.getAccounts(); + expect(accounts[0]?.address).toBe(referenceWallets.ref1.addresses.evmos); + expect(accounts[1]?.address).toBe(referenceWallets.ref2.addresses.evmos); + }); test('generateWalletFromMnemonic throws error if mnemonic is invalid', () => { expect(() => generateWalletFromMnemonic('', "m/44'/118'/0'/0/0", 'cosmos')).toThrow('Invalid mnemonic'); });