Skip to content

feat: cosmos wcv2 wallet support#11909

Open
gomes-bot wants to merge 18 commits intoshapeshift:developfrom
gomes-bot:feat_cosmos_wcv2_wallet
Open

feat: cosmos wcv2 wallet support#11909
gomes-bot wants to merge 18 commits intoshapeshift:developfrom
gomes-bot:feat_cosmos_wcv2_wallet

Conversation

@gomes-bot
Copy link
Contributor

@gomes-bot gomes-bot commented Feb 17, 2026

Description

Adds Cosmos chain support to the WalletConnect v2 wallet integration. When a user connects via WC, the session proposal now includes cosmos as an optional namespace, so wallets that support Cosmos (e.g. Keplr, Leap) will expose their cosmos accounts.

The approach is surgical - we keep EthereumProvider as the main provider and monkey-patch its internal signer.connect() to inject cosmos optionalNamespaces. This means zero changes to the web app layer (adapter, config, WalletProvider, etc.) - all the magic happens in hdwallet-walletconnectv2.

What's new:

  • cosmos.ts - cosmos-specific WC methods (getAddress from CAIP-10 session, signTx via OfflineAminoSigner backed by cosmos_signAmino)
  • walletconnectV2.ts - implements CosmosWallet/CosmosWalletInfo interfaces, _supportsCosmos dynamic getter based on session namespaces
  • patchSignerForNonEvmNamespaces() - moves all EVM namespaces from required to optional (CAIP-25 compliant), adds cosmos as optional namespace
  • patchProviderConnectForMultiNamespace() - patches provider.connect/enable to handle modal abort and eth_requestAccounts failures for non-EVM wallets
  • _supportsETH is now a dynamic getter checking session namespaces (was hardcoded true)
  • getDeviceID() has a fallback chain: ETH -> Cosmos -> 'wc:unknown'
  • Send and staking flows now allow WalletConnectV2 wallets for CosmosSdk chains

Issue (if applicable)

Risk

Low - additive only. EVM WC flow is unchanged in behavior:

  • eip155 chains/methods/events are preserved 1:1 (merged from required+optional into optional)
  • patchProviderConnectForMultiNamespace only catches errors for non-EVM sessions, EVM enable() succeeds as before
  • _supportsETH dynamic getter returns true after session establishment (same timing as all other wallet checks)
  • Send/staking guard only adds WCv2 bypass, doesn't change existing MM/Vultisig/offline-signing paths

Cosmos transactions via WalletConnect v2 wallets. EVM WC wallet flow is unaffected.

Testing

Engineering

Cosmos WC wallet (new):

  1. Connect a Cosmos-capable wallet via WalletConnect (e.g. Keplr mobile, Leap)
  2. Verify cosmos address is picked up from session namespaces
  3. Verify cosmos send works via cosmos_signAmino
  4. Verify cosmos staking actions work

EVM WC wallet (regression):

  1. Connect an EVM wallet via WalletConnect (e.g. MetaMask mobile, Trust Wallet, Rainbow)
  2. Verify ETH address is picked up as before
  3. Verify EVM sends work (ETH mainnet, L2s)
  4. Verify EVM swap signing works
  5. Verify wallet_switchEthereumChain works across L2s
  6. Verify disconnecting and reconnecting works

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

WC cosmos wallet connection + cosmos sends via WC-connected wallets.

Screenshots

https://jam.dev/c/0fb9a376-b3a3-4ba3-9120-a4539ee16647

Summary by CodeRabbit

  • New Features

    • Added Cosmos (Atom) support for WalletConnect V2 wallets: account discovery, address retrieval, and transaction signing available.
  • Bug Fixes

    • Updated wallet compatibility checks so send and staking flows properly exclude or handle WalletConnect V2 where offline signing isn't supported.

Add Cosmos chain support to WalletConnect v2 wallet integration.

- Inject cosmos optionalNamespaces into WC session via signer monkey-patch
- Implement cosmosGetAddress using CAIP-10 session accounts
- Implement cosmosSignTx using OfflineAminoSigner backed by WC cosmos_signAmino
- Add cosmos account paths, path description, and WalletInfo/Wallet interfaces

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gomes-bot
Copy link
Contributor Author

