From b61204fc5f2df38f111f8ac9a5d4d3482213f50f Mon Sep 17 00:00:00 2001 From: HippocampusSK Date: Wed, 11 Nov 2026 16:48:59 +0000 Subject: [PATCH] Offer Cosmos, NEAR and Starknet with Bitget Wallet Parked until the wallet exposes them: Bitget answers "ChainIds not supported" for Cosmos and Starknet and does not inject a NEAR provider. Co-Authored-By: Claude Fable 5.2 Claude-Session: https://claude.ai/code/session_01Y7T5QKQRd8CprqzccbMRJX --- .changeset/bitget-cosmos-near-starknet.md | 6 + .../src/bitget/__tests__/bitget.test.ts | 18 ++- .../wallet-extensions/src/bitget/helpers.ts | 116 +++++++++++++++++- .../wallet-extensions/src/bitget/index.ts | 16 ++- .../wallet-extensions/src/bitget/types.ts | 44 +++++++ packages/wallet-extensions/src/types.ts | 3 +- 6 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 .changeset/bitget-cosmos-near-starknet.md diff --git a/.changeset/bitget-cosmos-near-starknet.md b/.changeset/bitget-cosmos-near-starknet.md new file mode 100644 index 00000000..73aba13b --- /dev/null +++ b/.changeset/bitget-cosmos-near-starknet.md @@ -0,0 +1,6 @@ +--- +"@swapkit/wallet-extensions": minor +"@swapkit/wallets": minor +--- + +Bitget Wallet offers Cosmos and Starknet again, and connects NEAR. diff --git a/packages/wallet-extensions/src/bitget/__tests__/bitget.test.ts b/packages/wallet-extensions/src/bitget/__tests__/bitget.test.ts index d0aace60..2907f03c 100644 --- a/packages/wallet-extensions/src/bitget/__tests__/bitget.test.ts +++ b/packages/wallet-extensions/src/bitget/__tests__/bitget.test.ts @@ -1,12 +1,13 @@ // @ts-nocheck - Test file with intentional mocking of browser globals import { afterEach, describe, expect, test } from "bun:test"; import { createHash } from "node:crypto"; +import { actionCreators } from "@near-js/transactions"; import { base64 } from "@scure/base"; import { Keypair, SystemProgram, Transaction } from "@solana/web3.js"; import { Chain, SwapKitError } from "@swapkit/helpers"; import { encode } from "ripple-binary-codec"; -import { getWalletMethods } from "../helpers"; +import { getWalletMethods, toBitgetNearAction } from "../helpers"; import { bitgetWallet } from "../index"; const SUI_ADDRESS = `0x${"ab".repeat(32)}`; @@ -138,6 +139,21 @@ describe("Bitget Solana signer", () => { }); }); +describe("Bitget Near actions", () => { + test("turns transfers and contract calls into the JSON form Bitget expects", () => { + const call = actionCreators.functionCall("ft_transfer", { amount: "5", receiver_id: "bob.near" }, 30n, 1n); + + expect(toBitgetNearAction(actionCreators.transfer(1000n))).toEqual({ + params: { deposit: "1000" }, + type: "Transfer", + }); + expect(toBitgetNearAction(call)).toEqual({ + params: { args: { amount: "5", receiver_id: "bob.near" }, deposit: "1", gas: "30", methodName: "ft_transfer" }, + type: "FunctionCall", + }); + }); +}); + describe("Bitget Tron signer", () => { function mockTron(sign) { globalThis.window = { diff --git a/packages/wallet-extensions/src/bitget/helpers.ts b/packages/wallet-extensions/src/bitget/helpers.ts index 3b8aa035..5cf31fea 100644 --- a/packages/wallet-extensions/src/bitget/helpers.ts +++ b/packages/wallet-extensions/src/bitget/helpers.ts @@ -1,5 +1,14 @@ import { base64, hex } from "@scure/base"; -import { Chain, type EVMChain, EVMChains, prepareNetworkSwitch, SwapKitError, WalletOption } from "@swapkit/helpers"; +import { + Chain, + type EVMChain, + EVMChains, + GAIAConfig, + prepareNetworkSwitch, + SwapKitError, + WalletOption, +} from "@swapkit/helpers"; +import type { NearSigner } from "@swapkit/toolboxes/near"; import type { RippleTransaction } from "@swapkit/toolboxes/ripple"; import type { SolanaProvider } from "@swapkit/toolboxes/solana"; import type { SuiChainSigner } from "@swapkit/toolboxes/sui"; @@ -8,7 +17,7 @@ import { Transaction } from "@swapkit/utxo-signer"; import type { Eip1193Provider } from "ethers"; import { getTonConnectWallet } from "../helpers/tonConnect"; import { getUtxoScriptTypeParams } from "../helpers/utxoScriptType"; -import type { BitgetProviders, BitgetSolanaTransaction } from "./types"; +import type { BitgetNearAction, BitgetNearResponse, BitgetProviders, BitgetSolanaTransaction } from "./types"; const SUI_MAINNET = "sui:mainnet"; const TRON_USER_REJECTED = 4001; @@ -80,6 +89,21 @@ async function getBitcoinWalletMethods() { return { ...toolbox, address }; } +async function getCosmosWalletMethods() { + const wallet = getProvider("keplr", Chain.Cosmos); + + await wallet.enable(GAIAConfig.chainId); + const offlineSigner = await wallet.getOfflineSignerAuto(GAIAConfig.chainId); + const accounts = await offlineSigner.getAccounts(); + if (!accounts?.[0]) throw noAccountsError(Chain.Cosmos); + + const { getCosmosToolbox } = await import("@swapkit/toolboxes/cosmos"); + const [{ address }] = accounts; + const signer = Object.assign(offlineSigner, { getAddress: () => Promise.resolve(address) }); + + return { ...getCosmosToolbox(Chain.Cosmos, { signer }), address }; +} + async function getSolanaWalletMethods() { const provider = getProvider("solana", Chain.Solana); const connection = await provider.connect(); @@ -221,6 +245,88 @@ async function getSuiWalletMethods() { return { ...getSuiToolbox({ signer }), address: account.address }; } +async function getStarknetWalletMethods() { + const provider = window.starknet_bitkeep; + if (!provider) throw notFoundError(Chain.Starknet); + + await provider.enable(); + const { account } = provider; + if (!account) throw noAccountsError(Chain.Starknet); + + const { getStarknetToolbox } = await import("@swapkit/toolboxes/starknet"); + + return { ...getStarknetToolbox({ signer: account, walletDeploysAccount: true }), address: account.address }; +} + +type NearAction = { + functionCall?: { methodName: string; args: Uint8Array; gas: bigint; deposit: bigint }; + transfer?: { deposit: bigint }; +}; + +// Bitget takes NEAR actions in the wallet-selector JSON form instead of the borsh classes +export function toBitgetNearAction({ functionCall, transfer }: NearAction): BitgetNearAction { + if (transfer) return { params: { deposit: transfer.deposit.toString() }, type: "Transfer" }; + + if (functionCall) { + return { + params: { + args: JSON.parse(new TextDecoder().decode(functionCall.args) || "{}"), + deposit: functionCall.deposit.toString(), + gas: functionCall.gas.toString(), + methodName: functionCall.methodName, + }, + type: "FunctionCall", + }; + } + + throw new SwapKitError("wallet_near_method_not_supported", { method: "action", wallet: WalletOption.BITGET }); +} + +function readNearResponse({ error, response }: BitgetNearResponse) { + if (error) throw transactionFailedError(Chain.Near, error); + if (!(Array.isArray(response) && response.length)) throw transactionFailedError(Chain.Near, response); + + return response; +} + +async function getNearWalletMethods() { + const provider = getProvider("near", Chain.Near); + + if (!(provider.isSignedIn() && provider.getAccountId())) { + const { accessKey, error } = await provider.requestSignIn({ contractId: "", methodNames: [] }); + if (!accessKey || error) throw connectionRejectedError(Chain.Near); + } + + const address = provider.getAccountId(); + if (!address) throw noAccountsError(Chain.Near); + + const { getNearToolbox } = await import("@swapkit/toolboxes/near"); + + const notSupported = (method: string) => + Promise.reject(new SwapKitError("wallet_near_method_not_supported", { method, wallet: WalletOption.BITGET })); + + // Bitget only signs and sends NEAR transactions, so the toolbox falls back to signAndSendTransaction + const signer = { + getAddress: () => Promise.resolve(address), + async getPublicKey() { + const { PublicKey } = await import("@near-js/crypto"); + return PublicKey.fromString((await provider.getPublicKey()).toString()); + }, + async signAndSendTransaction({ actions, receiverId }: { actions: NearAction[]; receiverId?: string }) { + if (!receiverId) throw transactionFailedError(Chain.Near, "Missing receiver"); + + return readNearResponse( + await provider.signAndSendTransaction({ actions: actions.map(toBitgetNearAction), receiverId }), + ); + }, + signDelegateAction: () => notSupported("signDelegateAction"), + signNep413Message: () => notSupported("signNep413Message"), + signTransaction: () => notSupported("signTransaction"), + }; + + return { ...getNearToolbox({ signer: signer as unknown as NearSigner }), address }; +} + const XRP_WALLET_FIELDS = ["Fee", "LastLedgerSequence", "Sequence", "SigningPubKey"]; const XRP_FULLY_CANONICAL_SIG = 0x80000000; @@ -288,10 +394,16 @@ export function getWalletMethods(chain: Chain): Promise = createWallet({ ].map((chain) => [chain, { direct: true, typedData: true }]), ), [Chain.Bitcoin]: { direct: true }, + [Chain.Cosmos]: { direct: true }, + [Chain.Near]: { direct: true }, [Chain.Ripple]: { direct: true }, [Chain.Solana]: { direct: true }, + [Chain.Starknet]: { direct: true }, [Chain.Sui]: { direct: true }, [Chain.Ton]: { direct: true }, [Chain.Tron]: { direct: true }, // [Chain.Aptos]: blocked on toolbox — getAptosToolbox needs to accept AptosExtensionProvider }, - supportedChains: [...EVMChains, Chain.Bitcoin, Chain.Ripple, Chain.Solana, Chain.Sui, Chain.Ton, Chain.Tron], + supportedChains: [ + ...EVMChains, + Chain.Bitcoin, + Chain.Cosmos, + Chain.Near, + Chain.Ripple, + Chain.Solana, + Chain.Starknet, + Chain.Sui, + Chain.Ton, + Chain.Tron, + ], walletType: WalletOption.BITGET, }); diff --git a/packages/wallet-extensions/src/bitget/types.ts b/packages/wallet-extensions/src/bitget/types.ts index 96a0d5ac..75c05aad 100644 --- a/packages/wallet-extensions/src/bitget/types.ts +++ b/packages/wallet-extensions/src/bitget/types.ts @@ -1,5 +1,9 @@ +import type { AminoSignResponse, StdSignature, StdSignDoc } from "@cosmjs/amino"; import type { PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js"; import type { EthereumWindowProvider } from "@swapkit/helpers"; +import type { OfflineAminoSigner, OfflineDirectSigner } from "@swapkit/toolboxes/cosmos"; + +import type { StarknetToolboxParams } from "@swapkit/toolboxes/starknet"; type UnisatToSignInputs = { index: number; sighashTypes?: number[]; disableTweakSigner?: boolean } & ( | { address: string } @@ -16,6 +20,20 @@ export type BitgetBtcProvider = { ) => Promise; }; +export type BitgetKeplrProvider = { + enable: (chainId: string | string[]) => Promise; + signAmino: (chainId: string, signer: string, signDoc: StdSignDoc, signOptions: any) => Promise; + signArbitrary: (chainId: string, signer: string, data: string | Uint8Array) => Promise; + verifyArbitrary: ( + chainId: string, + signer: string, + data: string | Uint8Array, + signature: StdSignature, + ) => Promise; + getOfflineSignerOnlyAmino: (chainId: string) => OfflineAminoSigner; + getOfflineSignerAuto: (chainId: string) => Promise; +}; + export type BitgetSolanaTransaction = Transaction | VersionedTransaction; // Bitget answers either like Phantom or in the Wallet Standard shape, depending on the wallet version @@ -52,6 +70,30 @@ export type BitgetSuiProvider = { }) => Promise<{ bytes: string; signature: string }>; }; +export type BitgetNearAction = { type: string; params?: Record }; + +type BitgetNearOutcome = { transaction?: { hash?: string }; transaction_outcome?: { id?: string } }; + +export type BitgetNearResponse = { + error?: string | { type?: string; message?: string }; + response?: BitgetNearOutcome[] | { error: { message: string } }; +}; + +export type BitgetNearProvider = { + getAccountId: () => string; + getPublicKey: () => Promise<{ toString: () => string } | string>; + isSignedIn: () => boolean; + requestSignIn: (params: { + contractId: string; + methodNames?: string[]; + }) => Promise<{ accessKey?: unknown; error?: string | { type?: string } }>; + signAndSendTransaction: (params: { receiverId: string; actions: BitgetNearAction[] }) => Promise; +}; + +type StarknetAccount = NonNullable["signer"]>; + +export type BitgetStarknetProvider = { account?: StarknetAccount; enable: () => Promise }; + export type BitgetXrpProvider = { connect: () => Promise; signTransaction: (transaction: Record) => Promise; @@ -60,6 +102,8 @@ export type BitgetXrpProvider = { export type BitgetProviders = BitgetTronProvider & { aptos: unknown; ethereum: EthereumWindowProvider; + keplr: BitgetKeplrProvider; + near: BitgetNearProvider; solana: BitgetSolanaProvider; suiWallet: BitgetSuiProvider; unisat: BitgetBtcProvider; diff --git a/packages/wallet-extensions/src/types.ts b/packages/wallet-extensions/src/types.ts index 04e3e306..be394127 100644 --- a/packages/wallet-extensions/src/types.ts +++ b/packages/wallet-extensions/src/types.ts @@ -9,7 +9,7 @@ import type { SolanaProvider } from "@swapkit/toolboxes/solana"; import type { StarknetToolboxParams } from "@swapkit/toolboxes/starknet"; import type { SubstrateInjectedExtension } from "@swapkit/toolboxes/substrate"; import type { Eip1193Provider } from "ethers"; -import type { BitgetProviders } from "./bitget/types"; +import type { BitgetProviders, BitgetStarknetProvider } from "./bitget/types"; import type { NearBrowserWalletProvider } from "./helpers/near"; import type { TronLinkWindow } from "./tronlink"; @@ -88,6 +88,7 @@ declare global { }; bitkeep?: Partial; + starknet_bitkeep?: BitgetStarknetProvider; bitgetTonWallet?: { tonconnect: TonConnectBridge }; okxwallet?: