Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
78ab138
feat(bitcoin): add a connector-backed installed-wallet probe
chybisov Sep 18, 2026
ac5a81f
fix(bitcoin): list only wallets their connector can resolve
chybisov Sep 18, 2026
ee1ed8a
fix(provider): stop duplicating bigmi's Bitcoin wallet detection
chybisov Sep 18, 2026
2576f03
fix(bitcoin): re-probe when a wallet joins the registry
chybisov Sep 18, 2026
6d873a5
fix(deps): require the bigmi releases carrying the Bitcoin fixes
chybisov Sep 18, 2026
51b07fb
feat(wallet-management): make the ecosystem order explicit
chybisov Sep 18, 2026
3a2e9ae
fix(bitcoin): keep the newest wallet probe, and offer MetaMask by def…
chybisov Sep 18, 2026
8553b14
fix(bitcoin): ignore a duplicate connector
chybisov Sep 18, 2026
fbdc78d
fix(bitcoin): drain every connection on disconnect, and run the tests
chybisov Sep 18, 2026
c0c70d7
ci: run the tests on pull requests
chybisov Sep 18, 2026
9ff71fb
fix(bitcoin): report a disconnect that fails partway
chybisov Sep 21, 2026
3ea2f80
fix(bitcoin): give every connection its own disconnect attempt
chybisov Sep 21, 2026
9b484a2
fix(bitcoin): do not let a stale connection fail a real disconnect
chybisov Sep 21, 2026
915894d
fix(playground): keep @metamask/connect-evm, and re-probe on focus
chybisov Sep 21, 2026
b92b84c
fix(bitcoin): let a vanished wallet not abort the disconnect's caller
chybisov Sep 21, 2026
b83cad8
fix(wallet-management): freeze the exported ecosystem order
chybisov Sep 21, 2026
3d95a28
Merge remote-tracking branch 'origin/main' into fix/bitcoin-connector…
chybisov Sep 21, 2026
355e81e
fix(deps): require the released bigmi Bitcoin fixes
chybisov Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bitcoin-connector-dedupe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lifi/widget-provider-bitcoin': patch
---

Ignore a duplicate Bitcoin connector. Passing `metamask()` through `connectors` is now redundant because it is a default, and an integrator who still does would otherwise see MetaMask listed twice.
8 changes: 8 additions & 0 deletions .changeset/bitcoin-connector-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@lifi/widget-provider': minor
'@lifi/widget-provider-bitcoin': minor
---

Bitcoin wallets are now listed only when their connector can actually resolve a provider, instead of being detected by a second copy of that logic that had drifted from it. A wallet that impersonates MetaMask no longer offers a Bitcoin entry that cannot connect.

`isWalletInstalled` no longer answers for Bitcoin connector ids and returns `true` for them, as it does for any wallet it does not explicitly know. `metaMask` and `coinbase` are unchanged.
5 changes: 5 additions & 0 deletions .changeset/bitcoin-probe-race.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lifi/widget-provider-bitcoin': patch
---

Keep the newest installed-wallet probe. Every Wallet Standard wallet announces itself, so probes overlap, and a slower earlier one could restore a list predating the registration that triggered it — dropping MetaMask Bitcoin from the menu until the next event. The list is also reused when unchanged, so a registration no longer re-renders every Bitcoin consumer. Disconnecting now attempts every connection and never reports a wallet that has gone away as a failure, since `@bigmi/client` clears the connection regardless — reporting it would abort the connect a disconnect is usually preparing for.
6 changes: 6 additions & 0 deletions .changeset/bump-bigmi-bitcoin-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@lifi/widget-provider-bitcoin': patch
'@lifi/widget-light': minor
---

Require `@bigmi/client` 0.10.4, `@bigmi/core` 0.9.2 and `@bigmi/react` 0.9.4. The client release detects BitKeep when it injects only as `window.unisat`, which connector-backed wallet detection needs in order not to narrow. The core release reports a declined confirmation as a user rejection even when the wallet sends no rejection code, so a cancelled MetaMask Bitcoin signature now reads "Signature required" instead of "Unknown Error". The 0.10.4 client additionally fixes Binance detection when `window.binancew3w` carries no bitcoin provider, stops MetaMask Bitcoin opening the extension on page load, and keeps the store consistent when a wallet's own `disconnect()` throws.
8 changes: 8 additions & 0 deletions .changeset/metamask-bitcoin-by-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@lifi/widget-provider-bitcoin': minor
'@lifi/wallet-management': patch
---

Offer MetaMask Bitcoin by default. `createDefaultBigmiConfig` now includes the `metamask()` connector alongside the other eleven, so integrators no longer opt in.

This adds no dependency. The connector reaches MetaMask through the Wallet Standard registry, which the extension populates itself, and it imports only `@bigmi/core` and `@wallet-standard/app` — both already present. `@metamask/bitcoin-wallet-standard` and `@metamask/multichain-api-client` existed solely for the manual registration this replaces and are gone; `@metamask/connect-evm` stays, because `wagmi`'s EVM `metaMask()` connector imports it dynamically. The playground bundle is ~41 KB smaller.
9 changes: 9 additions & 0 deletions .changeset/wallet-ecosystem-default-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@lifi/wallet-management': minor
---

Order a multichain wallet's ecosystems Ethereum, Solana, Sui, Bitcoin, Tron, Stellar, via an explicit `defaultWalletEcosystemsOrder` that is exported so integrators can read it.

**This changes the displayed order.** It was previously a side effect of the sequence the wallet lists were combined in — Ethereum, Bitcoin, Solana, Sui, Tron, Stellar — so Bitcoin moves from second to fourth for every multichain wallet you do not name in `walletEcosystemsOrder`.

`walletConfig.walletEcosystemsOrder` still wins for the wallets it names, and now only needs the ecosystems you want to move — anything left out follows the default instead of falling back to the list-building order.
2 changes: 2 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
run: pnpm build
- name: Check types
run: pnpm check:types
- name: Test
run: pnpm test

changesets:
name: Changesets
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Test