@gomesalexandre review me senpai UwU 🥺✨ pls give cosmos the love it deserves 🌌💫

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Warning

Rate limit exceeded

@gomes-bot has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 55 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 5a622e7 and db0fe04.

📒 Files selected for processing (2)
  • packages/hdwallet-walletconnectv2/package.json
  • packages/hdwallet-walletconnectv2/src/walletconnectV2.ts
📝 Walkthrough

Walkthrough

Adds Cosmos support to WalletConnect V2: new Cosmos harness, address extraction from CAIP‑10 sessions, OfflineAmino signing flow, Cosmos account path utilities, and multi‑namespace integration in the WalletConnectV2 wallet implementation. UI checks updated to exclude WalletConnect V2 where appropriate.

Changes

Cohort / File(s) Summary
WalletConnect V2 Dependencies
packages/hdwallet-walletconnectv2/package.json
Added Cosmos signing dependencies: @cosmjs/amino, @cosmjs/stargate, and @shapeshiftoss/proto-tx-builder.
WalletConnect V2 Cosmos Core
packages/hdwallet-walletconnectv2/src/cosmos.ts
New Cosmos harness: path derivation, CAIP‑10 address extraction, OfflineAmino signer construction, amino signing delegation, SignerData assembly, and cosmosSignTx implementation with error handling.
WalletConnect V2 Main Module
packages/hdwallet-walletconnectv2/src/walletconnectV2.ts, packages/hdwallet-walletconnectv2/src/index.ts
Extended WalletConnectV2HDWallet/WalletInfo to implement CosmosWallet/CosmosWalletInfo, added cosmos* methods/getters, multi‑namespace connect/patching, and re‑exported Cosmos module from index.
UI Integration
src/components/Modals/Send/utils.ts, src/plugins/cosmos/hooks/useStakingAction/useStakingAction.tsx
Exclude WalletConnect V2 from certain offline‑signing/Send and staking flows by adding isWalletConnectV2 checks alongside existing wallet guards.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI
    participant WC as WalletConnect<br/>Provider
    participant Signer as OfflineAmino<br/>Signer
    participant TxBuilder as ProtoTx<br/>Builder
    participant Cosmos as Cosmos<br/>Network

    User->>UI: Initiate Cosmos tx
    UI->>WC: Read CAIP-10 account from session
    WC-->>UI: Cosmos address & pubkey
    UI->>Signer: Construct OfflineAminoSigner (addr + pubkey)
    UI->>WC: Request `cosmos_signAmino`
    WC-->>UI: Amino signature
    UI->>TxBuilder: Build SignerData & assemble signed tx
    UI->>Cosmos: Broadcast signed tx
    Cosmos-->>UI: Tx receipt
    UI-->>User: Confirmation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • gomesalexandre

Poem

🐰 I hopped into CAIP‑10 light,
offline signer snug and tight,
namespaces now play along,
amino sings its tiny song,
hops of joy — transactions take flight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: cosmos wcv2 wallet support' clearly and specifically describes the main change: adding Cosmos chain support to WalletConnect v2 wallets.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

gomes-bot and others added 5 commits February 17, 2026 16:11
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- re-export cosmosDescribePath from core instead of trivial wrapper
- remove unused params from standalone helper functions
- remove unused CosmosGetAddress import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move EVM from required to optional namespaces (fixes
  "params.requiredNamespaces.eip155 is not allowed" error)
- Add patchProviderConnectForMultiNamespace to handle modal
  subscribeState abort and enable() eth_requestAccounts failure
  for cosmos-only wallets
- Make _supportsETH a dynamic getter checking session namespaces
- Add getDeviceID fallback chain (ETH -> cosmos accounts -> unknown)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bypass offline-signing guard for WalletConnectV2 in send/staking flows
- Code simplifier cleanup: extract mergeUnique helper, WcCosmosAccount
  type, simplify CAIP-10 parsing and address caching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gomes-bot gomes-bot marked this pull request as ready for review February 19, 2026 08:56
@gomes-bot gomes-bot requested a review from a team as a code owner February 19, 2026 08:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
packages/hdwallet-walletconnectv2/src/walletconnectV2.ts (3)

