From 9632c43020384045e1dc070e34fe96f861562c1e Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 7 Feb 2024 10:41:28 +0800 Subject: [PATCH 1/5] feat: add bitget wallet --- src/wallet/constants/WalletName.ts | 1 + src/wallet/index.ts | 1 + src/wallet/wallets/bitget/BitgetController.ts | 77 +++++++++++++ src/wallet/wallets/bitget/BitgetExtension.ts | 109 ++++++++++++++++++ src/wallet/wallets/bitget/types.ts | 12 ++ src/wallet/wallets/window.d.ts | 4 +- 6 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/wallet/wallets/bitget/BitgetController.ts create mode 100644 src/wallet/wallets/bitget/BitgetExtension.ts create mode 100644 src/wallet/wallets/bitget/types.ts diff --git a/src/wallet/constants/WalletName.ts b/src/wallet/constants/WalletName.ts index 32079185..c1801d00 100644 --- a/src/wallet/constants/WalletName.ts +++ b/src/wallet/constants/WalletName.ts @@ -2,6 +2,7 @@ * The unique identifier of the wallet. */ export const WalletName = { + BITGET: "bitget", STATION: "station", KEPLR: "keplr", LEAP: "leap", diff --git a/src/wallet/index.ts b/src/wallet/index.ts index f891a640..c29e750e 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -13,6 +13,7 @@ export { type ChainInfo, type EventCallback, } from "./wallets/WalletController"; +export { BitgetController } from "./wallets/bitget/BitgetController"; export { CompassController } from "./wallets/compass/CompassController"; export { CosmostationController } from "./wallets/cosmostation/CosmostationController"; export { KeplrController } from "./wallets/keplr/KeplrController"; diff --git a/src/wallet/wallets/bitget/BitgetController.ts b/src/wallet/wallets/bitget/BitgetController.ts new file mode 100644 index 00000000..740d7a21 --- /dev/null +++ b/src/wallet/wallets/bitget/BitgetController.ts @@ -0,0 +1,77 @@ +import { Secp256k1PubKey } from "cosmes/client"; +import { base64 } from "cosmes/codec"; + +import { WalletName } from "../../constants/WalletName"; +import { WalletType } from "../../constants/WalletType"; +import { onWindowEvent } from "../../utils/window"; +import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2"; +import { ConnectedWallet } from "../ConnectedWallet"; +import { ChainInfo, WalletController } from "../WalletController"; +import { BitgetExtension } from "./BitgetExtension"; +// import { BitgetWalletConnectV2 } from "./BitgetWalletConnectV2"; + +export class BitgetController extends WalletController { + // private readonly wc: WalletConnectV2; + + constructor(wcProjectId: string) { + super(WalletName.BITGET); + // this.wc = new WalletConnectV2(wcProjectId, { + // // https://github.com/chainapsis/keplr-wallet/blob/master/packages/wc-qrcode-modal/src/modal.tsx#L61-L75 + // name: "Bitget", + // android: + // "intent://wcV2#Intent;package=com.chainapsis.keplr;scheme=keplrwallet;end;", + // ios: "keplrwallet://wcV2", + // }); + this.registerAccountChangeHandlers(); + } + + public async isInstalled(type: WalletType) { + return type === WalletType.EXTENSION ? "bitkeep" in window : true; + } + + protected async connectWalletConnect( + _chains: ChainInfo[] + ): Promise<{ + wallets: Map; + wc: WalletConnectV2; + }> { + // Compass does not support WC yet + throw new Error("WalletConnect not supported"); + } + + protected async connectExtension(chains: ChainInfo[]) { + const wallets = new Map(); + const ext = window.bitkeep && window.bitkeep.keplr; + if (!ext) { + throw new Error("Bitget extension is not installed"); + } + await ext.enable(chains.map(({ chainId }) => chainId)); + for (const { chainId, rpc, gasPrice } of Object.values(chains)) { + const { bech32Address, pubKey, isNanoLedger } = await ext.getKey(chainId); + const key = new Secp256k1PubKey({ + key: pubKey, + }); + wallets.set( + chainId, + new BitgetExtension( + this.id, + ext, + chainId, + key, + bech32Address, + rpc, + gasPrice, + isNanoLedger + ) + ); + } + return wallets; + } + + protected registerAccountChangeHandlers() { + onWindowEvent("keplr_keystorechange", () => + this.changeAccount(WalletType.EXTENSION) + ); + // this.wc.onAccountChange(() => this.changeAccount(WalletType.WALLETCONNECT)); + } +} diff --git a/src/wallet/wallets/bitget/BitgetExtension.ts b/src/wallet/wallets/bitget/BitgetExtension.ts new file mode 100644 index 00000000..8b0f4bb1 --- /dev/null +++ b/src/wallet/wallets/bitget/BitgetExtension.ts @@ -0,0 +1,109 @@ +import { PlainMessage } from "@bufbuild/protobuf"; +import { + Secp256k1PubKey, + ToSignDocParams, + ToStdSignDocParams, + Tx, +} from "cosmes/client"; +import { base16 } from "cosmes/codec"; +import { + CosmosBaseV1beta1Coin as Coin, + CosmosTxV1beta1Fee as Fee, + CosmosTxV1beta1TxRaw as TxRaw, +} from "cosmes/protobufs"; +import type { BroadcastMode, Keplr } from "cosmes/registry"; + +import { WalletName } from "../../constants/WalletName"; +import { WalletType } from "../../constants/WalletType"; +import { + ConnectedWallet, + SignArbitraryResponse, + UnsignedTx, +} from "../ConnectedWallet"; + +export class BitgetExtension extends ConnectedWallet { + private readonly ext: Keplr; + private readonly useAmino: boolean; + + constructor( + walletName: WalletName, + ext: Keplr, + chainId: string, + pubKey: Secp256k1PubKey, + address: string, + rpc: string, + gasPrice: PlainMessage, + useAmino: boolean + ) { + super( + walletName, + WalletType.EXTENSION, + chainId, + pubKey, + address, + rpc, + gasPrice + ); + this.ext = ext; + this.ext.defaultOptions = { + sign: { + preferNoSetFee: true, + preferNoSetMemo: true, + }, + }; + this.useAmino = useAmino; + } + + public async signArbitrary(data: string): Promise { + const res = await this.ext.signArbitrary(this.chainId, this.address, data); + return { + data, + pubKey: res.pub_key.value, + signature: res.signature, + }; + } + + protected async signAndBroadcastTx( + { msgs, memo, timeoutHeight }: UnsignedTx, + fee: Fee, + accountNumber: bigint, + sequence: bigint + ): Promise { + const tx = new Tx({ + chainId: this.chainId, + pubKey: this.pubKey, + msgs: msgs, + }); + + const params: ToStdSignDocParams | ToSignDocParams = { + accountNumber, + sequence, + fee, + memo, + timeoutHeight, + }; + let txRaw: TxRaw; + if (this.useAmino) { + const { signed, signature } = await this.ext.signAmino( + this.chainId, + this.address, + tx.toStdSignDoc(params) + ); + txRaw = tx.toSignedAmino(signed, signature.signature); + } else { + const { signed, signature } = await this.ext.signDirect( + this.chainId, + this.address, + tx.toSignDoc(params) + ); + txRaw = tx.toSignedDirect(signed, signature.signature); + } + + const txHash = await this.ext.sendTx( + this.chainId, + txRaw.toBinary(), + "sync" as BroadcastMode + ); + return base16.encode(txHash); + } +} diff --git a/src/wallet/wallets/bitget/types.ts b/src/wallet/wallets/bitget/types.ts new file mode 100644 index 00000000..815ebc2b --- /dev/null +++ b/src/wallet/wallets/bitget/types.ts @@ -0,0 +1,12 @@ +import { Keplr } from "cosmes/registry"; + +// Type is similar to Keplr +export type Bitget = Keplr; + +export type Window = { + bitkeep?: + | { + keplr: Bitget + } + | undefined; +}; diff --git a/src/wallet/wallets/window.d.ts b/src/wallet/wallets/window.d.ts index 90e6d54f..f6eaf622 100644 --- a/src/wallet/wallets/window.d.ts +++ b/src/wallet/wallets/window.d.ts @@ -1,5 +1,6 @@ import { Window as KeplrWindow } from "cosmes/registry"; +import { Window as BitgetWindow } from "./bitget/types"; import { Window as CompassWindow } from "./compass/types"; import { Window as CosmostationWindow } from "./cosmostation/types"; import { Window as LeapWindow } from "./leap/types"; @@ -9,7 +10,8 @@ import { Window as StationWindow } from "./station/types"; declare global { interface Window - extends KeplrWindow, + extends BitgetWindow, + KeplrWindow, CosmostationWindow, StationWindow, LeapWindow, From c640b4c80aa449b92ae105513ee72fc432f138dd Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 7 Feb 2024 10:41:44 +0800 Subject: [PATCH 2/5] test: test connect in example --- examples/solid-vite/src/App.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/solid-vite/src/App.tsx b/examples/solid-vite/src/App.tsx index 854ec9dc..df5687c2 100644 --- a/examples/solid-vite/src/App.tsx +++ b/examples/solid-vite/src/App.tsx @@ -3,6 +3,7 @@ import { createStore } from "solid-js/store"; import { MsgSend } from "cosmes/client"; import { + BitgetController, CompassController, ConnectedWallet, CosmostationController, @@ -34,6 +35,7 @@ const CHAINS: Record = { "pacific-1": "Sei", }; const WALLETS: Record = { + [WalletName.BITGET]: "Bitget", [WalletName.KEPLR]: "Keplr", [WalletName.COSMOSTATION]: "Cosmostation", [WalletName.STATION]: "Terra Station", @@ -47,6 +49,7 @@ const TYPES: Record = { [WalletType.WALLETCONNECT]: "Wallet Connect", }; const CONTROLLERS: Record = { + [WalletName.BITGET]: new BitgetController(WC_PROJECT_ID), [WalletName.STATION]: new StationController(), [WalletName.KEPLR]: new KeplrController(WC_PROJECT_ID), [WalletName.LEAP]: new LeapController(WC_PROJECT_ID), From f6aeaf68287fd1aaea4614134d3cc60382f94070 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 7 Feb 2024 11:05:33 +0800 Subject: [PATCH 3/5] feat: mobile scheme --- src/wallet/wallets/bitget/BitgetController.ts | 50 ++++++---- .../wallets/bitget/BitgetWalletConnectV2.ts | 94 +++++++++++++++++++ 2 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 src/wallet/wallets/bitget/BitgetWalletConnectV2.ts diff --git a/src/wallet/wallets/bitget/BitgetController.ts b/src/wallet/wallets/bitget/BitgetController.ts index 740d7a21..219dabab 100644 --- a/src/wallet/wallets/bitget/BitgetController.ts +++ b/src/wallet/wallets/bitget/BitgetController.ts @@ -8,20 +8,18 @@ import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2"; import { ConnectedWallet } from "../ConnectedWallet"; import { ChainInfo, WalletController } from "../WalletController"; import { BitgetExtension } from "./BitgetExtension"; -// import { BitgetWalletConnectV2 } from "./BitgetWalletConnectV2"; +import { BitgetWalletConnectV2 } from "./BitgetWalletConnectV2"; export class BitgetController extends WalletController { - // private readonly wc: WalletConnectV2; + private readonly wc: WalletConnectV2; constructor(wcProjectId: string) { super(WalletName.BITGET); - // this.wc = new WalletConnectV2(wcProjectId, { - // // https://github.com/chainapsis/keplr-wallet/blob/master/packages/wc-qrcode-modal/src/modal.tsx#L61-L75 - // name: "Bitget", - // android: - // "intent://wcV2#Intent;package=com.chainapsis.keplr;scheme=keplrwallet;end;", - // ios: "keplrwallet://wcV2", - // }); + this.wc = new WalletConnectV2(wcProjectId, { + name: "Bitget", + android: "https://bkcode.vip?", + ios: "bitkeep://bkconnect?", + }); this.registerAccountChangeHandlers(); } @@ -30,13 +28,31 @@ export class BitgetController extends WalletController { } protected async connectWalletConnect( - _chains: ChainInfo[] - ): Promise<{ - wallets: Map; - wc: WalletConnectV2; - }> { - // Compass does not support WC yet - throw new Error("WalletConnect not supported"); + chains: ChainInfo[] + ) { + const wallets = new Map(); + await this.wc.connect(chains.map(({ chainId }) => chainId)); + for (let i = 0; i < chains.length; i++) { + const { chainId, rpc, gasPrice } = chains[i]; + const { pubkey, address } = await this.wc.getAccount(chainId); + const key = new Secp256k1PubKey({ + key: base64.decode(pubkey), + }); + wallets.set( + chainId, + new BitgetWalletConnectV2( + this.id, + this.wc, + chainId, + key, + address, + rpc, + gasPrice, + true // TODO: use sign mode direct when supported + ) + ); + } + return { wallets, wc: this.wc }; } protected async connectExtension(chains: ChainInfo[]) { @@ -72,6 +88,6 @@ export class BitgetController extends WalletController { onWindowEvent("keplr_keystorechange", () => this.changeAccount(WalletType.EXTENSION) ); - // this.wc.onAccountChange(() => this.changeAccount(WalletType.WALLETCONNECT)); + this.wc.onAccountChange(() => this.changeAccount(WalletType.WALLETCONNECT)); } } diff --git a/src/wallet/wallets/bitget/BitgetWalletConnectV2.ts b/src/wallet/wallets/bitget/BitgetWalletConnectV2.ts new file mode 100644 index 00000000..c3991fd1 --- /dev/null +++ b/src/wallet/wallets/bitget/BitgetWalletConnectV2.ts @@ -0,0 +1,94 @@ +import { PlainMessage } from "@bufbuild/protobuf"; +import { + RpcClient, + Secp256k1PubKey, + ToSignDocParams, + ToStdSignDocParams, + Tx, +} from "cosmes/client"; +import { + CosmosBaseV1beta1Coin as Coin, + CosmosTxV1beta1Fee as Fee, + CosmosTxV1beta1TxRaw as TxRaw, +} from "cosmes/protobufs"; +import { WalletName, WalletType } from "cosmes/wallet"; + +import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2"; +import { + ConnectedWallet, + SignArbitraryResponse, + UnsignedTx, +} from "../ConnectedWallet"; + +export class BitgetWalletConnectV2 extends ConnectedWallet { + private readonly wc: WalletConnectV2; + private readonly useAmino: boolean; + + constructor( + walletName: WalletName, + wc: WalletConnectV2, + chainId: string, + pubKey: Secp256k1PubKey, + address: string, + rpc: string, + gasPrice: PlainMessage, + useAmino: boolean + ) { + super( + walletName, + WalletType.WALLETCONNECT, + chainId, + pubKey, + address, + rpc, + gasPrice + ); + this.wc = wc; + this.useAmino = useAmino; + } + + public async signArbitrary(_data: string): Promise { + // ! Not implemented by Bitget + throw new Error("Method not implemented."); + } + + public async signAndBroadcastTx( + { msgs, memo, timeoutHeight }: UnsignedTx, + fee: Fee, + accountNumber: bigint, + sequence: bigint + ): Promise { + const tx = new Tx({ + chainId: this.chainId, + pubKey: this.pubKey, + msgs: msgs, + }); + + const params: ToStdSignDocParams | ToSignDocParams = { + accountNumber, + sequence, + fee, + memo, + timeoutHeight, + }; + let txRaw: TxRaw; + if (this.useAmino) { + const { signed, signature } = await this.wc.signAmino( + this.chainId, + this.address, + tx.toStdSignDoc(params) + ); + txRaw = tx.toSignedAmino(signed, signature.signature); + } else { + const { signed, signature } = await this.wc.signDirect( + this.chainId, + this.address, + tx.toSignDoc(params) + ); + txRaw = tx.toSignedDirect(signed, signature.signature); + } + + // Since `sendTx` on WC isn't implemented yet, we have to broadcast manually + return RpcClient.broadcastTx(this.rpc, txRaw); + } +} From 5f8aab8519641dd627daddbf1ec8c46f26c79c92 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 7 Feb 2024 11:05:43 +0800 Subject: [PATCH 4/5] feat: mobile scheme --- src/wallet/wallets/bitget/BitgetController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallets/bitget/BitgetController.ts b/src/wallet/wallets/bitget/BitgetController.ts index 219dabab..07432e1b 100644 --- a/src/wallet/wallets/bitget/BitgetController.ts +++ b/src/wallet/wallets/bitget/BitgetController.ts @@ -17,8 +17,8 @@ export class BitgetController extends WalletController { super(WalletName.BITGET); this.wc = new WalletConnectV2(wcProjectId, { name: "Bitget", - android: "https://bkcode.vip?", - ios: "bitkeep://bkconnect?", + android: "https://bkcode.vip", + ios: "bitkeep://bkconnect", }); this.registerAccountChangeHandlers(); } From 65e27856d797b1147e7daf002847bbbdee530603 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 7 Feb 2024 11:05:56 +0800 Subject: [PATCH 5/5] docs: soupport info --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 451df2ea..031c0ec9 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ This directory is a [Cosmos Kit](https://cosmoskit.com) alternative to interact **Wallets supported**: +- [Bitget](https://web3.bitget.com/) (for Sei only) - [Station](https://docs.terra.money/learn/station/) - [Keplr](https://www.keplr.app/) - [Leap](https://www.leapwallet.io/)