Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CarbonEVMLedger class to support ledger evm signing (WIP) #540

Open
wants to merge 19 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions examples/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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),
}),
Expand Down Expand Up @@ -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));
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "carbon-js-sdk",
"version": "0.11.26",
"version": "0.11.27-beta.13",
"description": "TypeScript SDK for Carbon blockchain",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down
12 changes: 6 additions & 6 deletions src/CarbonSDK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -35,28 +35,28 @@ import {
LeverageModule,
LiquidityPoolModule,
MarketModule,
OTCModule,
OracleModule,
OrderModule,
PerpspoolModule,
PositionModule,
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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");

Expand Down
11 changes: 11 additions & 0 deletions src/provider/ledger/evm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface EthApp {
transport: {
close(): Promise<void>
}
}

export interface EvmLedger {
readonly ethApp: EthApp;
getBIP44Path(): string
signTransaction(msg: string, bip44String?: string): Promise<string>;
}
1 change: 1 addition & 0 deletions src/provider/ledger/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default } from "./ledger";
export * from "./evm";
15 changes: 14 additions & 1 deletion src/provider/ledger/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { signatureImport } from "secp256k1";
import { signTransactionWrapper } from "@carbon-sdk/util/provider";
const semver = require("semver");

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";

Expand All @@ -25,6 +27,7 @@ const BECH32PREFIX = `cosmos`;
class CosmosLedger {
private readonly testModeAllowed: Boolean;
private cosmosApp: any;
private ethApp: EthApp | undefined = undefined
private hdPath: Array<number>;
private hrp: string;
public platform: string;
Expand All @@ -42,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
Expand Down Expand Up @@ -132,12 +139,13 @@ class CosmosLedger {

// 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.ethApp?.transport.close()
}

async getDeviceName() {
Expand Down Expand Up @@ -286,6 +294,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
Expand Down Expand Up @@ -329,4 +341,5 @@ function getBrowser(userAgent: string) {
else return "chrome";
}


export default CosmosLedger;
36 changes: 32 additions & 4 deletions src/wallet/CarbonSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -150,9 +151,36 @@ export class CarbonLedgerSigner implements AminoCarbonSigner {
signature,
};
}

async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise<string> { // eslint-disable-line
throw new Error("signing not available");
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] => 44'/118'/0'/0/0
private getBip44String(): string {
const hdPathArray: Array<number> = this.ledger.getHdPath()
return hdPathArray.reduce((acc, element, index) => {
if (index === 0) {
acc = acc.concat(element.toString()).concat("'")
return acc
}
acc = acc.concat(`/`).concat(element.toString())
if (index === 0 || index === 1 || index === 2) acc = acc.concat("'")
return acc
}, '')
}

async sendEvmTransaction(api: CarbonSDK, req: ethers.providers.TransactionRequest): Promise<string> {
const bip44String = this.getBip44String()
const evmLedger = await this.connectToEthApp()
const unsignedTx = await populateUnsignedEvmTranscation(api, req)
const serializedTx = ethers.utils.serializeTransaction(unsignedTx)
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
}

async signMessage(_: string, message: string): Promise<string> {
Expand All @@ -165,5 +193,5 @@ export class CarbonLedgerSigner implements AminoCarbonSigner {
return Buffer.from(signature, 'base64').toString('hex')
}

constructor(readonly ledger: CosmosLedger) { }
constructor(readonly ledger: CosmosLedger, readonly opts?: LedgerWalletConnectionOpts) { }
}
16 changes: 13 additions & 3 deletions src/wallet/CarbonWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -69,6 +70,14 @@ export interface CarbonWalletGenericOpts {
onBroadcastTxFail?: CarbonWallet.OnBroadcastTxFailCallback;
}

export type LedgerWalletConnectionOpts = {
connectEthApp?: () => Promise<EvmLedger>
}

export interface CarbonLedgerWalletGenericOpts extends CarbonWalletGenericOpts {
connectionOpts?: LedgerWalletConnectionOpts
}

export interface MetaMaskWalletOpts {
publicKeyMessage?: string
publicKeyBase64?: string;
Expand Down Expand Up @@ -281,11 +290,12 @@ export class CarbonWallet {
});
}

public static withLedger(cosmosLedger: CosmosLedger, publicKeyBase64: string, opts: Omit<CarbonWalletInitOpts, "signer"> = {}) {
const signer = new CarbonLedgerSigner(cosmosLedger);
public static withLedger(cosmosLedger: CosmosLedger, publicKeyBase64: string, opts: Omit<CarbonLedgerWalletGenericOpts, "signer"> = {}) {
const { connectionOpts, ...rest } = opts
const signer = new CarbonLedgerSigner(cosmosLedger, connectionOpts);
const wallet = CarbonWallet.withSigner(signer, publicKeyBase64, {
providerAgent: ProviderAgent.Ledger,
...opts,
...rest,
});
return wallet;
}
Expand Down