240-270: patchProviderConnectForMultiNamespace silently swallows enable() errors when a session exists.

Line 266: if provider.signer?.session is truthy, any error from enable() (including network errors, user rejections, or auth failures) is swallowed and an empty array is returned. This is intentional for the Cosmos-only case where eth_requestAccounts fails, but it could mask legitimate EVM connection failures for wallets that do support EVM. Consider narrowing the catch to the specific error type/message from eth_requestAccounts if feasible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hdwallet-walletconnectv2/src/walletconnectV2.ts` around lines 240 -
270, The catch in patchProviderConnectForMultiNamespace around provider.enable()
is too broad and returns [] for any error when provider.signer?.session exists;
narrow it to only swallow the specific eth_requestAccounts/user-rejection error
for Cosmos-only flows by inspecting the thrown error in the catch of
provider.enable() (e.g., check error.name/message/code or a provider-specific
marker indicating eth_requestAccounts rejection) and return [] only for that
case, otherwise rethrow the error; update the provider.enable override in
patchProviderConnectForMultiNamespace to perform this conditional check so
legitimate EVM/network/auth errors are not silently suppressed.

480-488: getDeviceID() may return different IDs across sessions for the same wallet.

If a wallet supports both ETH and Cosmos, getDeviceID returns 'wc:' + ethAddr. If it later reconnects as Cosmos-only (e.g., session namespace changes), it returns 'wc:' + cosmosAddr. Since walletId is used for portfolio state filtering and persistence, a changing device ID for the same physical wallet could cause state mismatches. This may be acceptable for the current WC flow but worth keeping in mind.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hdwallet-walletconnectv2/src/walletconnectV2.ts` around lines 480 -
488, getDeviceID() can flip between eth and cosmos addresses across sessions
causing an unstable walletId; update getDeviceID to produce a deterministic,
stable identifier by combining available addresses or using a persisted stable
id: call ethGetAddress() and cosmosGetAddress() and if both exist return a
composite string like 'wc:eth:{ethAddr}|cosmos:{cosmosAddr}', if only one exists
return that same prefixed form, and optionally fall back to a stored persistent
id (persisted key tied to this connection) to ensure stability across namespace
changes; adjust getDeviceID, ethGetAddress, and cosmosGetAddress usage
accordingly so walletId remains consistent.

522-527: cosmosGetAddress is synchronous but wrapped in async — cached addresses are never cleared on session change or disconnect.

Both this.ethAddress (line 424) and this.cosmosAddress (line 525) persist after being set, even if the WalletConnect session is replaced or the wallet reconnects with a different account. The disconnect() method only calls provider.disconnect() without clearing these cached values. Consider clearing both cached addresses in disconnect() or clearSession() to prevent stale address usage after reconnection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hdwallet-walletconnectv2/src/walletconnectV2.ts` around lines 522 -
527, The cosmosGetAddress method is an unnecessary async wrapper around the
synchronous cosmosGetAddress() helper and cached addresses (this.cosmosAddress
and this.ethAddress) are never cleared on session/disconnect; change
cosmosGetAddress to a synchronous method that returns string | null (remove
async/Promise) and keep using the helper cosmosGetAddress(this.provider) to
populate this.cosmosAddress, and update disconnect() and/or clearSession() to
null out both this.cosmosAddress and this.ethAddress (in addition to calling
provider.disconnect()) so stale addresses cannot persist across session
replacement or reconnection.
packages/hdwallet-walletconnectv2/src/cosmos.ts (2)

65-66: algo cast to literal 'secp256k1' is unchecked.

The algo value coming from the WC wallet is cast to 'secp256k1' on line 70 without validation. If a wallet returns 'ed25519' or another algorithm, the signing flow will silently proceed with an incorrect key type, producing an invalid signature. A runtime guard would prevent hard-to-debug signing failures.

🛡️ Add a guard
  const { pubkey: pubkeyBase64, algo } = wcAccounts[0]
+ if (algo !== 'secp256k1') return null
  const pubkey = new Uint8Array(Buffer.from(pubkeyBase64, 'base64'))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hdwallet-walletconnectv2/src/cosmos.ts` around lines 65 - 66,
