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

new module for ledger related utils #193

Merged
merged 6 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
5 changes: 5 additions & 0 deletions .changeset/silver-bugs-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sei-js/ledger': minor
---

Packge init + helper functions to work with Ledger in Cosmos Stargate client
2 changes: 2 additions & 0 deletions packages/ledger/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
Empty file added packages/ledger/.gitkeep
Empty file.
13 changes: 13 additions & 0 deletions packages/ledger/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
src
node_modules
docs
coverage/

.gitkeep
jest.config.ts
tsconfig.json

yarn-error.log

.eslintignore
eslintrc.json
52 changes: 52 additions & 0 deletions packages/ledger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# @sei-js/ledger

## Installation
```bash
yarn add @sei-js/ledger
```

## Usage
```typescript
import {
coins,
SigningStargateClient,
StdFee
} from "@cosmjs/stargate";

import {
createTransportAndApp,
getAddresses,
LedgerOfflineAminoSigner
} from "@sei-js/ledger";

const testApp = async () => {
const validatorAddress = "seivaloper1sq7x0r2mf3gvwr2l9amtlye0yd3c6dqa4th95v";
const rpcUrl = "https://rpc-testnet.sei-apis.com/";
const memo = "Delegation";
const path = "m/44'/60'/0'/0/0";

const {app} = await createTransportAndApp();
const {nativeAddress} = await getAddresses(app, path);
const ledgerSigner = new LedgerOfflineAminoSigner(app, path)
const signingStargateClient = await SigningStargateClient.connectWithSigner(rpcUrl, ledgerSigner)

const msgDelegate = {
typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
value: {
delegatorAddress: nativeAddress.address,
validatorAddress: validatorAddress,
amount: coins(500, "usei"),
},
};

const fee: StdFee = {
amount: [{denom: "usei", amount: "20000"}],
gas: "200000",
};

const result = await signingStargateClient.signAndBroadcast(nativeAddress.address, [msgDelegate], fee, memo)
console.log("Broadcast result:", result);
};

testApp();
```
3 changes: 3 additions & 0 deletions packages/ledger/eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["../../eslint.base.json", "eslint:recommended", "plugin:@typescript-eslint/recommended"]
}
8 changes: 8 additions & 0 deletions packages/ledger/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
modulePathIgnorePatterns: ['<rootDir>/dist/']
};
50 changes: 50 additions & 0 deletions packages/ledger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@sei-js/ledger",
"version": "1.0.0",
"description": "TypeScript library for SEI Ledger app helper functions",
"main": "./dist/cjs/src/index.js",
"module": "./dist/esm/src/index.js",
"types": "./dist/types/src/index.d.ts",
"sideEffects": false,
"files": [
"dist"
],
"scripts": {
"prebuild": "rimraf dist",
"build": "yarn build:cjs && yarn build:esm && yarn build:prettier && yarn build:types",
"build:cjs": "tsc --outDir dist/cjs --module commonjs",
"build:esm": "tsc --outDir dist/esm --module esnext",
"build:types": "tsc --project ./tsconfig.declaration.json",
"build:prettier": "prettier --write 'dist/**/*.js'",
"docs": "typedoc --out docs",
"test": "jest --passWithNoTests",
"lint": "eslint --ext .ts"
},
"homepage": "https://github.com/sei-protocol/sei-js#readme",
"keywords": [
"sei",
"javascript",
"typescript",
"ledger"
],
"repository": "git@github.com:sei-protocol/sei-js.git",
"license": "MIT",
"private": false,
"publishConfig": {
"access": "public"
},
"dependencies": {
"@cosmjs/stargate": "^0.32.4",
"@ledgerhq/hw-transport-node-hid": "^6.29.3",
"@zondax/ledger-sei": "^1.0.0"
},
"peerDependencies": {},
"devDependencies": {},
"exports": {
".": {
"import": "./dist/esm/src/index.js",
"require": "./dist/cjs/src/index.js",
"types": "./dist/types/src/index.d.ts"
}
}
}
1 change: 1 addition & 0 deletions packages/ledger/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './wallet';
1 change: 1 addition & 0 deletions packages/ledger/src/wallet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./utils"
77 changes: 77 additions & 0 deletions packages/ledger/src/wallet/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Transport from '@ledgerhq/hw-transport-node-hid';
import {
AminoSignResponse,
encodeSecp256k1Signature,
OfflineAminoSigner,
serializeSignDoc,
StdSignDoc
} from '@cosmjs/amino';
import {fromHex } from '@cosmjs/encoding';
import { AccountData } from '@cosmjs/proto-signing';
import { Secp256k1Signature } from '@cosmjs/crypto';
import { SeiApp } from '@zondax/ledger-sei';

/**
* A signer implementation that uses a Ledger device to sign transactions
*/
export class LedgerOfflineAminoSigner implements OfflineAminoSigner {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, this is great! should we name this SeiLedgerOfflineAminoSigner so it's more specific?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

private readonly path: string;
private readonly app: SeiApp;

/**
* Creates a new LedgerOfflineAminoSigner
* @param app Ledger Sei app instance
* @param path hd derivation path (e.g. "m/44'/60'/0'/0/0")
*/
constructor(app: SeiApp, path: string) {
this.path = path;
this.app = app;
}

public async getAccounts(): Promise<readonly AccountData[]> {
const nativeAddress = await this.app.getCosmosAddress(this.path);
return [
{
address: nativeAddress.address,
algo: "secp256k1",
pubkey: fromHex(nativeAddress.pubKey)
},
];
}

public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
const signature = await this.app.signCosmos(this.path, Buffer.from(serializeSignDoc(signDoc)));
const sig = new Secp256k1Signature(signature.r, signature.s).toFixedLength();
const nativeAddress = await this.app.getCosmosAddress(this.path);
return {
signed: signDoc,
signature: encodeSecp256k1Signature(fromHex(nativeAddress.pubKey), sig),
};
}
}

/**
* Creates a transport and app instance
*
* @returns {Promise<{transport: Transport, app: SeiApp}>} transport and app instances
*/
export const createTransportAndApp = async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we keep this in the utils.ts file and move LedgerOfflineAminoSigner into another file to make it cleaner (maybe called signer.ts)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

const transport = await Transport.create();
const app = new SeiApp(transport);
return {transport, app};
};

/**
* Get the EVM and Cosmos addresses from the Ledger device
* @param app Ledger Sei app instance
* @param path hd derivation path (e.g. "m/44'/60'/0'/0/0")
*
* @returns {Promise<{evmAddress: string, nativeAddress: string}>} EVM and Cosmos address objects containing
* address and public key
*/
export const getAddresses = async (app: SeiApp, path: string) => {
const evmAddress = await app.getEVMAddress(path);
const nativeAddress = await app.getCosmosAddress(path);
return {evmAddress, nativeAddress};
};

10 changes: 10 additions & 0 deletions packages/ledger/tsconfig.declaration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./dist/types",
"declaration": true,
"emitDeclarationOnly": true,
"isolatedModules": false,
"preserveConstEnums": false
}
}
13 changes: 13 additions & 0 deletions packages/ledger/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"include": ["./src/**/*" ],
"compilerOptions": {
"outDir": "./dist/types",
"rootDir": "./",
},
"typedocOptions": {
"readme": "./README.md",
"name": "registry",
"entryPoints": ["src/index.ts"]
}
}
Loading
Loading