diff --git a/packages/launchpad-ledger/src/index.ts b/packages/launchpad-ledger/src/index.ts index 0ca77769..f0148827 100644 --- a/packages/launchpad-ledger/src/index.ts +++ b/packages/launchpad-ledger/src/index.ts @@ -1 +1,2 @@ export { LaunchpadLedger } from "./launchpadledger"; +export { LedgerWallet } from "./ledgerwallet"; diff --git a/packages/launchpad-ledger/src/ledger.demo.ts b/packages/launchpad-ledger/src/ledger.demo.ts new file mode 100644 index 00000000..023b466f --- /dev/null +++ b/packages/launchpad-ledger/src/ledger.demo.ts @@ -0,0 +1,38 @@ +import { toHex, toUtf8 } from "@cosmjs/encoding"; + +import { LedgerWallet } from "./ledgerwallet"; + +declare const window: any; +declare const document: any; + +const ledgerWallet = new LedgerWallet({ testModeAllowed: true }); + +window.getAccounts = async function getAccounts(): Promise { + const addressInput = document.getElementById("address"); + const accountsDiv = document.getElementById("accounts"); + accountsDiv.textContent = "Loading..."; + + try { + const accounts = await ledgerWallet.getAccounts(); + const prettyAccounts = accounts.map((account) => ({ ...account, pubkey: toHex(account.pubkey) })); + accountsDiv.textContent = JSON.stringify(prettyAccounts, null, "\t"); + addressInput.value = accounts[0].address; + } catch (error) { + accountsDiv.textContent = error; + } +}; + +window.sign = async function sign(): Promise { + const signatureDiv = document.getElementById("signature"); + signatureDiv.textContent = "Loading..."; + + try { + const address = document.getElementById("address").value; + const rawMessage = document.getElementById("message").textContent; + const message = JSON.stringify(JSON.parse(rawMessage)); + const signature = await ledgerWallet.sign(address, toUtf8(message)); + signatureDiv.textContent = JSON.stringify(signature, null, "\t"); + } catch (error) { + signatureDiv.textContent = error; + } +}; diff --git a/packages/launchpad-ledger/src/ledgerwallet.ts b/packages/launchpad-ledger/src/ledgerwallet.ts new file mode 100644 index 00000000..55e0ae13 --- /dev/null +++ b/packages/launchpad-ledger/src/ledgerwallet.ts @@ -0,0 +1,45 @@ +import { AccountData, encodeSecp256k1Signature, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; + +import { LaunchpadLedger } from "./launchpadledger"; + +interface LedgerWalletOptions { + readonly testModeAllowed: boolean; +} + +export class LedgerWallet implements OfflineSigner { + private readonly ledger: LaunchpadLedger; + private address: string | undefined; + private pubkey: Uint8Array | undefined; + + constructor(options?: LedgerWalletOptions) { + this.ledger = new LaunchpadLedger(options); + } + + public async getAccounts(): Promise { + await this.ledger.connect(); + + const address = (this.address = this.address || (await this.ledger.getCosmosAddress())); + const pubkey = (this.pubkey = this.pubkey || (await this.ledger.getPubKey())); + + return [ + { + algo: "secp256k1", + address: address, + pubkey: pubkey, + }, + ]; + } + + public async sign(address: string, message: Uint8Array): Promise { + await this.ledger.connect(); + + const thisAddress = (this.address = this.address || (await this.ledger.getCosmosAddress())); + if (address !== thisAddress) { + throw new Error(`Address ${address} not found in wallet`); + } + + const signature = await this.ledger.sign(message); + const pubkey = (this.pubkey = this.pubkey || (await this.ledger.getPubKey())); + return encodeSecp256k1Signature(pubkey, signature); + } +} diff --git a/packages/launchpad-ledger/types/index.d.ts b/packages/launchpad-ledger/types/index.d.ts index 0ca77769..f0148827 100644 --- a/packages/launchpad-ledger/types/index.d.ts +++ b/packages/launchpad-ledger/types/index.d.ts @@ -1 +1,2 @@ export { LaunchpadLedger } from "./launchpadledger"; +export { LedgerWallet } from "./ledgerwallet"; diff --git a/packages/launchpad-ledger/types/ledger.demo.d.ts b/packages/launchpad-ledger/types/ledger.demo.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/packages/launchpad-ledger/types/ledger.demo.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/launchpad-ledger/types/ledgerwallet.d.ts b/packages/launchpad-ledger/types/ledgerwallet.d.ts new file mode 100644 index 00000000..f47f48b2 --- /dev/null +++ b/packages/launchpad-ledger/types/ledgerwallet.d.ts @@ -0,0 +1,13 @@ +import { AccountData, OfflineSigner, StdSignature } from "@cosmjs/launchpad"; +interface LedgerWalletOptions { + readonly testModeAllowed: boolean; +} +export declare class LedgerWallet implements OfflineSigner { + private readonly ledger; + private address; + private pubkey; + constructor(options?: LedgerWalletOptions); + getAccounts(): Promise; + sign(address: string, message: Uint8Array): Promise; +} +export {}; diff --git a/packages/launchpad-ledger/webpack.web.config.js b/packages/launchpad-ledger/webpack.web.config.js index 7373cace..fe7d59e8 100644 --- a/packages/launchpad-ledger/webpack.web.config.js +++ b/packages/launchpad-ledger/webpack.web.config.js @@ -1,19 +1,29 @@ const glob = require("glob"); const path = require("path"); -const webpack = require("webpack"); +// const webpack = require("webpack"); const target = "web"; -const distdir = path.join(__dirname, "dist", "web"); +// const distdir = path.join(__dirname, "dist", "web"); +const demodir = path.join(__dirname, "dist", "demo"); module.exports = [ + // { + // // bundle used for Karma tests + // target: target, + // entry: glob.sync("./build/**/*.spec.js"), + // output: { + // path: distdir, + // filename: "tests.js", + // }, + // plugins: [new webpack.EnvironmentPlugin(["WASMD_ENABLED"])], + // }, { - // bundle used for Karma tests + // bundle used for Ledger demo target: target, - entry: glob.sync("./build/**/*.spec.js"), + entry: glob.sync("./build/**/*.demo.js"), output: { - path: distdir, - filename: "tests.js", + path: demodir, + filename: "ledger.js", }, - plugins: [new webpack.EnvironmentPlugin(["WASMD_ENABLED"])], }, ];