Validate the WC account's algorithm before treating it as secp256k1: check
wcAccounts[0].algo (the variable algo) and if it is not exactly 'secp256k1'
throw or return a clear error (or handle other algorithms explicitly) instead of
casting; ensure this guard is placed before creating pubkey/new
Uint8Array(Buffer.from(...)) and before any signing logic that assumes secp256k1
so the code using algo and pubkey fails fast for unsupported algorithms.

48-89: Inconsistent error handling: null return vs throw in the same flow.

cosmosSignTx returns null on line 53 when the signer address is missing from the session, but throws on line 63 when cosmos_getAccounts returns no accounts. Both represent "wallet can't sign Cosmos" — the caller must handle both a null return and catch an exception. Consider returning null consistently or throwing consistently.

♻️ Consistent null-return approach
-  if (!wcAccounts?.length) throw new Error('No cosmos accounts from WalletConnect')
+  if (!wcAccounts?.length) return null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hdwallet-walletconnectv2/src/cosmos.ts` around lines 48 - 89,
cosmosSignTx mixes return-null (when getCosmosAddressFromSession() yields no
address) and throwing an exception when provider.signer.request(...) for
'cosmos_getAccounts' returns no accounts; make this consistent by returning null
instead of throwing: replace the Error throw after checking wcAccounts?.length
with a null return (optionally logging the missing accounts) so callers only
need to handle a null result from cosmosSignTx; keep all other flow (pubkey
parsing, offlineSigner, sign(...)) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/hdwallet-walletconnectv2/src/cosmos.ts`:
- Around line 65-66: Validate the WC account's algorithm before treating it as
secp256k1: check wcAccounts[0].algo (the variable algo) and if it is not exactly
'secp256k1' throw or return a clear error (or handle other algorithms
explicitly) instead of casting; ensure this guard is placed before creating
pubkey/new Uint8Array(Buffer.from(...)) and before any signing logic that
assumes secp256k1 so the code using algo and pubkey fails fast for unsupported
algorithms.
- Around line 48-89: cosmosSignTx mixes return-null (when
getCosmosAddressFromSession() yields no address) and throwing an exception when
provider.signer.request(...) for 'cosmos_getAccounts' returns no accounts; make
this consistent by returning null instead of throwing: replace the Error throw
after checking wcAccounts?.length with a null return (optionally logging the
missing accounts) so callers only need to handle a null result from
cosmosSignTx; keep all other flow (pubkey parsing, offlineSigner, sign(...))
unchanged.

In `@packages/hdwallet-walletconnectv2/src/walletconnectV2.ts`:
- Around line 240-270: The catch in patchProviderConnectForMultiNamespace around
provider.enable() is too broad and returns [] for any error when
provider.signer?.session exists; narrow it to only swallow the specific
eth_requestAccounts/user-rejection error for Cosmos-only flows by inspecting the
thrown error in the catch of provider.enable() (e.g., check
error.name/message/code or a provider-specific marker indicating
eth_requestAccounts rejection) and return [] only for that case, otherwise
rethrow the error; update the provider.enable override in
patchProviderConnectForMultiNamespace to perform this conditional check so
legitimate EVM/network/auth errors are not silently suppressed.
- Around line 480-488: getDeviceID() can flip between eth and cosmos addresses
across sessions causing an unstable walletId; update getDeviceID to produce a
deterministic, stable identifier by combining available addresses or using a
persisted stable id: call ethGetAddress() and cosmosGetAddress() and if both
exist return a composite string like 'wc:eth:{ethAddr}|cosmos:{cosmosAddr}', if
only one exists return that same prefixed form, and optionally fall back to a
stored persistent id (persisted key tied to this connection) to ensure stability
across namespace changes; adjust getDeviceID, ethGetAddress, and
cosmosGetAddress usage accordingly so walletId remains consistent.
- Around line 522-527: The cosmosGetAddress method is an unnecessary async
wrapper around the synchronous cosmosGetAddress() helper and cached addresses
(this.cosmosAddress and this.ethAddress) are never cleared on
session/disconnect; change cosmosGetAddress to a synchronous method that returns
string | null (remove async/Promise) and keep using the helper
cosmosGetAddress(this.provider) to populate this.cosmosAddress, and update
disconnect() and/or clearSession() to null out both this.cosmosAddress and
this.ethAddress (in addition to calling provider.disconnect()) so stale
addresses cannot persist across session replacement or reconnection.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/hdwallet-walletconnectv2/src/walletconnectV2.ts (1)

