Repository navigation
Add disconnect method to freighter-api - #3036
Conversation
Lets a connected dApp revoke its own access without a confirmation prompt. The calling domain is removed from the allowlist for the active account on the active network, so the next requestAccess or setAllowed call prompts the user again. Returns a locked error when the wallet is locked, since the active public key isn't known then. Requests from versions of freighter-api that include disconnect resolve with an error after 2s against older extensions, which drop the unknown message type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M6pbgqeBtJ7cL5biPuxBJ1
|
PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-9923a1f894acec692672 |
There was a problem hiding this comment.
🟡 Changes recommended
Critical compilation and locked-wallet handling issues, plus transport-error mapping, remain unresolved.
3 open findings
What changed in this PR
Adds disconnect() to Freighter API, removing a dApp’s active account/network allow-list entry.
Changes:
- Adds the
DISCONNECTmessage and SDK export. - Adds background allow-list removal handling.
- Adds timeout, locked-wallet errors, and tests.
| File | Description |
|---|---|
extension/src/background/messageListener/freighterApiMessageListener.ts |
Handles disconnect requests and allow-list removal. |
extension/src/background/messageListener/__tests__/disconnect.test.ts |
Tests disconnect behavior. |
@stellar/freighter-api/src/index.ts |
Exports disconnect(). |
@stellar/freighter-api/src/disconnect.ts |
Implements the public API method. |
@stellar/freighter-api/src/__tests__/disconnect.test.js |
Tests SDK responses. |
@shared/constants/services.ts |
Adds the disconnect message type. |
@shared/api/helpers/extensionMessaging.ts |
Adds timeout and error handling. |
@shared/api/helpers/__tests__/extensionMessaging.test.ts |
Tests messaging timeout behavior. |
@shared/api/external.ts |
Sends disconnect requests and maps responses. |
🧠 Review effort: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The content script returns only `error` when it can't reach the background. Map that to FreighterApiInternalError so the dApp doesn't see a failed disconnect as a success. Also correct the locked-state comment: an idle lock keeps the session public key, so disconnect still removes the domain for that account. Only a session that hasn't been unlocked yet has no public key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M6pbgqeBtJ7cL5biPuxBJ1
setAllowListDomain and removeAllowListDomain read the whole allowlist and write it back. Two updates that overlap can overwrite each other, so a disconnect can report success while the domain stays allowed. Run the updates one at a time through an in-memory queue. All allowlist writers run in the background, so one queue covers grants, popup removal, and disconnect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M6pbgqeBtJ7cL5biPuxBJ1
piyalbasu
left a comment
There was a problem hiding this comment.
This is looking good. I tested it with your dev docs update as well as an independent dapp to make sure everything is working here. Can confirm that the UI updates appropriately when a dapp is disconnected 🎉
|
I also confirmed that this throws an informative error when you to try to call |


What
This PR adds the
disconnect()method to@stellar/freighter-api. A dApp uses this method to remove its access to the user's public key.Functional changes:
disconnect(). Freighter does not show a prompt to the user.sender.url), not from the dApp.FreighterApiLockedError. The allow list does not change. In this state, Freighter cannot find the active public key.FreighterApiInternalError.FreighterApiInternalError.FreighterApiInternalErrorafter 2 seconds. Without this time limit, the method does not return.Result after
disconnect():isAllowed()returnsfalse.getAddress()returns an empty string.requestAccess()andsetAllowed()show the access prompt again.Changed files:
@shared/constants/services.ts: Adds theDISCONNECTmessage type.@shared/api/external.ts: Adds thedisconnect()client function.@shared/api/helpers/extensionMessaging.ts: AddsFreighterApiLockedErrorand the 2-second time limit forDISCONNECT.@stellar/freighter-api/src/disconnect.ts,index.ts: Adds and exportsdisconnect().extension/src/background/messageListener/freighterApiMessageListener.ts: Adds theDISCONNECThandler. The handler uses the existingremoveAllowListDomainhelper. The popup uses the same helper for manual removal.extension/src/background/helpers/account.ts: Adds a queue (withAllowListLock).setAllowListDomainandremoveAllowListDomainuse the queue.Why
DTCC asked for a method to disconnect a dApp. dApps for EVM and Solana wallets usually have a "Disconnect" button. Those wallets do not show a prompt for this action. DTCC requires this: after a disconnect, the next request for the public key shows a prompt to the user.
Open question: scope of the disconnect
This PR uses the same scope as access grants and manual removal: one account on one network.
Known result: the user can give access to the dApp on more than one account or network. After a disconnect, the user can change to one of those other accounts or networks. Then the dApp gets that public key again without a prompt.
Other wallets keep one connection for each site, and a disconnect removes all of it:
wallet_revokePermissions({ eth_accounts: {} })removes the permission of the site for all accounts. With CAIP-25, it also removes all chains.disconnectdeletes the full session.Option B:
disconnect()removes the domain for all accounts and all networks. This option has these advantages:But option B probably also needs a change to how Freighter grants access (one grant for each site). That is a larger change, so this PR does not include it. Please give feedback.