From e288fbab698a7ba121d27a09bcc95d2435609de7 Mon Sep 17 00:00:00 2001 From: echel0n-HX Date: Tue, 23 Apr 2024 09:18:46 -0700 Subject: [PATCH 1/8] Added Galaxy Station wallet --- examples/solid-vite/src/App.tsx | 3 + src/wallet/constants/WalletName.ts | 1 + src/wallet/index.ts | 1 + .../galaxystation/GalaxyStationController.ts | 152 ++++++++++++++++++ .../galaxystation/GalaxyStationExtension.ts | 4 + .../GalaxyStationWalletConnectV1.ts | 99 ++++++++++++ src/wallet/wallets/galaxystation/types.ts | 89 ++++++++++ .../galaxystation/utils/toGalaxyStationTx.ts | 37 +++++ src/wallet/wallets/window.d.ts | 2 + 9 files changed, 388 insertions(+) create mode 100644 src/wallet/wallets/galaxystation/GalaxyStationController.ts create mode 100644 src/wallet/wallets/galaxystation/GalaxyStationExtension.ts create mode 100644 src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV1.ts create mode 100644 src/wallet/wallets/galaxystation/types.ts create mode 100644 src/wallet/wallets/galaxystation/utils/toGalaxyStationTx.ts diff --git a/examples/solid-vite/src/App.tsx b/examples/solid-vite/src/App.tsx index 959d3bed..4b0f562c 100644 --- a/examples/solid-vite/src/App.tsx +++ b/examples/solid-vite/src/App.tsx @@ -12,6 +12,7 @@ import { NinjiController, OWalletController, StationController, + GalaxyStationController, UnsignedTx, WalletController, WalletName, @@ -40,6 +41,7 @@ const WALLETS: Record = { [WalletName.KEPLR]: "Keplr", [WalletName.COSMOSTATION]: "Cosmostation", [WalletName.STATION]: "Terra Station", + [WalletName.GALAXYSTATION]: "Galaxy Station", [WalletName.LEAP]: "Leap", [WalletName.COMPASS]: "Compass", [WalletName.METAMASK_INJECTIVE]: "MetaMask", @@ -52,6 +54,7 @@ const TYPES: Record = { }; const CONTROLLERS: Record = { [WalletName.STATION]: new StationController(), + [WalletName.GALAXYSTATION]: new GalaxyStationController(), [WalletName.KEPLR]: new KeplrController(WC_PROJECT_ID), [WalletName.LEAP]: new LeapController(WC_PROJECT_ID), [WalletName.COMPASS]: new CompassController(), diff --git a/src/wallet/constants/WalletName.ts b/src/wallet/constants/WalletName.ts index c7ca8649..1c77ad66 100644 --- a/src/wallet/constants/WalletName.ts +++ b/src/wallet/constants/WalletName.ts @@ -3,6 +3,7 @@ */ export const WalletName = { STATION: "station", + GALAXYSTATION: "galaxystation", KEPLR: "keplr", LEAP: "leap", COMPASS: "compass", diff --git a/src/wallet/index.ts b/src/wallet/index.ts index 2c85cc66..6a3d051e 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -23,3 +23,4 @@ export { MnemonicWallet } from "./wallets/mnemonic/MnemonicWallet"; export { NinjiController } from "./wallets/ninji/NinjiController"; export { OWalletController } from "./wallets/owallet/OWalletController"; export { StationController } from "./wallets/station/StationController"; +export { GalaxyStationController } from "./wallets/galaxystation/GalaxyStationController"; diff --git a/src/wallet/wallets/galaxystation/GalaxyStationController.ts b/src/wallet/wallets/galaxystation/GalaxyStationController.ts new file mode 100644 index 00000000..bb2ef78b --- /dev/null +++ b/src/wallet/wallets/galaxystation/GalaxyStationController.ts @@ -0,0 +1,152 @@ +import { Secp256k1PubKey, getAccount, toBaseAccount } from "cosmes/client"; +import { CosmosCryptoSecp256k1PubKey } from "cosmes/protobufs"; + +import { WalletName } from "../../constants/WalletName"; +import { WalletType } from "../../constants/WalletType"; +import { onWindowEvent } from "../../utils/window"; +import { WalletConnectV1 } from "../../walletconnect/WalletConnectV1"; +import { ConnectedWallet } from "../ConnectedWallet"; +import { ChainInfo, WalletController } from "../WalletController"; +import { WalletError } from "../WalletError"; +import { GalaxyStationExtension } from "./GalaxyStationExtension"; +import { GalaxyStationWalletConnectV1 } from "./GalaxyStationWalletConnectV1"; + +const TERRA_MAINNET_CHAIN_ID = "columbus-5"; +const TERRA_TESTNET_CHAIN_ID = "octagon-1"; +const COIN_TYPE_330_CHAINS = [ + TERRA_MAINNET_CHAIN_ID, + TERRA_TESTNET_CHAIN_ID, +]; + +export class GalaxyStationController extends WalletController { + private readonly wc: WalletConnectV1; + + constructor() { + super(WalletName.GALAXYSTATION); + this.wc = new WalletConnectV1( + "cosmes.wallet.galaxyStation.wcSession", + { + name: "Galaxy Station", + android: "", + ios: "", + isStation: true, + }, + { + bridge: "https://walletconnect.terra.dev", + signingMethods: [], + } + ); + this.registerAccountChangeHandlers(); + } + + public async isInstalled(type: WalletType) { + return type === WalletType.EXTENSION ? "galaxyStation" in window : true; + } + + protected async connectWalletConnect( + chains: ChainInfo[] + ) { + for (const { chainId } of chains) { + // Galaxy Station mobile's WallectConnect only supports these chains + // TODO: update when Galaxy Station mobile supports more chains + if (COIN_TYPE_330_CHAINS.includes(chainId)) { + continue; + } + throw new Error(`${chainId} not supported`); + } + const wallets = new Map(); + const wc = await WalletError.wrap(this.wc.connect()); + // Galaxy Station mobile only returns 1 address for now + // TODO: update when Galaxy Station mobile supports more chains + const address = wc.accounts[0]; + for (let i = 0; i < chains.length; i++) { + const { chainId, rpc, gasPrice } = chains[i]; + try { + // Since Galaxy Station's WalletConnect doesn't support getting pub keys, we + // need to query the account to get it. However, if the wallet does + // not contain funds, the RPC will throw errors. + const key = await WalletError.wrap( + this.getPubKey(chainId, rpc, address) + ); + wallets.set( + chainId, + new GalaxyStationWalletConnectV1(wc, chainId, key, address, rpc, gasPrice) + ); + } catch (err) { + // We simply log and ignore the error for now + console.warn(err); + } + } + this.wc.cacheSession(wc); + return { wallets, wc: this.wc }; + } + + protected async connectExtension(chains: ChainInfo[]) { + const wallets = new Map(); + const ext = window.galaxyStation?.keplr; + if (!ext) { + throw new Error("Galaxy Station extension is not installed"); + } + // This method never throws on Galaxy Station + await WalletError.wrap(ext.enable(chains.map(({ chainId }) => chainId))); + for (const { chainId, rpc, gasPrice } of Object.values(chains)) { + try { + const { bech32Address, pubKey, isNanoLedger } = await WalletError.wrap( + ext.getKey(chainId) + ); + const key = new Secp256k1PubKey({ + key: pubKey, + chainId, + }); + wallets.set( + chainId, + new GalaxyStationExtension( + this.id, + ext, + chainId, + key, + bech32Address, + rpc, + gasPrice, + isNanoLedger + ) + ); + } catch (err) { + if (err instanceof Error) { + // The `getKey` method throws if the chain is not supported + console.warn(`Failed to get public key for ${chainId}`, err); + continue; + } + throw err; // Rethrow other stuff + } + } + return wallets; + } + + protected registerAccountChangeHandlers() { + onWindowEvent("galaxy_station_wallet_change", () => + this.changeAccount(WalletType.EXTENSION) + ); + onWindowEvent("galaxy_station_network_change", () => + this.changeAccount(WalletType.EXTENSION) + ); + // Galaxy Station's WalletConnect v1 doesn't support account change events + } + + private async getPubKey( + chainId: string, + rpc: string, + address: string + ): Promise { + const account = await getAccount(rpc, { address }); + const { pubKey } = toBaseAccount(account); + if (!pubKey) { + throw new Error("Unable to get pub key"); + } + // TODO: handle other key types (?) + return new Secp256k1PubKey({ + chainId, + key: CosmosCryptoSecp256k1PubKey.fromBinary(pubKey.value).key, + }); + } +} diff --git a/src/wallet/wallets/galaxystation/GalaxyStationExtension.ts b/src/wallet/wallets/galaxystation/GalaxyStationExtension.ts new file mode 100644 index 00000000..c72226f2 --- /dev/null +++ b/src/wallet/wallets/galaxystation/GalaxyStationExtension.ts @@ -0,0 +1,4 @@ +import { KeplrExtension } from "../keplr/KeplrExtension"; + +// Station's API is similar to Keplr. +export const GalaxyStationExtension = KeplrExtension; diff --git a/src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV1.ts b/src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV1.ts new file mode 100644 index 00000000..f688760a --- /dev/null +++ b/src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV1.ts @@ -0,0 +1,99 @@ +import { PlainMessage } from "@bufbuild/protobuf"; +import WalletConnect from "@walletconnect/legacy-client"; +import { Secp256k1PubKey } from "cosmes/client"; +import { base64, utf8 } from "cosmes/codec"; +import { + CosmosBaseV1beta1Coin as Coin, + CosmosTxV1beta1Fee as Fee, +} from "cosmes/protobufs"; + +import { WalletName } from "../../constants/WalletName"; +import { WalletType } from "../../constants/WalletType"; +import { isMobile } from "../../utils/os"; +import { + ConnectedWallet, + SignArbitraryResponse, + UnsignedTx, +} from "../ConnectedWallet"; +import { WalletError } from "../WalletError"; +import { PostResponse, SignBytesResponse } from "./types"; +import { toGalaxyStationTx } from "./utils/toGalaxyStationTx"; + +export class GalaxyStationWalletConnectV1 extends ConnectedWallet { + private readonly wc: WalletConnect; + + constructor( + wc: WalletConnect, + chainId: string, + pubKey: Secp256k1PubKey, + address: string, + rpc: string, + gasPrice: PlainMessage + ) { + super( + WalletName.GALAXYSTATION, + WalletType.WALLETCONNECT, + chainId, + pubKey, + address, + rpc, + gasPrice + ); + this.wc = wc; + } + + public async signArbitrary(data: string): Promise { + const res = await this.sendRequest( + "signBytes", + base64.encode(utf8.decode(data)) + ); + return { + data, + pubKey: res.public_key, + signature: res.signature, + }; + } + + public async signAndBroadcastTx( + { msgs, memo }: UnsignedTx, + fee: Fee + ): Promise { + // Signing a tx without posting it isn't supported + const { txhash } = await this.sendRequest( + "post", + toGalaxyStationTx(this.chainId, fee, msgs, memo) + ); + return txhash; + } + + private async sendRequest(method: string, params: unknown): Promise { + const id = Date.now(); + if (isMobile()) { + const payload = base64.encode( + utf8.decode( + JSON.stringify({ + id, + handshakeTopic: this.wc.handshakeTopic, + method, + params, + }) + ) + ); + window.location.href = `galaxystation://walletconnect_confirm/?action=walletconnect_confirm&payload=${payload}`; + } + try { + return await this.wc.sendCustomRequest({ + id, + method, + params: [params], + }); + } catch (err) { + if (err instanceof Error) { + // Error messages are JSON stringified (eg. '{"code":1,"message":"Denied by user"}') + const { message } = JSON.parse(err.message); + throw new WalletError(message, err); + } + throw new WalletError("unknown error", err); + } + } +} diff --git a/src/wallet/wallets/galaxystation/types.ts b/src/wallet/wallets/galaxystation/types.ts new file mode 100644 index 00000000..2d20ad86 --- /dev/null +++ b/src/wallet/wallets/galaxystation/types.ts @@ -0,0 +1,89 @@ +import { Keplr } from "cosmes/registry"; + +export type Window = { + galaxyStation?: GalaxyStation | undefined; +}; + +/** + * A subset of the Galaxy Station extension API that is injected into the `window` object. + * + */ +export type GalaxyStation = { + keplr?: Keplr | undefined; + connect: () => Promise; + getPublicKey: () => Promise; + signBytes(bytes: string, purgeQueue?: boolean): Promise; + post: (tx: GalaxyStationTx, purgeQueue?: boolean) => Promise; + sign: (tx: GalaxyStationTx, purgeQueue?: boolean) => Promise; +}; + +export type GalaxyStationTx = { + chainID: string; + msgs: string[]; + fee?: string; + memo?: string; +}; + +export type ConnectResponse = { + addresses: Record; + /** + * Maps the coin type to the base64 encoded public key. + * Is `undefined` for legacy versions of the extension. + */ + pubkey?: + | { + "60": string; + "118": string; + "330": string; + } + | undefined; +}; + +export type GetPubKeyResponse = { + addresses: Record; + /** + * Maps the coin type to the base64 encoded public key. + * Is `undefined` for legacy versions of the extension. + */ + pubkey?: + | { + "118": string; + "330": string; + } + | undefined; +}; + +export type SignBytesResponse = { + public_key: string; + signature: string; + recid: number; +}; + +export type PostResponse = { + code?: number | undefined; + raw_log: string; + txhash: string; +}; + +// Unnecessary fields are omitted for brevity +export type SignResponse = { + auth_info: { + fee: { + amount: { + amount: string; + denom: string; + }[]; + gas_limit: string; + granter: string; + payer: string; + }; + signer_infos: { + mode_info: { + single: { + mode: string; + }; + }; + }[]; + }; + signatures: string[]; +}; diff --git a/src/wallet/wallets/galaxystation/utils/toGalaxyStationTx.ts b/src/wallet/wallets/galaxystation/utils/toGalaxyStationTx.ts new file mode 100644 index 00000000..a2d90eed --- /dev/null +++ b/src/wallet/wallets/galaxystation/utils/toGalaxyStationTx.ts @@ -0,0 +1,37 @@ +import { Adapter } from "cosmes/client"; +import { CosmosTxV1beta1Fee as Fee } from "cosmes/protobufs"; + +import { GalaxyStationTx } from "../types"; + +/** + * Translates the given args to a tx that can be sent to either + * the Galaxy Station extension wallet or WalletConnect wallet. + */ +export function toGalaxyStationTx( + chainId: string, + fee: Fee, + msgs: Adapter[], + memo?: string | undefined +): GalaxyStationTx { + return { + chainID: chainId, + fee: toGalaxyStationFee(fee), + msgs: msgs.map(toGalaxyStationMsg), + memo: memo, + }; +} + +function toGalaxyStationFee({ amount, gasLimit }: Fee): string { + return JSON.stringify({ + amount, + gas_limit: gasLimit.toString(), + }); +} + +function toGalaxyStationMsg(msg: Adapter): string { + const { value } = msg.toAmino(); + return JSON.stringify({ + "@type": "/" + msg.toProto().getType().typeName, + ...value, + }); +} diff --git a/src/wallet/wallets/window.d.ts b/src/wallet/wallets/window.d.ts index 783b9e8a..19cbd7cd 100644 --- a/src/wallet/wallets/window.d.ts +++ b/src/wallet/wallets/window.d.ts @@ -7,12 +7,14 @@ import { Window as EthereumWindow } from "./metamask-injective/types"; import { Window as NinjiWindow } from "./ninji/types"; import { Window as OWalletWindow } from "./owallet/types"; import { Window as StationWindow } from "./station/types"; +import { Window as GalaxyStationWindow } from "./galaxystation/types"; declare global { interface Window extends KeplrWindow, CosmostationWindow, StationWindow, + GalaxyStationWindow, LeapWindow, CompassWindow, EthereumWindow, From 1e0fd206a4fbab8fedaf8b43fb118b59e845a7d4 Mon Sep 17 00:00:00 2001 From: echel0n-HX Date: Tue, 23 Apr 2024 10:53:02 -0700 Subject: [PATCH 2/8] updated README.md --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index a5e1dbc9..419d1397 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A tree-shakeable, framework agnostic, [pure ESM](https://gist.github.com/sindres - [Using with TypeScript](#using-with-typescript) - [Using with Vite](#using-with-vite) - [Using Station wallet](#using-station-wallet) + - [Using Galaxy Station wallet](#using-galaxy-station-wallet) - [Examples](#examples) - [Modules](#modules) - [`cosmes/client`](#cosmesclient) @@ -92,6 +93,26 @@ See [`examples/solid-vite`](./examples/solid-vite) for a working example. > This can be removed once support for WalletConnect v1 is no longer required. +### Using Galaxy Station wallet + +The Galaxy Station wallet currently relies on WalletConnect v1. If you want to import and use `GalaxyStationController`, a polyfill for `Buffer` is required: + +```ts +// First, install the buffer package +npm install buffer + +// Then, create a new file 'polyfill.ts' +import { Buffer } from "buffer"; +(window as any).Buffer = Buffer; + +// Finally, import the above file in your entry file +import "./polyfill"; +``` + +See [`examples/solid-vite`](./examples/solid-vite) for a working example. + +> This can be removed once support for WalletConnect v1 is no longer required. + ## Examples Docs do not exist yet - see the [`examples`](./examples) folder for various working examples: @@ -128,6 +149,7 @@ This directory is a [Cosmos Kit](https://cosmoskit.com) alternative to interact **Wallets supported**: - [Station](https://docs.terra.money/learn/station/) +- [Galaxy Station](https://www.hexxagon.io) - [Keplr](https://www.keplr.app/) - [Leap](https://www.leapwallet.io/) - [Cosmostation](https://wallet.cosmostation.io/) From d7cc1a775273f88b67ffe8f8efdc29ac5a8cc47c Mon Sep 17 00:00:00 2001 From: echel0n-HX Date: Tue, 23 Apr 2024 22:01:30 -0700 Subject: [PATCH 3/8] updated Galaxy Station dashboard URL in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 419d1397..c0545fa5 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ This directory is a [Cosmos Kit](https://cosmoskit.com) alternative to interact **Wallets supported**: - [Station](https://docs.terra.money/learn/station/) -- [Galaxy Station](https://www.hexxagon.io) +- [Galaxy Station](https://station.hexxagon.io) - [Keplr](https://www.keplr.app/) - [Leap](https://www.leapwallet.io/) - [Cosmostation](https://wallet.cosmostation.io/) From eb22ebf7c8df79e9ca739e8f5fc6824738d5028c Mon Sep 17 00:00:00 2001 From: echel0n-HX Date: Sun, 5 May 2024 23:00:09 -0700 Subject: [PATCH 4/8] updated package.json for examples, replaced link with file --- examples/batch-query/package.json | 2 +- examples/batch-query/pnpm-lock.yaml | 4 ++-- examples/mnemonic-wallet/package.json | 2 +- examples/solid-vite/package.json | 2 +- examples/verify-signatures/package.json | 2 +- package.json | 3 +++ 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/batch-query/package.json b/examples/batch-query/package.json index c7f8f47f..1fb3e894 100644 --- a/examples/batch-query/package.json +++ b/examples/batch-query/package.json @@ -6,7 +6,7 @@ "start": "tsx src/index.ts" }, "dependencies": { - "cosmes": "link:../.." + "cosmes": "file:../.." }, "devDependencies": { "@types/node": "^20.2.0", diff --git a/examples/batch-query/pnpm-lock.yaml b/examples/batch-query/pnpm-lock.yaml index 36a25e66..77d46d60 100644 --- a/examples/batch-query/pnpm-lock.yaml +++ b/examples/batch-query/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: cosmes: - specifier: link:../.. - version: link:../.. + specifier: file:../.. + version: file:../.. devDependencies: '@types/node': diff --git a/examples/mnemonic-wallet/package.json b/examples/mnemonic-wallet/package.json index 972008e3..5ed0f6c7 100644 --- a/examples/mnemonic-wallet/package.json +++ b/examples/mnemonic-wallet/package.json @@ -6,7 +6,7 @@ "start": "tsx src/index.ts" }, "dependencies": { - "cosmes": "link:../.." + "cosmes": "file:../.." }, "devDependencies": { "@types/node": "^20.2.0", diff --git a/examples/solid-vite/package.json b/examples/solid-vite/package.json index ff430ccd..898b5187 100644 --- a/examples/solid-vite/package.json +++ b/examples/solid-vite/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "buffer": "^6.0.3", - "cosmes": "link:../..", + "cosmes": "file:../..", "solid-js": "^1.7.3" }, "devDependencies": { diff --git a/examples/verify-signatures/package.json b/examples/verify-signatures/package.json index d2fd693a..8af3b063 100644 --- a/examples/verify-signatures/package.json +++ b/examples/verify-signatures/package.json @@ -6,7 +6,7 @@ "start": "tsx src/index.ts" }, "dependencies": { - "cosmes": "link:../.." + "cosmes": "file:../.." }, "devDependencies": { "@types/node": "^20.2.0", diff --git a/package.json b/package.json index d16182fe..e1f7a51c 100644 --- a/package.json +++ b/package.json @@ -75,5 +75,8 @@ "tsx": "^3.12.7", "typescript": "^5.0.4", "vitest": "^0.31.0" + }, + "dependencies": { + "pnpm": "^8.3.0" } } From 1892e382b2a8bbca18ecc92f01263c11c9dcef88 Mon Sep 17 00:00:00 2001 From: echel0n-HX Date: Sun, 5 May 2024 23:26:55 -0700 Subject: [PATCH 5/8] updated COIN_TYPE_330_CHAINS --- .../wallets/galaxystation/GalaxyStationController.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wallet/wallets/galaxystation/GalaxyStationController.ts b/src/wallet/wallets/galaxystation/GalaxyStationController.ts index bb2ef78b..99fcc6af 100644 --- a/src/wallet/wallets/galaxystation/GalaxyStationController.ts +++ b/src/wallet/wallets/galaxystation/GalaxyStationController.ts @@ -11,11 +11,11 @@ import { WalletError } from "../WalletError"; import { GalaxyStationExtension } from "./GalaxyStationExtension"; import { GalaxyStationWalletConnectV1 } from "./GalaxyStationWalletConnectV1"; -const TERRA_MAINNET_CHAIN_ID = "columbus-5"; -const TERRA_TESTNET_CHAIN_ID = "octagon-1"; const COIN_TYPE_330_CHAINS = [ - TERRA_MAINNET_CHAIN_ID, - TERRA_TESTNET_CHAIN_ID, + "columbus-5", + "phoenix-1", + "octagon-1", + "pisco-1", ]; export class GalaxyStationController extends WalletController { From c543cc6a4fdbb68f7891bb62d01b9d56c54eba9c Mon Sep 17 00:00:00 2001 From: echel0n Date: Sun, 19 Jan 2025 16:49:03 -0800 Subject: [PATCH 6/8] Added Galaxy Station mobile support --- examples/solid-vite/src/App.tsx | 2 +- .../galaxystation/GalaxyStationController.ts | 83 +++++++--------- .../GalaxyStationWalletConnectV1.ts | 99 ------------------- .../GalaxyStationWalletConnectV2.ts | 4 + 4 files changed, 42 insertions(+), 146 deletions(-) delete mode 100644 src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV1.ts create mode 100644 src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV2.ts diff --git a/examples/solid-vite/src/App.tsx b/examples/solid-vite/src/App.tsx index 9f636693..c46e3434 100644 --- a/examples/solid-vite/src/App.tsx +++ b/examples/solid-vite/src/App.tsx @@ -54,7 +54,7 @@ const TYPES: Record = { }; const CONTROLLERS: Record = { [WalletName.STATION]: new StationController(), - [WalletName.GALAXYSTATION]: new GalaxyStationController(), + [WalletName.GALAXYSTATION]: new GalaxyStationController(WC_PROJECT_ID), [WalletName.KEPLR]: new KeplrController(WC_PROJECT_ID), [WalletName.LEAP]: new LeapController(WC_PROJECT_ID), [WalletName.COMPASS]: new CompassController(), diff --git a/src/wallet/wallets/galaxystation/GalaxyStationController.ts b/src/wallet/wallets/galaxystation/GalaxyStationController.ts index 99fcc6af..80750bc6 100644 --- a/src/wallet/wallets/galaxystation/GalaxyStationController.ts +++ b/src/wallet/wallets/galaxystation/GalaxyStationController.ts @@ -1,15 +1,16 @@ import { Secp256k1PubKey, getAccount, toBaseAccount } from "cosmes/client"; import { CosmosCryptoSecp256k1PubKey } from "cosmes/protobufs"; +import { base64 } from "cosmes/codec"; import { WalletName } from "../../constants/WalletName"; import { WalletType } from "../../constants/WalletType"; import { onWindowEvent } from "../../utils/window"; -import { WalletConnectV1 } from "../../walletconnect/WalletConnectV1"; +import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2"; import { ConnectedWallet } from "../ConnectedWallet"; import { ChainInfo, WalletController } from "../WalletController"; import { WalletError } from "../WalletError"; import { GalaxyStationExtension } from "./GalaxyStationExtension"; -import { GalaxyStationWalletConnectV1 } from "./GalaxyStationWalletConnectV1"; +import { GalaxyStationWalletConnectV2 } from "./GalaxyStationWalletConnectV2"; const COIN_TYPE_330_CHAINS = [ "columbus-5", @@ -19,23 +20,16 @@ const COIN_TYPE_330_CHAINS = [ ]; export class GalaxyStationController extends WalletController { - private readonly wc: WalletConnectV1; + private readonly wc: WalletConnectV2; - constructor() { + constructor(wcProjectId: string) { super(WalletName.GALAXYSTATION); - this.wc = new WalletConnectV1( - "cosmes.wallet.galaxyStation.wcSession", - { - name: "Galaxy Station", - android: "", - ios: "", - isStation: true, - }, - { - bridge: "https://walletconnect.terra.dev", - signingMethods: [], - } - ); + this.wc = new WalletConnectV2(wcProjectId, { + name: "Galaxy Station", + android: + "galaxystation://wcV2#Intent;package=io.hexxagon.station;scheme=galaxystation;end;", + ios: "galaxystation://wcV2", + }); this.registerAccountChangeHandlers(); } @@ -46,38 +40,34 @@ export class GalaxyStationController extends WalletController { protected async connectWalletConnect( chains: ChainInfo[] ) { - for (const { chainId } of chains) { - // Galaxy Station mobile's WallectConnect only supports these chains - // TODO: update when Galaxy Station mobile supports more chains - if (COIN_TYPE_330_CHAINS.includes(chainId)) { - continue; - } - throw new Error(`${chainId} not supported`); - } const wallets = new Map(); - const wc = await WalletError.wrap(this.wc.connect()); - // Galaxy Station mobile only returns 1 address for now - // TODO: update when Galaxy Station mobile supports more chains - const address = wc.accounts[0]; + await WalletError.wrap( + this.wc.connect(chains.map(({ chainId }) => chainId)) + ); for (let i = 0; i < chains.length; i++) { const { chainId, rpc, gasPrice } = chains[i]; - try { - // Since Galaxy Station's WalletConnect doesn't support getting pub keys, we - // need to query the account to get it. However, if the wallet does - // not contain funds, the RPC will throw errors. - const key = await WalletError.wrap( - this.getPubKey(chainId, rpc, address) - ); - wallets.set( + const { name, pubkey, address } = await WalletError.wrap( + this.wc.getAccount(chainId) + ); + const key = new Secp256k1PubKey({ + chainId, + key: base64.decode(pubkey), + }); + wallets.set( + chainId, + new GalaxyStationWalletConnectV2( + this.id, + name, + this.wc, chainId, - new GalaxyStationWalletConnectV1(wc, chainId, key, address, rpc, gasPrice) - ); - } catch (err) { - // We simply log and ignore the error for now - console.warn(err); - } + key, + address, + rpc, + gasPrice, + true // TODO: use sign mode direct when supported + ) + ); } - this.wc.cacheSession(wc); return { wallets, wc: this.wc }; } @@ -91,7 +81,7 @@ export class GalaxyStationController extends WalletController { await WalletError.wrap(ext.enable(chains.map(({ chainId }) => chainId))); for (const { chainId, rpc, gasPrice } of Object.values(chains)) { try { - const { bech32Address, pubKey, isNanoLedger } = await WalletError.wrap( + const { name, bech32Address, pubKey, isNanoLedger } = await WalletError.wrap( ext.getKey(chainId) ); const key = new Secp256k1PubKey({ @@ -102,6 +92,7 @@ export class GalaxyStationController extends WalletController { chainId, new GalaxyStationExtension( this.id, + name, ext, chainId, key, @@ -130,7 +121,7 @@ export class GalaxyStationController extends WalletController { onWindowEvent("galaxy_station_network_change", () => this.changeAccount(WalletType.EXTENSION) ); - // Galaxy Station's WalletConnect v1 doesn't support account change events + this.wc.onAccountChange(() => this.changeAccount(WalletType.WALLETCONNECT)); } private async getPubKey( diff --git a/src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV1.ts b/src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV1.ts deleted file mode 100644 index f688760a..00000000 --- a/src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV1.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { PlainMessage } from "@bufbuild/protobuf"; -import WalletConnect from "@walletconnect/legacy-client"; -import { Secp256k1PubKey } from "cosmes/client"; -import { base64, utf8 } from "cosmes/codec"; -import { - CosmosBaseV1beta1Coin as Coin, - CosmosTxV1beta1Fee as Fee, -} from "cosmes/protobufs"; - -import { WalletName } from "../../constants/WalletName"; -import { WalletType } from "../../constants/WalletType"; -import { isMobile } from "../../utils/os"; -import { - ConnectedWallet, - SignArbitraryResponse, - UnsignedTx, -} from "../ConnectedWallet"; -import { WalletError } from "../WalletError"; -import { PostResponse, SignBytesResponse } from "./types"; -import { toGalaxyStationTx } from "./utils/toGalaxyStationTx"; - -export class GalaxyStationWalletConnectV1 extends ConnectedWallet { - private readonly wc: WalletConnect; - - constructor( - wc: WalletConnect, - chainId: string, - pubKey: Secp256k1PubKey, - address: string, - rpc: string, - gasPrice: PlainMessage - ) { - super( - WalletName.GALAXYSTATION, - WalletType.WALLETCONNECT, - chainId, - pubKey, - address, - rpc, - gasPrice - ); - this.wc = wc; - } - - public async signArbitrary(data: string): Promise { - const res = await this.sendRequest( - "signBytes", - base64.encode(utf8.decode(data)) - ); - return { - data, - pubKey: res.public_key, - signature: res.signature, - }; - } - - public async signAndBroadcastTx( - { msgs, memo }: UnsignedTx, - fee: Fee - ): Promise { - // Signing a tx without posting it isn't supported - const { txhash } = await this.sendRequest( - "post", - toGalaxyStationTx(this.chainId, fee, msgs, memo) - ); - return txhash; - } - - private async sendRequest(method: string, params: unknown): Promise { - const id = Date.now(); - if (isMobile()) { - const payload = base64.encode( - utf8.decode( - JSON.stringify({ - id, - handshakeTopic: this.wc.handshakeTopic, - method, - params, - }) - ) - ); - window.location.href = `galaxystation://walletconnect_confirm/?action=walletconnect_confirm&payload=${payload}`; - } - try { - return await this.wc.sendCustomRequest({ - id, - method, - params: [params], - }); - } catch (err) { - if (err instanceof Error) { - // Error messages are JSON stringified (eg. '{"code":1,"message":"Denied by user"}') - const { message } = JSON.parse(err.message); - throw new WalletError(message, err); - } - throw new WalletError("unknown error", err); - } - } -} diff --git a/src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV2.ts b/src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV2.ts new file mode 100644 index 00000000..db1133bf --- /dev/null +++ b/src/wallet/wallets/galaxystation/GalaxyStationWalletConnectV2.ts @@ -0,0 +1,4 @@ +import { KeplrWalletConnectV2 } from "../keplr/KeplrWalletConnectV2"; + +// Galaxy Station's API is similar to Keplr. +export const GalaxyStationWalletConnectV2 = KeplrWalletConnectV2; From 2750739396a70239eac7f4caa6b2c5d54a9bc053 Mon Sep 17 00:00:00 2001 From: echel0n Date: Mon, 20 Jan 2025 02:06:06 -0800 Subject: [PATCH 7/8] refactored android / ios uri's for galaxy station mobile --- src/wallet/wallets/galaxystation/GalaxyStationController.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wallet/wallets/galaxystation/GalaxyStationController.ts b/src/wallet/wallets/galaxystation/GalaxyStationController.ts index 80750bc6..40d29f7c 100644 --- a/src/wallet/wallets/galaxystation/GalaxyStationController.ts +++ b/src/wallet/wallets/galaxystation/GalaxyStationController.ts @@ -26,9 +26,8 @@ export class GalaxyStationController extends WalletController { super(WalletName.GALAXYSTATION); this.wc = new WalletConnectV2(wcProjectId, { name: "Galaxy Station", - android: - "galaxystation://wcV2#Intent;package=io.hexxagon.station;scheme=galaxystation;end;", - ios: "galaxystation://wcV2", + android: "https://localhost:3000/wcV2#Intent;package=io.hexxagon.station;scheme=galaxystation;end;", + ios: "https://station.hexxagon.io/wcV2", }); this.registerAccountChangeHandlers(); } From 9fe898f8be30eb411395709af93779b6465523c1 Mon Sep 17 00:00:00 2001 From: echel0n Date: Mon, 20 Jan 2025 02:06:47 -0800 Subject: [PATCH 8/8] fixed typo in galaxy station mobile android uri --- src/wallet/wallets/galaxystation/GalaxyStationController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallets/galaxystation/GalaxyStationController.ts b/src/wallet/wallets/galaxystation/GalaxyStationController.ts index 40d29f7c..7d9dae1e 100644 --- a/src/wallet/wallets/galaxystation/GalaxyStationController.ts +++ b/src/wallet/wallets/galaxystation/GalaxyStationController.ts @@ -26,7 +26,7 @@ export class GalaxyStationController extends WalletController { super(WalletName.GALAXYSTATION); this.wc = new WalletConnectV2(wcProjectId, { name: "Galaxy Station", - android: "https://localhost:3000/wcV2#Intent;package=io.hexxagon.station;scheme=galaxystation;end;", + android: "https://station.hexxagon.io/wcV2#Intent;package=io.hexxagon.station;scheme=galaxystation;end;", ios: "https://station.hexxagon.io/wcV2", }); this.registerAccountChangeHandlers();