262-265: Avoid any in the provider patch boundary

Line 262 and Line 265 introduce any in critical connection code paths. Prefer unknown + narrow typed shape for patched provider methods.

As per coding guidelines **/*.{ts,tsx}: "NEVER use any type unless absolutely necessary in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hdwallet-walletconnectv2/src/walletconnectV2.ts` around lines 262 -
265, The patched provider currently uses the unsafe any type (const provider =
this.provider as any) and should be changed to use unknown with a narrow
interface: declare a local type (e.g., PatchableProvider) describing the connect
method signature and bindable methods, cast this.provider to unknown then
perform a runtime type-check (type guard) to ensure it matches PatchableProvider
before assigning originalConnect = provider.connect.bind(provider) and
overriding provider.connect; update references to originalConnect and
provider.connect accordingly so the compiler knows the exact method shape and
you avoid any usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/hdwallet-walletconnectv2/src/walletconnectV2.ts`:
- Around line 242-256: The current rewrite clears caller-provided namespaces by
passing namespaces: {} into originalConnect; preserve any non-eip155 namespaces
by merging instead of replacing. In the return from the connect wrapper, merge
params.namespaces and params.optionalNamespaces with the constructed eip155
payloads (e.g. compute mergedNamespaces = { ...(params.namespaces || {}),
eip155: /* requiredEvm merged into required namespace */ } and
mergedOptionalNamespaces = { ...(params.optionalNamespaces || {}), eip155: {
chains: mergeUnique(...), methods: mergeUnique(...), events: mergeUnique(...),
rpcMap: { ...(requiredEvm as any)?.rpcMap, ...(optionalEvm as any)?.rpcMap } },
cosmos: COSMOS_OPTIONAL_NAMESPACE }, then call originalConnect({ ...params,
namespaces: mergedNamespaces, optionalNamespaces: mergedOptionalNamespaces }).
This preserves caller non-EVM namespaces while applying the eip155 and cosmos
merges.
- Around line 269-271: The stub for modal.subscribeState is wrong: replace the
current no-arg noop with a function that accepts the callback parameter and
returns an unsubscribe function so callers won't get undefined; specifically
update modal.subscribeState to a signature that takes callback (e.g., callback:
any or (newState: PublicStateControllerState) => void) and returns a () => void
unsubscribe closure so originalConnect() or other callers can invoke it and
receive a valid unsubscribe function.

---

Nitpick comments:
In `@packages/hdwallet-walletconnectv2/src/walletconnectV2.ts`:
- Around line 262-265: The patched provider currently uses the unsafe any type
(const provider = this.provider as any) and should be changed to use unknown
with a narrow interface: declare a local type (e.g., PatchableProvider)
describing the connect method signature and bindable methods, cast this.provider
to unknown then perform a runtime type-check (type guard) to ensure it matches
PatchableProvider before assigning originalConnect =
provider.connect.bind(provider) and overriding provider.connect; update
references to originalConnect and provider.connect accordingly so the compiler
knows the exact method shape and you avoid any usage.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between db5b277 and 5a622e7.

📒 Files selected for processing (1)
  • packages/hdwallet-walletconnectv2/src/walletconnectV2.ts

Copy link
Collaborator

@NeOMakinG NeOMakinG left a comment

Choose a reason for hiding this comment

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

EVM and Cosmos is working through WC on Keplr mobile, tested account exposure, send/sign txs

@gomesalexandre gomesalexandre enabled auto-merge (squash) March 1, 2026 21:45
- Spread other required/optional namespaces so non-eip155 namespaces
  (e.g. solana) aren't silently dropped during connect
- Fix modal.subscribeState stub to accept callback param and return
  unsubscribe function matching Web3Modal's expected signature
auto-merge was automatically disabled March 1, 2026 22:05

Head branch was pushed to by a user without write access

@gomesalexandre gomesalexandre enabled auto-merge (squash) March 4, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants