feat: add XBullWallet adapter implementation (Closes #488) - #579
feat: add XBullWallet adapter implementation (Closes #488)#579waterWang wants to merge 1 commit into
Conversation
Add XBullWallet to wallet.ts, alongside FreighterWallet and LobstrWallet, implementing the full WalletAdapter interface (isInstalled, isAuthorized, connect, sign) for the xBull mobile wallet via window.xBullSDK. xBull has no passive site-permission query, so isAuthorized mirrors the LobstrWallet stored-key pattern (sessionStorage signal written on connect). sign passes the networkPassphrase through to signXDR. Closes: drydocs#488 Signed-off-by: waterWang <water.wang@users.noreply.github.com>
|
Someone is attempting to deploy a commit to the Collins' projects Team on Vercel. A member of the Team first needs to authorize it. |
collinsezedike
left a comment
There was a problem hiding this comment.
CI has a real failure: Lint & Typecheck is failing on pnpm format:check, apps/web/src/__tests__/lib/xbull-wallet.test.ts and apps/web/src/lib/wallet.ts both have Prettier formatting issues. Run pnpm --filter @meridian/web format (or prettier --write) and push.
| async () => { | ||
| const sdk = xbullSdk(); | ||
| if (!sdk) throw new Error("xBull wallet not found"); | ||
| const signedXdr = await sdk.signXDR(xdr, { network: networkPassphrase }); |
There was a problem hiding this comment.
XBullSdk.signXDR's own type (a few lines up) declares an optional publicKey in its options, but this call only passes network. If a user connects account A (stored, and what Meridian's UI shows as the signer), then switches the active account to B inside the xBull extension without reconnecting, and xBull signs with whichever account is currently active rather than erroring, the returned signature comes from B while the app still believes and displays A as the signer, a silent account mismatch. Passing publicKey: readStoredXbullPublicKey() ?? undefined closes this the same way the stored key already backs isAuthorized().
| return window.sessionStorage.getItem(XBULL_PUBLIC_KEY_STORAGE_KEY); | ||
| } | ||
|
|
||
| function storeXbullPublicKey(publicKey: string): void { |
There was a problem hiding this comment.
This session-stored-public-key pattern, readStoredXbullPublicKey/storeXbullPublicKey plus the isAuthorized/connect bodies that use them, is a byte-for-byte structural copy of LobstrWallet's equivalent (readStoredLobstrPublicKey/storeLobstrPublicKey, lines 116-171), differing only in the storage-key string. A future third wallet needing the same no-passive-permission workaround copies this a third time, and a fix to the pattern itself (key validation, clearing stale keys on disconnect, etc.) has to land in every copy and will silently miss whichever one gets forgotten. Worth factoring into a small shared helper, e.g. createSessionKeyAuth(storageKey) returning { read, store }, callable from both LobstrWallet and XBullWallet.
Summary
Implements
XBullWalletinapps/web/src/lib/wallet.ts, a thirdWalletAdapterimplementation alongsideFreighterWalletandLobstrWallet, targeting the xBull Stellar wallet.xBull is mobile-first (unlike Freighter, which is desktop-only), so it is a real path to reaching the low-end Android users the roadmap targets — the issue explicitly scopes this to the adapter implementation only, with a wallet-picker to follow later.
Implementation
XBullWallet implements WalletAdapterwith all four methods:isInstalled()— checks forwindow.xBullSDK(injected by the xBull browser extension)connect()— callssdk.connect({ canRequestPublicKey: true, canRequestSign: true })thensdk.getPublicKey()isAuthorized()— xBull has no passive site-permission query (noisAllowedequivalent; the only grant flow isconnect(), which prompts). Mirrors theLobstrWalletstored-key pattern: a successfulconnect()writes the public key tosessionStorage(meridian-xbull-public-key), andisAuthorized()treats installed + stored key as authorized. This keepsrevalidate()(which runs on mount and focus) non-prompting.sign()— passesnetworkPassphrasethrough tosdk.signXDR(xdr, { network }), throws"Signing cancelled"on falsy returnwithMockWallet()helper, so Playwright e2e tests can exercise the connect/sign paths without a real extension — same pattern asFreighterWallet/LobstrWallet.Tests
New file
apps/web/src/__tests__/lib/xbull-wallet.test.ts(207 lines), mirroring the structure oflobstr-wallet.test.ts:window.xBullSDK): isInstalled true/false, isAuthorized stored-key semantics (incl. a "never calls connect/getPublicKey passively" guard), connect success + key storage + permissions payload, connect error paths (SDK missing, empty key), sign success, signXDR receives networkPassphrase, sign error paths (SDK missing, cancel, propagated rejection)Verification
pnpm --filter @meridian/web lint— no new lint errorspnpm --filter @meridian/web test— all wallet tests pass (existing Freighter/LOBSTR + new xBull suite)Closes #488