From 6066b8dc947aa76dabc1906f39fb54619110eac6 Mon Sep 17 00:00:00 2001 From: Thong Yuan Yu Sarah Date: Wed, 16 Oct 2024 12:35:53 +0800 Subject: [PATCH 01/18] Add CarbonEVMLedger class to support ledger evm signing (WIP) --- examples/query.ts | 16 +- .../ledger/CarbonEVMLedger/CarbonEVMLedger.ts | 243 ++++++++++++++++ src/provider/ledger/CarbonEVMLedger/index.ts | 1 + src/provider/ledger/CarbonEVMLedger/utils.ts | 273 ++++++++++++++++++ src/provider/ledger/ledger.ts | 14 + 5 files changed, 542 insertions(+), 5 deletions(-) create mode 100644 src/provider/ledger/CarbonEVMLedger/CarbonEVMLedger.ts create mode 100644 src/provider/ledger/CarbonEVMLedger/index.ts create mode 100644 src/provider/ledger/CarbonEVMLedger/utils.ts diff --git a/examples/query.ts b/examples/query.ts index 9b07aeeb..c10c52ea 100644 --- a/examples/query.ts +++ b/examples/query.ts @@ -5,9 +5,15 @@ import { CarbonSDK, CarbonTx, GenericUtils } from "./_sdk"; import "./_setup"; (async () => { - const sdk = await CarbonSDK.instance(); + const sdk = await CarbonSDK.instance({ + network: CarbonSDK.Network.MainNet, + }); // GRPC Queries + const txDetails = await sdk.query.chain.getTx("DD13F908B5A9C3495B23049B32D20EB7A1631F606DECF76EAAD79444250565CE"); + const decodedTx = CarbonTx.decode(txDetails?.tx); + console.log("tx decoded", decodedTx?.authInfo?.fee); + process.exit(0); // query market stats const marketStats = await sdk.query.marketstats.MarketStats({ @@ -71,7 +77,7 @@ import "./_setup"; // query all profiles with pagination const profiles = await sdk.query.profile.ProfileAll({ - username: "", + // username: "", pagination: PageRequest.fromPartial({ limit: new Long(5), }), @@ -140,9 +146,9 @@ import "./_setup"; console.log("tx events", JSON.parse(tx.rawLog)); // decode tx - const decodedTx = CarbonTx.decode(tx.tx); - console.log("tx decoded", JSON.stringify(decodedTx)); - console.log("tx msgs", decodedTx?.body?.messages); + // const decodedTx = CarbonTx.decode(tx.tx); + // console.log("tx decoded", JSON.stringify(decodedTx)); + // console.log("tx msgs", decodedTx?.body?.messages); })() .catch(console.error) .finally(() => process.exit(0)); diff --git a/src/provider/ledger/CarbonEVMLedger/CarbonEVMLedger.ts b/src/provider/ledger/CarbonEVMLedger/CarbonEVMLedger.ts new file mode 100644 index 00000000..d224d237 --- /dev/null +++ b/src/provider/ledger/CarbonEVMLedger/CarbonEVMLedger.ts @@ -0,0 +1,243 @@ +import type Transport from "@ledgerhq/hw-transport"; +import { log } from "@ledgerhq/logs"; + +import { ethers } from "ethers"; + +import { decodeTxInfo, splitPath } from "./utils"; + +const defaultPath: string = "m/44'/118'/0'/0/0"; + +function waiter(duration: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, duration); + }); +} + +class CarbonEVMLedger { + transport: Transport; + private path: string; + + constructor(transport: Transport, path: string = defaultPath, scrambleKey = "w0w") { + this.path = path; + this.transport = transport; + transport.decorateAppAPIMethods( + this, + [ + // "getChallange", | ⚠️ + // "provideERC20TokenInformation", | Those methods are not decorated as they're + // "setExternalPlugin", | being used inside of the `signTransaction` flow + // "setPlugin", | and shouldn't be locking the transport + // "provideDomainName", | ⚠️ + // "provideNFTInformation", | + "getAddress", + "signTransaction", + "signEIP712Tx", + ], + scrambleKey, + ); + } + + _retry(callback: () => Promise, timeout?: number): Promise { + return new Promise(async (resolve, reject) => { + if (timeout && timeout > 0) { + setTimeout(() => { reject(new Error("timeout")); }, timeout); + } + + // Wait up to 5 seconds + for (let i = 0; i < 50; i++) { + try { + const result = await callback(); + return resolve(result); + } catch (error) { + if (error.id !== "TransportLocked") { + return reject(error); + } + } + await waiter(100); + } + + return reject(new Error("timeout")); + }); + } + + async signEIP712Tx(transaction: ethers.providers.TransactionRequest): Promise { + const tx = await ethers.utils.resolveProperties(transaction); + const baseTx: ethers.utils.UnsignedTransaction = { + chainId: (tx.chainId || undefined), + data: (tx.data || undefined), + gasLimit: (tx.gasLimit || undefined), + gasPrice: (tx.gasPrice || undefined), + nonce: (tx.nonce ? ethers.BigNumber.from(tx.nonce).toNumber(): undefined), + to: (tx.to || undefined), + value: (tx.value || undefined), + }; + + const unsignedTx = ethers.utils.serializeTransaction(baseTx).substring(2); + const sig = await this._retry(() => this.signTransaction(this.path, unsignedTx)); + + return ethers.utils.serializeTransaction(baseTx, { + v: ethers.BigNumber.from("0x" + sig.v).toNumber(), + r: ("0x" + sig.r), + s: ("0x" + sig.s), + }); + } + + /** + * You can sign a transaction and retrieve v, r, s given the raw transaction and the BIP 32 path of the account to sign. + * + * @param path: the BIP32 path to sign the transaction on + * @param rawTxHex: the raw ethereum transaction in hexadecimal to sign + * @param resolution: resolution is an object with all "resolved" metadata necessary to allow the device to clear sign information. This includes: ERC20 token information, plugins, contracts, NFT signatures,... You must explicitly provide something to avoid having a warning. By default, you can use Ledger's service or your own resolution service. See services/types.js for the contract. Setting the value to "null" will fallback everything to blind signing but will still allow the device to sign the transaction. + * @example + import { ledgerService } from "@ledgerhq/hw-app-eth" + const tx = "e8018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a2487400080"; // raw tx to sign + const resolution = await ledgerService.resolveTransaction(tx); + const result = eth.signTransaction("44'/60'/0'/0/0", tx, resolution); + console.log(result); + */ + async signTransaction( + path: string, + rawTxHex: string, + ): Promise<{ + s: string; + v: string; + r: string; + }> { + + const rawTx = Buffer.from(rawTxHex, "hex"); + const { vrsOffset, txType, chainId, chainIdTruncated } = decodeTxInfo(rawTx); + + const paths = splitPath(path); + let response; + let offset = 0; + while (offset !== rawTx.length) { + const first = offset === 0; + const maxChunkSize = first ? 150 - 1 - paths.length * 4 : 150; + let chunkSize = offset + maxChunkSize > rawTx.length ? rawTx.length - offset : maxChunkSize; + + if (vrsOffset != 0 && offset + chunkSize >= vrsOffset) { + // Make sure that the chunk doesn't end right on the EIP 155 marker if set + chunkSize = rawTx.length - offset; + } + + const buffer = Buffer.alloc(first ? 1 + paths.length * 4 + chunkSize : chunkSize); + + if (first) { + buffer[0] = paths.length; + paths.forEach((element, index) => { + buffer.writeUInt32BE(element, 1 + 4 * index); + }); + rawTx.copy(buffer, 1 + 4 * paths.length, offset, offset + chunkSize); + } else { + rawTx.copy(buffer, 0, offset, offset + chunkSize); + } + + response = await this.transport + .send(0xe0, 0x04, first ? 0x00 : 0x80, 0x00, buffer) + .catch(e => { + throw (e as Error).message; + }); + + offset += chunkSize; + } + if (!response) { + throw new Error('Something went wrong'); + } + + const response_byte: number = response[0]; + let v = ""; + + if (chainId.times(2).plus(35).plus(1).isGreaterThan(255)) { + const oneByteChainId = (chainIdTruncated * 2 + 35) % 256; + + const ecc_parity = Math.abs(response_byte - oneByteChainId); + + if (txType != null) { + // For EIP2930 and EIP1559 tx, v is simply the parity. + v = ecc_parity % 2 == 1 ? "00" : "01"; + } else { + // Legacy type transaction with a big chain ID + v = chainId.times(2).plus(35).plus(ecc_parity).toString(16); + } + } else { + v = response_byte.toString(16); + } + + // Make sure v has is prefixed with a 0 if its length is odd ("1" -> "01"). + if (v.length % 2 == 1) { + v = "0" + v; + } + + const r = response.slice(1, 1 + 32).toString("hex"); + const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex"); + return { v, r, s }; + } + + /** + * Method returning a 4 bytes TLV challenge as an hexa string + * + * @returns {Promise} + */ + async getChallenge(): Promise { + enum APDU_FIELDS { + CLA = 0xe0, + INS = 0x20, + P1 = 0x00, + P2 = 0x00, + LC = 0x00, + } + return this.transport + .send(APDU_FIELDS.CLA, APDU_FIELDS.INS, APDU_FIELDS.P1, APDU_FIELDS.P2) + .then(res => { + const [, fourBytesChallenge, statusCode] = + new RegExp("(.*)(.{4}$)").exec(res.toString("hex")) || []; + + if (statusCode !== "9000") { + throw new Error( + `An error happened while generating the challenge. Status code: ${statusCode}`, + ); + } + return `0x${fourBytesChallenge}`; + }) + .catch(e => { + log("error", "couldn't request a challenge", e); + throw e; + }); + } + + /** + * provides a domain name (like ENS) to be displayed during transactions in place of the address it is associated to. It shall be run just before a transaction involving the associated address that would be displayed on the device. + * + * @param data an stringied buffer of some TLV encoded data to represent the domain + * @returns a boolean + */ + async provideDomainName(data: string): Promise { + enum APDU_FIELDS { + CLA = 0xe0, + INS = 0x22, + P1_FIRST_CHUNK = 0x01, + P1_FOLLOWING_CHUNK = 0x00, + P2 = 0x00, + } + const buffer = Buffer.from(data, "hex"); + const payload = Buffer.concat([Buffer.from(intAsHexBytes(buffer.length, 2), "hex"), buffer]); + + const bufferChunks = new Array(Math.ceil(payload.length / 256)) + .fill(null) + .map((_, i) => payload.slice(i * 255, (i + 1) * 255)); + for (const chunk of bufferChunks) { + const isFirstChunk = chunk === bufferChunks[0]; + await this.transport.send( + APDU_FIELDS.CLA, + APDU_FIELDS.INS, + isFirstChunk ? APDU_FIELDS.P1_FIRST_CHUNK : APDU_FIELDS.P1_FOLLOWING_CHUNK, + APDU_FIELDS.P2, + chunk, + ); + } + + return true; + } +} + +export default CarbonEVMLedger diff --git a/src/provider/ledger/CarbonEVMLedger/index.ts b/src/provider/ledger/CarbonEVMLedger/index.ts new file mode 100644 index 00000000..9ef29ae7 --- /dev/null +++ b/src/provider/ledger/CarbonEVMLedger/index.ts @@ -0,0 +1 @@ +export { default } from './CarbonEVMLedger' diff --git a/src/provider/ledger/CarbonEVMLedger/utils.ts b/src/provider/ledger/CarbonEVMLedger/utils.ts new file mode 100644 index 00000000..e18f3879 --- /dev/null +++ b/src/provider/ledger/CarbonEVMLedger/utils.ts @@ -0,0 +1,273 @@ +import { FetchUtils } from "@carbon-sdk/util"; +import { decode, encode } from "@ethersproject/rlp"; +import { log } from "@ledgerhq/logs"; +import BigNumber from "bignumber.js"; + +export type SupportedRegistries = "ens"; + +export type DomainDescriptor = { + registry: SupportedRegistries; + domain: string; + address: string; + type: "forward" | "reverse"; +}; + +export type LedgerEthTransactionResolution = { + // device serialized data that contains ERC20 data (hex format) + erc20Tokens: Array; + // device serialized data that contains external plugin data (hex format) + externalPlugin: Array<{ payload: string; signature: string }>; + // device serialized data that contains plugin data (hex format) + plugin: Array; + // device serialized data that contain trusted names data (hex format) + domains: DomainDescriptor[]; +}; + +declare enum CoinType { + ATOM = 118 +} + +export type Registry = { + name: SupportedRegistries; + resolvers: { + forward: string; + reverse: string; + }; + signatures: { + forward: string; + reverse: string; + }; + patterns: { + forward: RegExp; + reverse: RegExp; + }; + coinTypes: CoinType[]; +}; + +const REGISTRIES: Registry[] = [ + { + name: "ens", + resolvers: { + forward: "https://explorers.api.live.ledger.com/blockchain/v4/eth/ens/resolve/{name}", + reverse: + "https://explorers.api.live.ledger.com/blockchain/v4/eth/ens/reverse-resolve/{address}", + }, + signatures: { + forward: "https://nft.api.live.ledger.com/v1/names/ens/forward/{name}?challenge={challenge}", + reverse: + "https://nft.api.live.ledger.com/v1/names/ens/reverse/{address}?challenge={challenge}", + }, + patterns: { + forward: new RegExp("\\.eth$"), + reverse: new RegExp("^0x[0-9a-fA-F]{40}$"), + }, + coinTypes: [CoinType.ATOM], + }, +]; + +/** + * Method is voluntarly made async so it can be replaced by a backend call once implemented + */ +export const getRegistries = async (): Promise => REGISTRIES; + +/** + * Get an APDU to sign a domain resolution on the nano + * + * @param {string} domain + * @param {SupportedRegistries} registryName + * @param {string} challenge + * @returns {Promise} + */ +export const signDomainResolution = async ( + domain: string, + registryName: SupportedRegistries, + challenge: string, +): Promise => { + if (!validateDomain(domain)) { + throw new Error( + `Domains with more than 255 caracters or with unicode are not supported on the nano. Domain: ${domain}`, + ); + } + const registries = await getRegistries(); + const registry = registries.find(r => r.name === registryName); + if (!registry) return null; + + const url = registry.signatures.forward + .replace("{name}", domain) + .replace("{challenge}", challenge); + + return FetchUtils.fetch(url) + .then(({ data }) => data.payload) + .catch(error => { + /* istanbul ignore next: don't test logs */ + if (error.status !== 404) { + log("domain-service", "failed to get APDU for a domain", { + domain, + error, + }); + } + return null; + }); +}; + +/** + * Get an APDU to sign an address resolve resolution on the nano + * + * @param {string} address + * @param {SupportedRegistries} registryName + * @param {string} challenge + * @returns {Promise} + */ +export const signAddressResolution = async ( + address: string, + registryName: SupportedRegistries, + challenge: string, +): Promise => { + const registries = await getRegistries(); + const registry = registries.find(r => r.name === registryName); + if (!registry) return null; + + const url = registry.signatures.reverse + .replace("{address}", address) + .replace("{challenge}", challenge); + + return FetchUtils.fetch(url) + .then(({ data }) => data.payload) + .catch(error => { + /* istanbul ignore next: don't test logs */ + if (error.status !== 404) { + log("domain-service", "failed to get APDU for an address", { + address, + error, + }); + } + return null; + }); +}; + +/** + * Helper to know in advance if a domain is compatible with the nano + * + * @param domain string representing the domain + * @returns {Boolean} + */ +const validateDomain = (domain: string | undefined): boolean => { + if (typeof domain !== "string") { + return false; + } + + const lengthIsValid = domain.length > 0 && Number(domain.length) < 30; + const containsOnlyValidChars = new RegExp("^[a-zA-Z0-9\\-\\_\\.]+$").test(domain); + + return lengthIsValid && containsOnlyValidChars; +}; + +export const decodeTxInfo = (rawTx: Buffer) => { + const VALID_TYPES = [1, 2]; + const txType = VALID_TYPES.includes(rawTx[0]) ? rawTx[0] : null; + const rlpData = txType === null ? rawTx : rawTx.slice(1); + const rlpTx = decode(rlpData).map(hex => Buffer.from(hex.slice(2), "hex")); + let chainIdTruncated = 0; + const rlpDecoded = decode(rlpData); + + let decodedTx; + if (txType === 2) { + // EIP1559 + decodedTx = { + data: rlpDecoded[7], + to: rlpDecoded[5], + chainId: rlpTx[0], + }; + } else if (txType === 1) { + // EIP2930 + decodedTx = { + data: rlpDecoded[6], + to: rlpDecoded[4], + chainId: rlpTx[0], + }; + } else { + // Legacy tx + decodedTx = { + data: rlpDecoded[5], + to: rlpDecoded[3], + // Default to 1 for non EIP 155 txs + chainId: rlpTx.length > 6 ? rlpTx[6] : Buffer.from("01", "hex"), + }; + } + + const chainIdSrc = decodedTx.chainId; + let chainId = new BigNumber(0); + if (chainIdSrc) { + // Using BigNumber because chainID could be any uint256. + chainId = new BigNumber(chainIdSrc.toString("hex"), 16); + const chainIdTruncatedBuf = Buffer.alloc(4); + if (chainIdSrc.length > 4) { + chainIdSrc.copy(chainIdTruncatedBuf); + } else { + chainIdSrc.copy(chainIdTruncatedBuf, 4 - chainIdSrc.length); + } + chainIdTruncated = chainIdTruncatedBuf.readUInt32BE(0); + } + + let vrsOffset = 0; + if (txType === null && rlpTx.length > 6) { + const rlpVrs = Buffer.from(encode(rlpTx.slice(-3)).slice(2), "hex"); + + vrsOffset = rawTx.length - (rlpVrs.length - 1); + + // First byte > 0xf7 means the length of the list length doesn't fit in a single byte. + if (rlpVrs[0] > 0xf7) { + // Increment vrsOffset to account for that extra byte. + vrsOffset++; + + // Compute size of the list length. + const sizeOfListLen = rlpVrs[0] - 0xf7; + + // Increase rlpOffset by the size of the list length. + vrsOffset += sizeOfListLen - 1; + } + } + + return { + decodedTx, + txType, + chainId, + chainIdTruncated, + vrsOffset, + }; +}; + +export const mergeResolutions = ( + resolutionsArray: Partial[], +): LedgerEthTransactionResolution => { + const mergedResolutions: LedgerEthTransactionResolution = { + erc20Tokens: [], + externalPlugin: [], + plugin: [], + domains: [], + }; + + for (const resolutions of resolutionsArray) { + for (const key in resolutions) { + mergedResolutions[key].push(...resolutions[key]); + } + } + + return mergedResolutions; +}; + +export function splitPath(path: string): number[] { + const result: number[] = []; + const components = path.split("/"); + components.forEach(element => { + let number = parseInt(element, 10); + if (isNaN(number)) { + return; // FIXME shouldn't it throws instead? + } + if (element.length > 1 && element[element.length - 1] === "'") { + number += 0x80000000; + } + result.push(number); + }); + return result; +} diff --git a/src/provider/ledger/ledger.ts b/src/provider/ledger/ledger.ts index b60918bb..4abfa758 100644 --- a/src/provider/ledger/ledger.ts +++ b/src/provider/ledger/ledger.ts @@ -12,6 +12,8 @@ import { signatureImport } from "secp256k1"; import { signTransactionWrapper } from "@carbon-sdk/util/provider"; const semver = require("semver"); +import CarbonEVMLedger from "./CarbonEVMLedger"; + const INTERACTION_TIMEOUT = 120; // seconds to wait for user action on Ledger, currently is always limited to 60 const REQUIRED_COSMOS_APP_VERSION = "1.5.3"; @@ -25,6 +27,7 @@ const BECH32PREFIX = `cosmos`; class CosmosLedger { private readonly testModeAllowed: Boolean; private cosmosApp: any; + private carbonEVMApp: any; private hdPath: Array; private hrp: string; public platform: string; @@ -129,6 +132,8 @@ class CosmosLedger { const cosmosLedgerApp = new CosmosLedgerApp(transport); this.cosmosApp = cosmosLedgerApp; + const carbonEVMLedgerApp = new CarbonEVMLedger(transport); + this.carbonEVMApp = carbonEVMLedgerApp; // checks if the Ledger is connected and the app is open await this.isReady(); @@ -138,6 +143,7 @@ class CosmosLedger { async disconnect() { await this.cosmosApp.transport.close() + await this.carbonEVMApp.transport.close() } async getDeviceName() { @@ -252,6 +258,14 @@ class CosmosLedger { return parsedSignature; } + async signEvmMessage(tx: ethers.providers.TransactionRequest) { + await this.connect(); + const response = await signTransactionWrapper(async () => { + return await this.carbonEVMApp.signEIP712Tx(tx); + }); + return response; + } + // parse Ledger errors in a more user friendly format /* istanbul ignore next: maps a bunch of errors */ private async checkLedgerErrors( From aedf21367bf81b7dcbdf9d0ef3e8b1069143c722 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 12:11:25 +0800 Subject: [PATCH 02/18] direct ledger evm tx signing --- src/CarbonSDK.ts | 12 +- .../ledger/CarbonEVMLedger/CarbonEVMLedger.ts | 243 ---------------- src/provider/ledger/CarbonEVMLedger/index.ts | 1 - src/provider/ledger/CarbonEVMLedger/utils.ts | 273 ------------------ src/provider/ledger/evm.ts | 27 ++ src/provider/ledger/index.ts | 1 + src/provider/ledger/ledger.ts | 23 +- src/wallet/CarbonSigner.ts | 20 +- src/wallet/CarbonWallet.ts | 16 +- 9 files changed, 73 insertions(+), 543 deletions(-) delete mode 100644 src/provider/ledger/CarbonEVMLedger/CarbonEVMLedger.ts delete mode 100644 src/provider/ledger/CarbonEVMLedger/index.ts delete mode 100644 src/provider/ledger/CarbonEVMLedger/utils.ts create mode 100644 src/provider/ledger/evm.ts diff --git a/src/CarbonSDK.ts b/src/CarbonSDK.ts index b2f2974d..6c0808fd 100644 --- a/src/CarbonSDK.ts +++ b/src/CarbonSDK.ts @@ -14,7 +14,7 @@ import { Tendermint37Client } from "@cosmjs/tendermint-rpc"; import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport"; import BigNumber from "bignumber.js"; import * as clients from "./clients"; -import { CarbonQueryClient, AxelarBridgeClient, ETHClient, HydrogenClient, InsightsQueryClient, NEOClient, TokenClient, ZILClient } from "./clients"; +import { AxelarBridgeClient, CarbonQueryClient, ETHClient, HydrogenClient, InsightsQueryClient, NEOClient, TokenClient, ZILClient } from "./clients"; import GasFee from "./clients/GasFee"; import GrpcQueryClient from "./clients/GrpcQueryClient"; import N3Client from "./clients/N3Client"; @@ -35,6 +35,7 @@ import { LeverageModule, LiquidityPoolModule, MarketModule, + OTCModule, OracleModule, OrderModule, PerpspoolModule, @@ -42,21 +43,20 @@ import { ProfileModule, SubAccountModule, XChainModule, - OTCModule, } from "./modules"; import { GrantModule } from "./modules/grant"; import { StakingModule } from "./modules/staking"; import { CosmosLedger, Keplr, KeplrAccount, LeapAccount, LeapExtended } from "./provider"; import { MetaMask } from "./provider/metamask/MetaMask"; +import RainbowKitAccount, { RainbowKitWalletOpts } from "./provider/rainbowKit/RainbowKitAccount"; import { SWTHAddressOptions } from "./util/address"; import { Blockchain } from "./util/blockchain"; import { bnOrZero } from "./util/number"; import { SimpleMap } from "./util/type"; -import { CarbonLedgerSigner, CarbonSigner, CarbonSignerTypes, CarbonWallet, CarbonWalletGenericOpts, MetaMaskWalletOpts } from "./wallet"; +import { CarbonLedgerSigner, CarbonLedgerWalletGenericOpts, CarbonSigner, CarbonSignerTypes, CarbonWallet, CarbonWalletGenericOpts, MetaMaskWalletOpts } from "./wallet"; export { CarbonTx } from "@carbon-sdk/util"; export { CarbonSigner, CarbonSignerTypes, CarbonWallet, CarbonWalletGenericOpts, CarbonWalletInitOpts } from "@carbon-sdk/wallet"; export * as Carbon from "./codec/carbon-models"; -import RainbowKitAccount, { RainbowKitWalletOpts } from "./provider/rainbowKit/RainbowKitAccount"; export interface CarbonSDKOpts { network: Network; @@ -359,7 +359,7 @@ class CarbonSDK { public static async instanceWithLedger( ledger: CosmosLedger, sdkOpts: CarbonSDKInitOpts = DEFAULT_SDK_INIT_OPTS, - walletOpts?: CarbonWalletGenericOpts + walletOpts?: CarbonLedgerWalletGenericOpts ) { const sdk = await CarbonSDK.instance(sdkOpts); return sdk.connectWithLedger(ledger, walletOpts); @@ -531,7 +531,7 @@ class CarbonSDK { return this.connect(wallet); } - public async connectWithLedger(ledger: CosmosLedger, opts?: CarbonWalletGenericOpts) { + public async connectWithLedger(ledger: CosmosLedger, opts?: CarbonLedgerWalletGenericOpts) { const publicKeyBuffer = await ledger.getPubKey(); const publicKeyBase64 = publicKeyBuffer.toString("base64"); diff --git a/src/provider/ledger/CarbonEVMLedger/CarbonEVMLedger.ts b/src/provider/ledger/CarbonEVMLedger/CarbonEVMLedger.ts deleted file mode 100644 index d224d237..00000000 --- a/src/provider/ledger/CarbonEVMLedger/CarbonEVMLedger.ts +++ /dev/null @@ -1,243 +0,0 @@ -import type Transport from "@ledgerhq/hw-transport"; -import { log } from "@ledgerhq/logs"; - -import { ethers } from "ethers"; - -import { decodeTxInfo, splitPath } from "./utils"; - -const defaultPath: string = "m/44'/118'/0'/0/0"; - -function waiter(duration: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, duration); - }); -} - -class CarbonEVMLedger { - transport: Transport; - private path: string; - - constructor(transport: Transport, path: string = defaultPath, scrambleKey = "w0w") { - this.path = path; - this.transport = transport; - transport.decorateAppAPIMethods( - this, - [ - // "getChallange", | ⚠️ - // "provideERC20TokenInformation", | Those methods are not decorated as they're - // "setExternalPlugin", | being used inside of the `signTransaction` flow - // "setPlugin", | and shouldn't be locking the transport - // "provideDomainName", | ⚠️ - // "provideNFTInformation", | - "getAddress", - "signTransaction", - "signEIP712Tx", - ], - scrambleKey, - ); - } - - _retry(callback: () => Promise, timeout?: number): Promise { - return new Promise(async (resolve, reject) => { - if (timeout && timeout > 0) { - setTimeout(() => { reject(new Error("timeout")); }, timeout); - } - - // Wait up to 5 seconds - for (let i = 0; i < 50; i++) { - try { - const result = await callback(); - return resolve(result); - } catch (error) { - if (error.id !== "TransportLocked") { - return reject(error); - } - } - await waiter(100); - } - - return reject(new Error("timeout")); - }); - } - - async signEIP712Tx(transaction: ethers.providers.TransactionRequest): Promise { - const tx = await ethers.utils.resolveProperties(transaction); - const baseTx: ethers.utils.UnsignedTransaction = { - chainId: (tx.chainId || undefined), - data: (tx.data || undefined), - gasLimit: (tx.gasLimit || undefined), - gasPrice: (tx.gasPrice || undefined), - nonce: (tx.nonce ? ethers.BigNumber.from(tx.nonce).toNumber(): undefined), - to: (tx.to || undefined), - value: (tx.value || undefined), - }; - - const unsignedTx = ethers.utils.serializeTransaction(baseTx).substring(2); - const sig = await this._retry(() => this.signTransaction(this.path, unsignedTx)); - - return ethers.utils.serializeTransaction(baseTx, { - v: ethers.BigNumber.from("0x" + sig.v).toNumber(), - r: ("0x" + sig.r), - s: ("0x" + sig.s), - }); - } - - /** - * You can sign a transaction and retrieve v, r, s given the raw transaction and the BIP 32 path of the account to sign. - * - * @param path: the BIP32 path to sign the transaction on - * @param rawTxHex: the raw ethereum transaction in hexadecimal to sign - * @param resolution: resolution is an object with all "resolved" metadata necessary to allow the device to clear sign information. This includes: ERC20 token information, plugins, contracts, NFT signatures,... You must explicitly provide something to avoid having a warning. By default, you can use Ledger's service or your own resolution service. See services/types.js for the contract. Setting the value to "null" will fallback everything to blind signing but will still allow the device to sign the transaction. - * @example - import { ledgerService } from "@ledgerhq/hw-app-eth" - const tx = "e8018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a2487400080"; // raw tx to sign - const resolution = await ledgerService.resolveTransaction(tx); - const result = eth.signTransaction("44'/60'/0'/0/0", tx, resolution); - console.log(result); - */ - async signTransaction( - path: string, - rawTxHex: string, - ): Promise<{ - s: string; - v: string; - r: string; - }> { - - const rawTx = Buffer.from(rawTxHex, "hex"); - const { vrsOffset, txType, chainId, chainIdTruncated } = decodeTxInfo(rawTx); - - const paths = splitPath(path); - let response; - let offset = 0; - while (offset !== rawTx.length) { - const first = offset === 0; - const maxChunkSize = first ? 150 - 1 - paths.length * 4 : 150; - let chunkSize = offset + maxChunkSize > rawTx.length ? rawTx.length - offset : maxChunkSize; - - if (vrsOffset != 0 && offset + chunkSize >= vrsOffset) { - // Make sure that the chunk doesn't end right on the EIP 155 marker if set - chunkSize = rawTx.length - offset; - } - - const buffer = Buffer.alloc(first ? 1 + paths.length * 4 + chunkSize : chunkSize); - - if (first) { - buffer[0] = paths.length; - paths.forEach((element, index) => { - buffer.writeUInt32BE(element, 1 + 4 * index); - }); - rawTx.copy(buffer, 1 + 4 * paths.length, offset, offset + chunkSize); - } else { - rawTx.copy(buffer, 0, offset, offset + chunkSize); - } - - response = await this.transport - .send(0xe0, 0x04, first ? 0x00 : 0x80, 0x00, buffer) - .catch(e => { - throw (e as Error).message; - }); - - offset += chunkSize; - } - if (!response) { - throw new Error('Something went wrong'); - } - - const response_byte: number = response[0]; - let v = ""; - - if (chainId.times(2).plus(35).plus(1).isGreaterThan(255)) { - const oneByteChainId = (chainIdTruncated * 2 + 35) % 256; - - const ecc_parity = Math.abs(response_byte - oneByteChainId); - - if (txType != null) { - // For EIP2930 and EIP1559 tx, v is simply the parity. - v = ecc_parity % 2 == 1 ? "00" : "01"; - } else { - // Legacy type transaction with a big chain ID - v = chainId.times(2).plus(35).plus(ecc_parity).toString(16); - } - } else { - v = response_byte.toString(16); - } - - // Make sure v has is prefixed with a 0 if its length is odd ("1" -> "01"). - if (v.length % 2 == 1) { - v = "0" + v; - } - - const r = response.slice(1, 1 + 32).toString("hex"); - const s = response.slice(1 + 32, 1 + 32 + 32).toString("hex"); - return { v, r, s }; - } - - /** - * Method returning a 4 bytes TLV challenge as an hexa string - * - * @returns {Promise} - */ - async getChallenge(): Promise { - enum APDU_FIELDS { - CLA = 0xe0, - INS = 0x20, - P1 = 0x00, - P2 = 0x00, - LC = 0x00, - } - return this.transport - .send(APDU_FIELDS.CLA, APDU_FIELDS.INS, APDU_FIELDS.P1, APDU_FIELDS.P2) - .then(res => { - const [, fourBytesChallenge, statusCode] = - new RegExp("(.*)(.{4}$)").exec(res.toString("hex")) || []; - - if (statusCode !== "9000") { - throw new Error( - `An error happened while generating the challenge. Status code: ${statusCode}`, - ); - } - return `0x${fourBytesChallenge}`; - }) - .catch(e => { - log("error", "couldn't request a challenge", e); - throw e; - }); - } - - /** - * provides a domain name (like ENS) to be displayed during transactions in place of the address it is associated to. It shall be run just before a transaction involving the associated address that would be displayed on the device. - * - * @param data an stringied buffer of some TLV encoded data to represent the domain - * @returns a boolean - */ - async provideDomainName(data: string): Promise { - enum APDU_FIELDS { - CLA = 0xe0, - INS = 0x22, - P1_FIRST_CHUNK = 0x01, - P1_FOLLOWING_CHUNK = 0x00, - P2 = 0x00, - } - const buffer = Buffer.from(data, "hex"); - const payload = Buffer.concat([Buffer.from(intAsHexBytes(buffer.length, 2), "hex"), buffer]); - - const bufferChunks = new Array(Math.ceil(payload.length / 256)) - .fill(null) - .map((_, i) => payload.slice(i * 255, (i + 1) * 255)); - for (const chunk of bufferChunks) { - const isFirstChunk = chunk === bufferChunks[0]; - await this.transport.send( - APDU_FIELDS.CLA, - APDU_FIELDS.INS, - isFirstChunk ? APDU_FIELDS.P1_FIRST_CHUNK : APDU_FIELDS.P1_FOLLOWING_CHUNK, - APDU_FIELDS.P2, - chunk, - ); - } - - return true; - } -} - -export default CarbonEVMLedger diff --git a/src/provider/ledger/CarbonEVMLedger/index.ts b/src/provider/ledger/CarbonEVMLedger/index.ts deleted file mode 100644 index 9ef29ae7..00000000 --- a/src/provider/ledger/CarbonEVMLedger/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CarbonEVMLedger' diff --git a/src/provider/ledger/CarbonEVMLedger/utils.ts b/src/provider/ledger/CarbonEVMLedger/utils.ts deleted file mode 100644 index e18f3879..00000000 --- a/src/provider/ledger/CarbonEVMLedger/utils.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { FetchUtils } from "@carbon-sdk/util"; -import { decode, encode } from "@ethersproject/rlp"; -import { log } from "@ledgerhq/logs"; -import BigNumber from "bignumber.js"; - -export type SupportedRegistries = "ens"; - -export type DomainDescriptor = { - registry: SupportedRegistries; - domain: string; - address: string; - type: "forward" | "reverse"; -}; - -export type LedgerEthTransactionResolution = { - // device serialized data that contains ERC20 data (hex format) - erc20Tokens: Array; - // device serialized data that contains external plugin data (hex format) - externalPlugin: Array<{ payload: string; signature: string }>; - // device serialized data that contains plugin data (hex format) - plugin: Array; - // device serialized data that contain trusted names data (hex format) - domains: DomainDescriptor[]; -}; - -declare enum CoinType { - ATOM = 118 -} - -export type Registry = { - name: SupportedRegistries; - resolvers: { - forward: string; - reverse: string; - }; - signatures: { - forward: string; - reverse: string; - }; - patterns: { - forward: RegExp; - reverse: RegExp; - }; - coinTypes: CoinType[]; -}; - -const REGISTRIES: Registry[] = [ - { - name: "ens", - resolvers: { - forward: "https://explorers.api.live.ledger.com/blockchain/v4/eth/ens/resolve/{name}", - reverse: - "https://explorers.api.live.ledger.com/blockchain/v4/eth/ens/reverse-resolve/{address}", - }, - signatures: { - forward: "https://nft.api.live.ledger.com/v1/names/ens/forward/{name}?challenge={challenge}", - reverse: - "https://nft.api.live.ledger.com/v1/names/ens/reverse/{address}?challenge={challenge}", - }, - patterns: { - forward: new RegExp("\\.eth$"), - reverse: new RegExp("^0x[0-9a-fA-F]{40}$"), - }, - coinTypes: [CoinType.ATOM], - }, -]; - -/** - * Method is voluntarly made async so it can be replaced by a backend call once implemented - */ -export const getRegistries = async (): Promise => REGISTRIES; - -/** - * Get an APDU to sign a domain resolution on the nano - * - * @param {string} domain - * @param {SupportedRegistries} registryName - * @param {string} challenge - * @returns {Promise} - */ -export const signDomainResolution = async ( - domain: string, - registryName: SupportedRegistries, - challenge: string, -): Promise => { - if (!validateDomain(domain)) { - throw new Error( - `Domains with more than 255 caracters or with unicode are not supported on the nano. Domain: ${domain}`, - ); - } - const registries = await getRegistries(); - const registry = registries.find(r => r.name === registryName); - if (!registry) return null; - - const url = registry.signatures.forward - .replace("{name}", domain) - .replace("{challenge}", challenge); - - return FetchUtils.fetch(url) - .then(({ data }) => data.payload) - .catch(error => { - /* istanbul ignore next: don't test logs */ - if (error.status !== 404) { - log("domain-service", "failed to get APDU for a domain", { - domain, - error, - }); - } - return null; - }); -}; - -/** - * Get an APDU to sign an address resolve resolution on the nano - * - * @param {string} address - * @param {SupportedRegistries} registryName - * @param {string} challenge - * @returns {Promise} - */ -export const signAddressResolution = async ( - address: string, - registryName: SupportedRegistries, - challenge: string, -): Promise => { - const registries = await getRegistries(); - const registry = registries.find(r => r.name === registryName); - if (!registry) return null; - - const url = registry.signatures.reverse - .replace("{address}", address) - .replace("{challenge}", challenge); - - return FetchUtils.fetch(url) - .then(({ data }) => data.payload) - .catch(error => { - /* istanbul ignore next: don't test logs */ - if (error.status !== 404) { - log("domain-service", "failed to get APDU for an address", { - address, - error, - }); - } - return null; - }); -}; - -/** - * Helper to know in advance if a domain is compatible with the nano - * - * @param domain string representing the domain - * @returns {Boolean} - */ -const validateDomain = (domain: string | undefined): boolean => { - if (typeof domain !== "string") { - return false; - } - - const lengthIsValid = domain.length > 0 && Number(domain.length) < 30; - const containsOnlyValidChars = new RegExp("^[a-zA-Z0-9\\-\\_\\.]+$").test(domain); - - return lengthIsValid && containsOnlyValidChars; -}; - -export const decodeTxInfo = (rawTx: Buffer) => { - const VALID_TYPES = [1, 2]; - const txType = VALID_TYPES.includes(rawTx[0]) ? rawTx[0] : null; - const rlpData = txType === null ? rawTx : rawTx.slice(1); - const rlpTx = decode(rlpData).map(hex => Buffer.from(hex.slice(2), "hex")); - let chainIdTruncated = 0; - const rlpDecoded = decode(rlpData); - - let decodedTx; - if (txType === 2) { - // EIP1559 - decodedTx = { - data: rlpDecoded[7], - to: rlpDecoded[5], - chainId: rlpTx[0], - }; - } else if (txType === 1) { - // EIP2930 - decodedTx = { - data: rlpDecoded[6], - to: rlpDecoded[4], - chainId: rlpTx[0], - }; - } else { - // Legacy tx - decodedTx = { - data: rlpDecoded[5], - to: rlpDecoded[3], - // Default to 1 for non EIP 155 txs - chainId: rlpTx.length > 6 ? rlpTx[6] : Buffer.from("01", "hex"), - }; - } - - const chainIdSrc = decodedTx.chainId; - let chainId = new BigNumber(0); - if (chainIdSrc) { - // Using BigNumber because chainID could be any uint256. - chainId = new BigNumber(chainIdSrc.toString("hex"), 16); - const chainIdTruncatedBuf = Buffer.alloc(4); - if (chainIdSrc.length > 4) { - chainIdSrc.copy(chainIdTruncatedBuf); - } else { - chainIdSrc.copy(chainIdTruncatedBuf, 4 - chainIdSrc.length); - } - chainIdTruncated = chainIdTruncatedBuf.readUInt32BE(0); - } - - let vrsOffset = 0; - if (txType === null && rlpTx.length > 6) { - const rlpVrs = Buffer.from(encode(rlpTx.slice(-3)).slice(2), "hex"); - - vrsOffset = rawTx.length - (rlpVrs.length - 1); - - // First byte > 0xf7 means the length of the list length doesn't fit in a single byte. - if (rlpVrs[0] > 0xf7) { - // Increment vrsOffset to account for that extra byte. - vrsOffset++; - - // Compute size of the list length. - const sizeOfListLen = rlpVrs[0] - 0xf7; - - // Increase rlpOffset by the size of the list length. - vrsOffset += sizeOfListLen - 1; - } - } - - return { - decodedTx, - txType, - chainId, - chainIdTruncated, - vrsOffset, - }; -}; - -export const mergeResolutions = ( - resolutionsArray: Partial[], -): LedgerEthTransactionResolution => { - const mergedResolutions: LedgerEthTransactionResolution = { - erc20Tokens: [], - externalPlugin: [], - plugin: [], - domains: [], - }; - - for (const resolutions of resolutionsArray) { - for (const key in resolutions) { - mergedResolutions[key].push(...resolutions[key]); - } - } - - return mergedResolutions; -}; - -export function splitPath(path: string): number[] { - const result: number[] = []; - const components = path.split("/"); - components.forEach(element => { - let number = parseInt(element, 10); - if (isNaN(number)) { - return; // FIXME shouldn't it throws instead? - } - if (element.length > 1 && element[element.length - 1] === "'") { - number += 0x80000000; - } - result.push(number); - }); - return result; -} diff --git a/src/provider/ledger/evm.ts b/src/provider/ledger/evm.ts new file mode 100644 index 00000000..c3bc471d --- /dev/null +++ b/src/provider/ledger/evm.ts @@ -0,0 +1,27 @@ +interface Signature { + v: string, + s: string, + r: string, +} + +export interface EthApp { + getAddress( + path: string, + boolDisplay?: boolean, + boolChaincode?: boolean, + ): Promise<{ + publicKey: string + address: string + chainCode?: string + }> + signTransaction(path: string, messageHex: string): Promise + transport: { + close(): Promise + } +} + +export interface EvmLedger { + readonly ethApp: EthApp; + getBIP44Path(): string; + signTransaction(msg: string, bip44String?: string): Promise; +} \ No newline at end of file diff --git a/src/provider/ledger/index.ts b/src/provider/ledger/index.ts index 1038101a..e0cd7797 100644 --- a/src/provider/ledger/index.ts +++ b/src/provider/ledger/index.ts @@ -1 +1,2 @@ export { default } from "./ledger"; +export * from "./evm"; diff --git a/src/provider/ledger/ledger.ts b/src/provider/ledger/ledger.ts index 4abfa758..826cabca 100644 --- a/src/provider/ledger/ledger.ts +++ b/src/provider/ledger/ledger.ts @@ -12,7 +12,7 @@ import { signatureImport } from "secp256k1"; import { signTransactionWrapper } from "@carbon-sdk/util/provider"; const semver = require("semver"); -import CarbonEVMLedger from "./CarbonEVMLedger"; +import { EthApp } from "./evm"; const INTERACTION_TIMEOUT = 120; // seconds to wait for user action on Ledger, currently is always limited to 60 const REQUIRED_COSMOS_APP_VERSION = "1.5.3"; @@ -27,7 +27,7 @@ const BECH32PREFIX = `cosmos`; class CosmosLedger { private readonly testModeAllowed: Boolean; private cosmosApp: any; - private carbonEVMApp: any; + private ethApp: EthApp | undefined = undefined private hdPath: Array; private hrp: string; public platform: string; @@ -132,18 +132,16 @@ class CosmosLedger { const cosmosLedgerApp = new CosmosLedgerApp(transport); this.cosmosApp = cosmosLedgerApp; - const carbonEVMLedgerApp = new CarbonEVMLedger(transport); - this.carbonEVMApp = carbonEVMLedgerApp; // checks if the Ledger is connected and the app is open await this.isReady(); - + return this; } async disconnect() { await this.cosmosApp.transport.close() - await this.carbonEVMApp.transport.close() + await this.ethApp?.transport.close() } async getDeviceName() { @@ -258,14 +256,6 @@ class CosmosLedger { return parsedSignature; } - async signEvmMessage(tx: ethers.providers.TransactionRequest) { - await this.connect(); - const response = await signTransactionWrapper(async () => { - return await this.carbonEVMApp.signEIP712Tx(tx); - }); - return response; - } - // parse Ledger errors in a more user friendly format /* istanbul ignore next: maps a bunch of errors */ private async checkLedgerErrors( @@ -300,6 +290,10 @@ class CosmosLedger { throw new Error(`Ledger Native Error: ${error_message}`); } } + + public async initEthApp(app: EthApp) { + this.ethApp = app + } } // stiched version string from Ledger app version object @@ -343,4 +337,5 @@ function getBrowser(userAgent: string) { else return "chrome"; } + export default CosmosLedger; diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index e006de06..208e2250 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -8,6 +8,7 @@ import { constructAdr36SignDoc } from "@carbon-sdk/util/message"; import { AminoSignResponse, encodeSecp256k1Signature, OfflineAminoSigner, Secp256k1Wallet, StdSignDoc } from "@cosmjs/amino"; import { AccountData, DirectSecp256k1Wallet, DirectSignResponse, OfflineDirectSigner, OfflineSigner } from "@cosmjs/proto-signing"; import { ethers } from "ethers"; +import { LedgerWalletConnectionOpts } from "./CarbonWallet"; export enum CarbonSignerTypes { Ledger, @@ -150,9 +151,22 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { signature, }; } + private async connectoToEthApp() { + if (!this.opts?.connectEthApp) throw new Error('evm app initialisation is not provided') + const evmLedger = await this.opts.connectEthApp() + this.ledger.initEthApp(evmLedger.ethApp) + return evmLedger + } - async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise { // eslint-disable-line - throw new Error("signing not available"); + async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise { + const evmLedger = await this.connectoToEthApp() + const unsignedTx = await populateUnsignedEvmTranscation(api, req) + const serializedTx = ethers.utils.serializeTransaction(unsignedTx) + const bipString = evmLedger.getBIP44Path() + const signature = await evmLedger.signTransaction(bipString, serializedTx.substring(2)) + const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) + const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) + return (await provider!.sendTransaction(signedTx)).hash } async signMessage(_: string, message: string): Promise { @@ -165,5 +179,5 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { return Buffer.from(signature, 'base64').toString('hex') } - constructor(readonly ledger: CosmosLedger) { } + constructor(readonly ledger: CosmosLedger, readonly opts?: LedgerWalletConnectionOpts) { } } diff --git a/src/wallet/CarbonWallet.ts b/src/wallet/CarbonWallet.ts index 1efae03d..c2cec80c 100644 --- a/src/wallet/CarbonWallet.ts +++ b/src/wallet/CarbonWallet.ts @@ -32,6 +32,7 @@ import utc from "dayjs/plugin/utc"; import { CarbonEIP712Signer, CarbonLedgerSigner, CarbonNonSigner, CarbonPrivateKeySigner, CarbonSigner, CarbonSignerTypes, isCarbonEIP712Signer } from "./CarbonSigner"; import { CarbonSigningClient } from "./CarbonSigningClient"; import RainbowKitAccount from "@carbon-sdk/provider/rainbowKit/RainbowKitAccount"; +import { EvmLedger } from "@carbon-sdk/provider/ledger"; dayjs.extend(utc) @@ -69,6 +70,14 @@ export interface CarbonWalletGenericOpts { onBroadcastTxFail?: CarbonWallet.OnBroadcastTxFailCallback; } +export type LedgerWalletConnectionOpts = { + connectEthApp?: () => Promise +} + +export interface CarbonLedgerWalletGenericOpts extends CarbonWalletGenericOpts { + connectionOpts?: LedgerWalletConnectionOpts +} + export interface MetaMaskWalletOpts { publicKeyMessage?: string publicKeyBase64?: string; @@ -281,11 +290,12 @@ export class CarbonWallet { }); } - public static withLedger(cosmosLedger: CosmosLedger, publicKeyBase64: string, opts: Omit = {}) { - const signer = new CarbonLedgerSigner(cosmosLedger); + public static withLedger(cosmosLedger: CosmosLedger, publicKeyBase64: string, opts: Omit = {}) { + const { connectionOpts, ...rest } = opts + const signer = new CarbonLedgerSigner(cosmosLedger, connectionOpts); const wallet = CarbonWallet.withSigner(signer, publicKeyBase64, { providerAgent: ProviderAgent.Ledger, - ...opts, + ...rest, }); return wallet; } From 7607adab20ae96f36ac2e98ba4b7ab51d2be51ff Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 12:12:20 +0800 Subject: [PATCH 03/18] v0.11.27-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4f43045f..ea1f0d84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.26", + "version": "0.11.27-beta.1", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", From 901ddea7225ccad0c897dd808242328f0da577c2 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 16:45:08 +0800 Subject: [PATCH 04/18] logs to test evm transaction --- src/wallet/CarbonSigner.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index 208e2250..d559cb89 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -163,6 +163,8 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { const unsignedTx = await populateUnsignedEvmTranscation(api, req) const serializedTx = ethers.utils.serializeTransaction(unsignedTx) const bipString = evmLedger.getBIP44Path() + console.log('xx serialisedTx: ',serializedTx) + console.log('xx serialisedTx substring(2): ',serializedTx.substring(2)) const signature = await evmLedger.signTransaction(bipString, serializedTx.substring(2)) const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) From 3fad8b0abfe03b1167316db4c428ba56ae684ae9 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 16:45:37 +0800 Subject: [PATCH 05/18] v0.11.27-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea1f0d84..ebcc6ed2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.1", + "version": "0.11.27-beta.2", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", From e0edb4f20ecd4ab317bef02b47fa6c6bfc25e7be Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 17:33:46 +0800 Subject: [PATCH 06/18] reconstruct evm tx that is signable by ledger --- package.json | 2 +- src/wallet/CarbonSigner.ts | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ebcc6ed2..9d8afd4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.2", + "version": "0.11.27-beta.3", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index d559cb89..a2ff7990 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -161,10 +161,18 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise { const evmLedger = await this.connectoToEthApp() const unsignedTx = await populateUnsignedEvmTranscation(api, req) - const serializedTx = ethers.utils.serializeTransaction(unsignedTx) + // reconstruct tx that is signable by ledger + const baseTx: ethers.utils.UnsignedTransaction = { + chainId: (unsignedTx.chainId || undefined), + data: (unsignedTx.data || undefined), + gasLimit: (unsignedTx.gasLimit || undefined), + gasPrice: (unsignedTx.gasPrice || undefined), + nonce: (unsignedTx.nonce ? ethers.BigNumber.from(unsignedTx.nonce).toNumber(): undefined), + to: (unsignedTx.to || undefined), + value: (unsignedTx.value || undefined), + }; + const serializedTx = ethers.utils.serializeTransaction(baseTx) const bipString = evmLedger.getBIP44Path() - console.log('xx serialisedTx: ',serializedTx) - console.log('xx serialisedTx substring(2): ',serializedTx.substring(2)) const signature = await evmLedger.signTransaction(bipString, serializedTx.substring(2)) const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) From cb18122a1608999aff9058e5f235c48594dda744 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 18:10:29 +0800 Subject: [PATCH 07/18] fix signTransaction --- package.json | 2 +- src/wallet/CarbonSigner.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9d8afd4a..515f1a7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.3", + "version": "0.11.27-beta.4", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index a2ff7990..5f9adc27 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -172,8 +172,7 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { value: (unsignedTx.value || undefined), }; const serializedTx = ethers.utils.serializeTransaction(baseTx) - const bipString = evmLedger.getBIP44Path() - const signature = await evmLedger.signTransaction(bipString, serializedTx.substring(2)) + const signature = await evmLedger.signTransaction(serializedTx.substring(2)) const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) return (await provider!.sendTransaction(signedTx)).hash From 299ee0ee0f010bef90cdfa9f03c2f47764bb1f2b Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 18:35:17 +0800 Subject: [PATCH 08/18] add bip string to get correct address --- src/provider/ledger/evm.ts | 16 ---------------- src/wallet/CarbonSigner.ts | 15 +++------------ 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/src/provider/ledger/evm.ts b/src/provider/ledger/evm.ts index c3bc471d..d36cd5d0 100644 --- a/src/provider/ledger/evm.ts +++ b/src/provider/ledger/evm.ts @@ -1,20 +1,4 @@ -interface Signature { - v: string, - s: string, - r: string, -} - export interface EthApp { - getAddress( - path: string, - boolDisplay?: boolean, - boolChaincode?: boolean, - ): Promise<{ - publicKey: string - address: string - chainCode?: string - }> - signTransaction(path: string, messageHex: string): Promise transport: { close(): Promise } diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index 5f9adc27..6f91eb4c 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -161,18 +161,9 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise { const evmLedger = await this.connectoToEthApp() const unsignedTx = await populateUnsignedEvmTranscation(api, req) - // reconstruct tx that is signable by ledger - const baseTx: ethers.utils.UnsignedTransaction = { - chainId: (unsignedTx.chainId || undefined), - data: (unsignedTx.data || undefined), - gasLimit: (unsignedTx.gasLimit || undefined), - gasPrice: (unsignedTx.gasPrice || undefined), - nonce: (unsignedTx.nonce ? ethers.BigNumber.from(unsignedTx.nonce).toNumber(): undefined), - to: (unsignedTx.to || undefined), - value: (unsignedTx.value || undefined), - }; - const serializedTx = ethers.utils.serializeTransaction(baseTx) - const signature = await evmLedger.signTransaction(serializedTx.substring(2)) + const serializedTx = ethers.utils.serializeTransaction(unsignedTx) + const bipString = evmLedger.getBIP44Path() + const signature = await evmLedger.signTransaction(serializedTx.substring(2), bipString) const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) return (await provider!.sendTransaction(signedTx)).hash From 4ca5749dbf2bff3ae379a894cfd8d0d94bfaac98 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 18:35:35 +0800 Subject: [PATCH 09/18] v0.11.27-beta.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 515f1a7e..666249d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.4", + "version": "0.11.27-beta.5", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", From b78542385b1d9eb0118e8c21979127bcad942633 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 19:03:54 +0800 Subject: [PATCH 10/18] fix bipPath --- package.json | 2 +- src/provider/ledger/evm.ts | 2 +- src/wallet/CarbonSigner.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 666249d0..391aaebf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.5", + "version": "0.11.27-beta.6", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/provider/ledger/evm.ts b/src/provider/ledger/evm.ts index d36cd5d0..2d60e553 100644 --- a/src/provider/ledger/evm.ts +++ b/src/provider/ledger/evm.ts @@ -6,6 +6,6 @@ export interface EthApp { export interface EvmLedger { readonly ethApp: EthApp; - getBIP44Path(): string; + getBIP44Path(): string signTransaction(msg: string, bip44String?: string): Promise; } \ No newline at end of file diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index 6f91eb4c..f93b0ff7 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -163,6 +163,7 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { const unsignedTx = await populateUnsignedEvmTranscation(api, req) const serializedTx = ethers.utils.serializeTransaction(unsignedTx) const bipString = evmLedger.getBIP44Path() + console.log('xx bipString:',bipString) const signature = await evmLedger.signTransaction(serializedTx.substring(2), bipString) const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) From 53495272e9727e9c95187c1f5f3ee4fd0bd9ea82 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 22:29:32 +0800 Subject: [PATCH 11/18] get correct bip44Path for ledger evm signing --- package.json | 2 +- src/provider/ledger/ledger.ts | 4 ++++ src/wallet/CarbonSigner.ts | 19 +++++++++++++++---- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 391aaebf..4f9265dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.6", + "version": "0.11.27-beta.7", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/provider/ledger/ledger.ts b/src/provider/ledger/ledger.ts index 826cabca..21148eb0 100644 --- a/src/provider/ledger/ledger.ts +++ b/src/provider/ledger/ledger.ts @@ -45,6 +45,10 @@ class CosmosLedger { this.userAgent = navigator.userAgent; // set it here to overwrite in tests } + public getHdPath() { + return this.hdPath + } + // quickly test connection and compatibility with the Ledger device throwing away the connection async testDevice() { // poll device with low timeout to check if the device is connected diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index f93b0ff7..b637e044 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -151,19 +151,30 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { signature, }; } - private async connectoToEthApp() { + private async connectToEthApp() { if (!this.opts?.connectEthApp) throw new Error('evm app initialisation is not provided') const evmLedger = await this.opts.connectEthApp() this.ledger.initEthApp(evmLedger.ethApp) return evmLedger } + // [44, 118, 0, 0, 0] => m/44'/118/0/0/0 + private getBip44Path(): string { + const hdPathArray: Array = this.ledger.getHdPath() + return hdPathArray.reduce((acc, element, index) => { + acc.concat(`/`).concat(element.toString()) + if (index === 0 || index === 1) acc.concat("'") + + return acc + }, 'm') + } + async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise { - const evmLedger = await this.connectoToEthApp() + const evmLedger = await this.connectToEthApp() const unsignedTx = await populateUnsignedEvmTranscation(api, req) const serializedTx = ethers.utils.serializeTransaction(unsignedTx) - const bipString = evmLedger.getBIP44Path() - console.log('xx bipString:',bipString) + const bipString = this.getBip44Path() + console.log('xx bipString:', bipString) const signature = await evmLedger.signTransaction(serializedTx.substring(2), bipString) const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) From 8467de68bbd46f27f74011a333131c2634b27e50 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 22:57:32 +0800 Subject: [PATCH 12/18] add bip44String when connecting evmapp --- package.json | 2 +- src/wallet/CarbonSigner.ts | 18 +++++++++--------- src/wallet/CarbonWallet.ts | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 4f9265dd..3532c396 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.7", + "version": "0.11.27-beta.8", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index b637e044..c8de0b76 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -151,31 +151,31 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { signature, }; } - private async connectToEthApp() { + private async connectToEthApp(bip44String: string) { if (!this.opts?.connectEthApp) throw new Error('evm app initialisation is not provided') - const evmLedger = await this.opts.connectEthApp() + const evmLedger = await this.opts.connectEthApp(bip44String) this.ledger.initEthApp(evmLedger.ethApp) return evmLedger } // [44, 118, 0, 0, 0] => m/44'/118/0/0/0 - private getBip44Path(): string { + private getBip44String(): string { const hdPathArray: Array = this.ledger.getHdPath() return hdPathArray.reduce((acc, element, index) => { - acc.concat(`/`).concat(element.toString()) - if (index === 0 || index === 1) acc.concat("'") + acc = acc.concat(`/`).concat(element.toString()) + if (index === 0 || index === 1) acc = acc.concat("'") return acc }, 'm') } async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise { - const evmLedger = await this.connectToEthApp() + const bip44String = this.getBip44String() + const evmLedger = await this.connectToEthApp(bip44String) const unsignedTx = await populateUnsignedEvmTranscation(api, req) const serializedTx = ethers.utils.serializeTransaction(unsignedTx) - const bipString = this.getBip44Path() - console.log('xx bipString:', bipString) - const signature = await evmLedger.signTransaction(serializedTx.substring(2), bipString) + console.log('xx bipString:', bip44String) + const signature = await evmLedger.signTransaction(serializedTx.substring(2), bip44String) const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) return (await provider!.sendTransaction(signedTx)).hash diff --git a/src/wallet/CarbonWallet.ts b/src/wallet/CarbonWallet.ts index c2cec80c..c2f06ad7 100644 --- a/src/wallet/CarbonWallet.ts +++ b/src/wallet/CarbonWallet.ts @@ -71,7 +71,7 @@ export interface CarbonWalletGenericOpts { } export type LedgerWalletConnectionOpts = { - connectEthApp?: () => Promise + connectEthApp?: (bipString?: string) => Promise } export interface CarbonLedgerWalletGenericOpts extends CarbonWalletGenericOpts { From fcb65d5968c6755e520967ed75521ab02f56f8b4 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 23:20:16 +0800 Subject: [PATCH 13/18] update bip44string generation --- package.json | 2 +- src/wallet/CarbonSigner.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3532c396..6748f819 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.8", + "version": "0.11.27-beta.9", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index c8de0b76..8700b4fb 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -158,12 +158,12 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { return evmLedger } - // [44, 118, 0, 0, 0] => m/44'/118/0/0/0 + // [44, 118, 0, 0, 0] => 44'/118'/0'/0/0 private getBip44String(): string { const hdPathArray: Array = this.ledger.getHdPath() return hdPathArray.reduce((acc, element, index) => { acc = acc.concat(`/`).concat(element.toString()) - if (index === 0 || index === 1) acc = acc.concat("'") + if (index === 0 || index === 1 || index === 2) acc = acc.concat("'") return acc }, 'm') From 5a0378bf7e24f01b26e7d1ff49fbf95a6d650151 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 23:26:08 +0800 Subject: [PATCH 14/18] update bip44string generation --- package.json | 2 +- src/wallet/CarbonSigner.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6748f819..a62b5378 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.9", + "version": "0.11.27-beta.10", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index 8700b4fb..4fde934f 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -162,11 +162,14 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { private getBip44String(): string { const hdPathArray: Array = this.ledger.getHdPath() return hdPathArray.reduce((acc, element, index) => { - acc = acc.concat(`/`).concat(element.toString()) + if (index === 0) { + acc = acc.concat(element.toString()) + return acc + } + acc = acc.concat(`/`).concat(element.toString()) if (index === 0 || index === 1 || index === 2) acc = acc.concat("'") - return acc - }, 'm') + }, '') } async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise { From 5d39e06379e07a5092d88a50f4ec5c1b9e33a298 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 23:30:19 +0800 Subject: [PATCH 15/18] remove need to have bip4String in connectToEthApp --- package.json | 2 +- src/wallet/CarbonSigner.ts | 6 +++--- src/wallet/CarbonWallet.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a62b5378..6f8c462f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.10", + "version": "0.11.27-beta.11", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index 4fde934f..adfa85ce 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -151,9 +151,9 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { signature, }; } - private async connectToEthApp(bip44String: string) { + private async connectToEthApp() { if (!this.opts?.connectEthApp) throw new Error('evm app initialisation is not provided') - const evmLedger = await this.opts.connectEthApp(bip44String) + const evmLedger = await this.opts.connectEthApp() this.ledger.initEthApp(evmLedger.ethApp) return evmLedger } @@ -174,7 +174,7 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise { const bip44String = this.getBip44String() - const evmLedger = await this.connectToEthApp(bip44String) + const evmLedger = await this.connectToEthApp() const unsignedTx = await populateUnsignedEvmTranscation(api, req) const serializedTx = ethers.utils.serializeTransaction(unsignedTx) console.log('xx bipString:', bip44String) diff --git a/src/wallet/CarbonWallet.ts b/src/wallet/CarbonWallet.ts index c2f06ad7..c2cec80c 100644 --- a/src/wallet/CarbonWallet.ts +++ b/src/wallet/CarbonWallet.ts @@ -71,7 +71,7 @@ export interface CarbonWalletGenericOpts { } export type LedgerWalletConnectionOpts = { - connectEthApp?: (bipString?: string) => Promise + connectEthApp?: () => Promise } export interface CarbonLedgerWalletGenericOpts extends CarbonWalletGenericOpts { From 551cfa56ce2780533f8dfb9025a491ec8037e249 Mon Sep 17 00:00:00 2001 From: Randy Date: Fri, 18 Oct 2024 23:52:28 +0800 Subject: [PATCH 16/18] fix bip44String --- package.json | 2 +- src/wallet/CarbonSigner.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6f8c462f..836f28e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.11", + "version": "0.11.27-beta.12", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index adfa85ce..1640cf8e 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -163,7 +163,7 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { const hdPathArray: Array = this.ledger.getHdPath() return hdPathArray.reduce((acc, element, index) => { if (index === 0) { - acc = acc.concat(element.toString()) + acc = acc.concat(element.toString()).concat("'") return acc } acc = acc.concat(`/`).concat(element.toString()) @@ -178,7 +178,7 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { const unsignedTx = await populateUnsignedEvmTranscation(api, req) const serializedTx = ethers.utils.serializeTransaction(unsignedTx) console.log('xx bipString:', bip44String) - const signature = await evmLedger.signTransaction(serializedTx.substring(2), bip44String) + const signature = await evmLedger.signTransaction(serializedTx.substring(2)) const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) return (await provider!.sendTransaction(signedTx)).hash From 32812d3b212e7b2b4954a23a558db51251299a26 Mon Sep 17 00:00:00 2001 From: Randy Date: Sat, 19 Oct 2024 00:18:31 +0800 Subject: [PATCH 17/18] fix bip44String --- src/wallet/CarbonSigner.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wallet/CarbonSigner.ts b/src/wallet/CarbonSigner.ts index 1640cf8e..dfc7447f 100644 --- a/src/wallet/CarbonSigner.ts +++ b/src/wallet/CarbonSigner.ts @@ -166,7 +166,7 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { acc = acc.concat(element.toString()).concat("'") return acc } - acc = acc.concat(`/`).concat(element.toString()) + acc = acc.concat(`/`).concat(element.toString()) if (index === 0 || index === 1 || index === 2) acc = acc.concat("'") return acc }, '') @@ -177,8 +177,7 @@ export class CarbonLedgerSigner implements AminoCarbonSigner { const evmLedger = await this.connectToEthApp() const unsignedTx = await populateUnsignedEvmTranscation(api, req) const serializedTx = ethers.utils.serializeTransaction(unsignedTx) - console.log('xx bipString:', bip44String) - const signature = await evmLedger.signTransaction(serializedTx.substring(2)) + const signature = await evmLedger.signTransaction(serializedTx.substring(2), bip44String) const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature) const provider = new ethers.providers.JsonRpcProvider(NetworkConfigs[api.network].evmJsonRpcUrl) return (await provider!.sendTransaction(signedTx)).hash From f7fd15f989513c44cf70e8ccedbf70edf4c5031e Mon Sep 17 00:00:00 2001 From: Randy Date: Sat, 19 Oct 2024 00:18:49 +0800 Subject: [PATCH 18/18] v0.11.27-beta.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 836f28e5..b704e375 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-js-sdk", - "version": "0.11.27-beta.12", + "version": "0.11.27-beta.13", "description": "TypeScript SDK for Carbon blockchain", "main": "lib/index.js", "types": "lib/index.d.ts",