Skip to content

Add disconnect method to freighter-api - #3036

Merged
JakeUrban merged 4 commits into
masterfrom
claude/freighter-api-disconnect-05975m
Oct 12, 2026
Merged

JakeUrban merged 4 commits into
masterfrom
claude/freighter-api-disconnect-05975m

Conversation

@JakeUrban

@JakeUrban JakeUrban commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

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:

  • The dApp calls disconnect(). Freighter does not show a prompt to the user.
  • Freighter removes the domain of the dApp from the allow list. Freighter removes the domain only for the active account on the active network.
  • A dApp can remove only its own domain. Freighter gets the domain from the browser (sender.url), not from the dApp.
  • If the domain is not on the allow list, the method returns no error. The allow list does not change.
  • After an idle lock, the method operates as usual. The session keeps the active public key during an idle lock.
  • Before the first unlock (for example, after a browser restart), the method returns a new error, FreighterApiLockedError. The allow list does not change. In this state, Freighter cannot find the active public key.
  • If an error occurs in storage, the method returns FreighterApiInternalError.
  • If the content script cannot send the message to the extension, the method returns FreighterApiInternalError.
  • Older versions of the extension ignore the new message. In this case, the method returns FreighterApiInternalError after 2 seconds. Without this time limit, the method does not return.
  • Allow list updates now run one at a time. Before this change, two updates at the same time could overwrite each other. For example, a completed disconnect could leave the domain on the allow list. This change also applies to grants and to removal from the popup.

Result after disconnect():

  • isAllowed() returns false.
  • getAddress() returns an empty string.
  • requestAccess() and setAllowed() show the access prompt again.

Changed files:

  • @shared/constants/services.ts: Adds the DISCONNECT message type.
  • @shared/api/external.ts: Adds the disconnect() client function.
  • @shared/api/helpers/extensionMessaging.ts: Adds FreighterApiLockedError and the 2-second time limit for DISCONNECT.
  • @stellar/freighter-api/src/disconnect.ts, index.ts: Adds and exports disconnect().
  • extension/src/background/messageListener/freighterApiMessageListener.ts: Adds the DISCONNECT handler. The handler uses the existing removeAllowListDomain helper. The popup uses the same helper for manual removal.
  • extension/src/background/helpers/account.ts: Adds a queue (withAllowListLock). setAllowListDomain and removeAllowListDomain use 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:

  • MetaMask: wallet_revokePermissions({ eth_accounts: {} }) removes the permission of the site for all accounts. With CAIP-25, it also removes all chains.
  • WalletConnect: disconnect deletes the full session.

Option B: disconnect() removes the domain for all accounts and all networks. This option has these advantages:

  • It operates like MetaMask.
  • It always causes a prompt on the next request.
  • It operates before the first unlock.

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.

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
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-9923a1f894acec692672
Backend: V1 prod + V2 beta (no sandbox configured for @JakeUrban). SDF collaborators only — install instructions in the release description.

@JakeUrban
JakeUrban marked this pull request as ready for review October 9, 2026 15:48
Copilot AI lite review requested due to automatic review settings October 9, 2026 15:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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 DISCONNECT message 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.

Comment thread @shared/api/helpers/extensionMessaging.ts
Comment thread @shared/api/external.ts Outdated
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
Copilot AI lite review requested due to automatic review settings October 9, 2026 15:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The background handler has a critical unsynchronized allow-list update race that must be fixed before approval.

1 open finding
3 resolved since last review

🧠 Review effort: Lite

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
Copilot AI lite review requested due to automatic review settings October 9, 2026 16:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Cover disconnect in the export contract test and replace the raw timeout literal with a named constant.

0 open findings

1 resolved since last review

🧠 Review effort: Lite

@piyalbasu piyalbasu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 🎉

@piyalbasu

Copy link
Copy Markdown
Contributor

I also confirmed that this throws an informative error when you to try to call disconnect on an unsupported version of Freighter

Copilot AI lite review requested due to automatic review settings October 12, 2026 18:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Add production error reporting for disconnect failures and document the public API contract.

0 open findings

🧠 Review effort: Lite

@JakeUrban
JakeUrban merged commit e0f36ea into master Oct 12, 2026
12 checks passed
@JakeUrban
JakeUrban deleted the claude/freighter-api-disconnect-05975m branch October 12, 2026 18:36
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.

4 participants