on:
pull_request:
types: [opened, reopened, synchronize]
branches: [main]
paths:
- 'packages/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.github/workflows/test.yaml'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install dependencies
uses: ./.github/actions/pnpm-install
- name: Test
run: pnpm test
6 changes: 3 additions & 3 deletions examples/dynamic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
"preview": "vite preview"
},
"dependencies": {
"@bigmi/client": "^0.10.2",
"@bigmi/core": "^0.9.1",
"@bigmi/react": "^0.9.2",
"@bigmi/client": "^0.10.4",
"@bigmi/core": "^0.9.2",
"@bigmi/react": "^0.9.4",
"@dynamic-labs/bitcoin": "5.8.0",
"@dynamic-labs/ethereum": "5.8.0",
"@dynamic-labs/ethereum-aa": "5.8.0",
Expand Down
6 changes: 3 additions & 3 deletions examples/reown/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
"@bigmi/client": "^0.10.2",
"@bigmi/core": "^0.9.1",
"@bigmi/react": "^0.9.2",
"@bigmi/client": "^0.10.4",
"@bigmi/core": "^0.9.2",
"@bigmi/react": "^0.9.4",
"@lifi/sdk": "^4.7.0",
"@lifi/wallet-management": "4.2.0",
"@lifi/widget": "4.6.0",
Expand Down
2 changes: 1 addition & 1 deletion examples/vite-iframe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"@bigmi/react": "^0.9.2",
"@bigmi/react": "^0.9.4",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@lifi/sdk": "^4.7.0",
Expand Down
1 change: 0 additions & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"@base-org/account",
"@coinbase/wallet-sdk",
"@metamask/connect-evm",
"@metamask/connect-multichain",
"@walletconnect/ethereum-provider"
]
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"check:write": "biome check --write",
"check:write:unsafe": "biome check --write --unsafe",
"check:types": "pnpm -r --parallel check:types",
"test": "pnpm -r --parallel --filter \"./packages/**\" test",
"check:circular-deps": "pnpm -r --parallel check:circular-deps",
"link:bigmi": "node scripts/linkBigmi.js link && pnpm install",
"unlink:bigmi": "node scripts/linkBigmi.js unlink && pnpm install",
Expand Down
4 changes: 3 additions & 1 deletion packages/wallet-management/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"build:prerelease": "node ../../scripts/prerelease.js && cpy '../../README.md' .",
"build:postrelease": "node ../../scripts/postrelease.js && rm -rf README.md",
"clean": "rm -rf dist",
"test": "vitest run",
"check:types": "tsc --noEmit",
"check:circular-deps": "madge --circular $(find ./src -name '*.ts' -o -name '*.tsx')",
"check:circular-deps-graph": "madge --circular $(find ./src -name '*.ts' -o -name '*.tsx') --image graph.svg"
Expand Down Expand Up @@ -59,7 +60,8 @@
"madge": "^8.0.0",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"typescript": "^7.0.2"
"typescript": "^7.0.2",
"vitest": "^5.0.0"
},
"peerDependencies": {
"@tanstack/react-query": ">=5.90.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChainId, ChainType } from '@lifi/sdk'
import { isWalletInstalled, useBitcoinContext } from '@lifi/widget-provider'
import { useBitcoinContext } from '@lifi/widget-provider'
import type { JSX } from 'react'
import { useLastConnectedAccount } from '../hooks/useAccount.js'
import { useWalletManagementEvents } from '../hooks/useWalletManagementEvents.js'
Expand All @@ -14,7 +14,6 @@ export const BitcoinListItemButton = ({
ecosystemSelection,
connector,
tagType,
onNotInstalled,
onConnected,
onConnecting,
onError,
Expand All @@ -34,13 +33,6 @@ export const BitcoinListItemButton = ({
}

try {
const identityCheckPassed = isWalletInstalled(
connector.id ?? connector.name
)
if (!identityCheckPassed) {
onNotInstalled?.(connector)
return
}
onConnecting?.()
if (isConnected) {
await disconnect()
Expand Down
88 changes: 88 additions & 0 deletions packages/wallet-management/src/hooks/combineWalletLists.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { ChainType } from '@lifi/sdk'
import type { WalletConnector } from '@lifi/widget-provider'
import { describe, expect, it } from 'vitest'
import {
combineWalletLists,
defaultWalletEcosystemsOrder,
} from './useCombinedWallets.js'

const connector = (name: string): WalletConnector => ({ id: name, name })

const ecosystemsOf = (
wallets: ReturnType<typeof combineWalletLists>,
name: string
) => wallets.find((w) => w.name === name)?.connectors.map((c) => c.chainType)

// One wallet present in every ecosystem, so ordering is the only variable.
const everywhere = (walletEcosystemsOrder?: Record<string, ChainType[]>) =>
combineWalletLists(
[connector('Omni')],
[connector('Omni')],
[connector('Omni')],
[connector('Omni')],
[connector('Omni')],
[connector('Omni')],
walletEcosystemsOrder
)

describe('combineWalletLists ecosystem ordering', () => {
it('orders Ethereum, Solana, Sui, Bitcoin, Tron, Stellar by default', () => {
expect(ecosystemsOf(everywhere(), 'Omni')).toEqual([
ChainType.EVM,
ChainType.SVM,
ChainType.MVM,
ChainType.UTXO,
ChainType.TVM,
ChainType.STL,
])
})

it('matches the exported default order', () => {
expect(ecosystemsOf(everywhere(), 'Omni')).toEqual(
defaultWalletEcosystemsOrder
)
})

it('lets a per-wallet order win over the default', () => {
const order = ecosystemsOf(
everywhere({ Omni: [ChainType.TVM, ChainType.UTXO] }),
'Omni'
)
expect(order?.slice(0, 2)).toEqual([ChainType.TVM, ChainType.UTXO])
})

it('keeps ecosystems absent from a per-wallet order in default order', () => {
const order = ecosystemsOf(everywhere({ Omni: [ChainType.STL] }), 'Omni')
expect(order?.[0]).toBe(ChainType.STL)
expect(order?.slice(1)).toEqual([
ChainType.EVM,
ChainType.SVM,
ChainType.MVM,
ChainType.UTXO,
ChainType.TVM,
])
})

it('applies the default to a wallet with no configured order', () => {
const wallets = combineWalletLists(
[connector('Other')],
[connector('Other')],
[connector('Other')],
[],
[],
[],
{ Omni: [ChainType.TVM] }
)
expect(ecosystemsOf(wallets, 'Other')).toEqual([
ChainType.EVM,
ChainType.SVM,
ChainType.UTXO,
])
})

it('does not mutate the caller-supplied order array', () => {
const order = [ChainType.TVM, ChainType.UTXO]
everywhere({ Omni: order })
expect(order).toEqual([ChainType.TVM, ChainType.UTXO])
})
})
46 changes: 29 additions & 17 deletions packages/wallet-management/src/hooks/useCombinedWallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,20 @@ export type CombinedWallet = {

const normalizeName = (name: string) => name.split(' ')[0].toLowerCase().trim()

const combineWalletLists = (
// Frozen: it is handed to the comparator by reference, so a consumer that
// mutated the export would reorder every wallet in the picker.
export const defaultWalletEcosystemsOrder: readonly ChainType[] = Object.freeze(
[
ChainType.EVM,
ChainType.SVM,
ChainType.MVM,
ChainType.UTXO,
ChainType.TVM,
ChainType.STL,
]
)

export const combineWalletLists = (
ethereumConnectorList: WalletConnector[],
bitcoinConnectorList: WalletConnector[],
solanaWalletList: WalletConnector[],
Expand Down Expand Up @@ -128,21 +141,20 @@ const combineWalletLists = (
walletMap.set(normalizedName, existing)
})

let combinedWallets = Array.from(walletMap.values())
if (walletEcosystemsOrder) {
combinedWallets = combinedWallets.map((wallet) => {
const order = walletEcosystemsOrder[wallet.name]
if (order) {
return {
...wallet,
connectors: wallet.connectors.sort((a, b) =>
walletEcosystemsComparator(a, b, order)
),
}
}
return wallet
})
}
const combinedWallets = Array.from(walletMap.values()).map((wallet) => {
const configured = walletEcosystemsOrder?.[wallet.name]
// Anything the consumer left out still follows the default, so a partial
// order cannot leak the list-building order back in.
const order = configured
? [...new Set([...configured, ...defaultWalletEcosystemsOrder])]
: defaultWalletEcosystemsOrder
return {
...wallet,
connectors: [...wallet.connectors].sort((a, b) =>
walletEcosystemsComparator(a, b, order)
),
}
})
combinedWallets.sort(walletComparator)

return combinedWallets
Expand Down Expand Up @@ -206,7 +218,7 @@ const walletComparator = (a: CombinedWallet, b: CombinedWallet) => {
const walletEcosystemsComparator = (
a: CombinedWalletConnector,
b: CombinedWalletConnector,
order: ChainType[]
order: readonly ChainType[]
) => {
if (!order.length) {
return 0
Expand Down
1 change: 1 addition & 0 deletions packages/wallet-management/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './hooks/useAccount.js'
export * from './hooks/useAccountDisconnect.js'
export {
type CombinedWallet,
defaultWalletEcosystemsOrder,
useCombinedWallets,
} from './hooks/useCombinedWallets.js'
export {
Expand Down
6 changes: 3 additions & 3 deletions packages/widget-light/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"check:types": "tsc --noEmit",
"check:circular-deps": "madge --circular $(find ./src -name '*.ts' -o -name '*.tsx')",
"check:circular-deps-graph": "madge --circular $(find ./src -name '*.ts' -o -name '*.tsx') --image graph.svg",
"test": "vitest run"
"test": "vitest run --passWithNoTests"
},
"author": "Eugene Chybisov <eugene@li.finance>",
"homepage": "https://github.com/lifinance/widget",
Expand Down Expand Up @@ -60,8 +60,8 @@
"vitest": "^5.0.0"
},
"peerDependencies": {
"@bigmi/client": "^0.10.1",
"@bigmi/react": "^0.9.1",
"@bigmi/client": "^0.10.4",
"@bigmi/react": "^0.9.4",
"@mysten/dapp-kit-react": "^2.1.3",
"@wagmi/core": "^3.5.0",
"@wallet-standard/base": "^1.1.0",
Expand Down
9 changes: 3 additions & 6 deletions packages/widget-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
},
"dependencies": {
"@base-org/account": "~2.5.10",
"@bigmi/client": "^0.10.2",
"@bigmi/core": "^0.9.1",
"@bigmi/react": "^0.9.2",
"@bigmi/client": "^0.10.4",
"@bigmi/core": "^0.9.2",
"@bigmi/react": "^0.9.4",
"@coinbase/wallet-sdk": "~4.3.7",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
Expand All @@ -35,10 +35,7 @@
"@lifi/widget-provider-sui": "workspace:*",
"@lifi/widget-provider-transak": "workspace:*",
"@lifi/widget-provider-tron": "workspace:*",
"@metamask/bitcoin-wallet-standard": "^1.3.0",
"@metamask/connect-evm": "^2.1.1",
"@metamask/connect-multichain": "^1.2.0",
"@metamask/multichain-api-client": "^0.12.0",
"@mui/icons-material": "^9.4.0",
"@mui/material": "^9.4.0",
"@mui/system": "^9.4.0",
Expand Down
Loading
Loading