feat(widget): bind chainId into bridge sign-in (multi-signer) — D-027-v3 §4 - #13
Merged
Merged
Conversation
added 2 commits
June 1, 2026 18:15
…-v3 §4
The bridge is upgrading sign-in to multi-signer (EOA + ERC-1271 + ERC-6492).
ERC-1271 verification is chain-bound, so the signed message must carry the
chainId or a signature from one chain can be replayed against a verifier on
another. Per D-027-v3 §4 the message format becomes:
${purpose}:${address.toLowerCase()}:${chainId}:${timestamp}
and the POST /auth/sign-in body gains a chainId field. This widget half must
match the bridge byte-for-byte or every sign-in 401s.
- SupportSigner gains optional chainId (the wallet's chain).
- signInWithBridge binds chainId into both the signed message and the body,
sourced from signer.chainId, defaulting to 84532 (Base Sepolia) to match
the widget's existing chain default. New buildSignMessage helper mirrors
the bridge's.
- Privy + Thirdweb adapters carry chainId through.
- Tests assert the new message format, the body.chainId field, the default,
and adapter passthrough. README sign-in step updated.
BREAKING, lockstep with the companion bridge multi-signer PR: they must
merge and deploy together or sign-in fails on a message mismatch.
…t — D-027-v3 §4
Adversarial-review blockers on the multi-signer sign-in (hard cutover):
- Resolve chainId from the LIVE connector at sign time via a
SupportSigner.getChainId resolver, not a cached optional prop. Privy
parses its CAIP-2 chainId string ("eip155:8453" -> 8453) inside the
adapter; Thirdweb reads the active chain id (getChain().id) inside the
adapter. Both read live on every call.
- Remove the DEFAULT_SUPPORT_CHAIN_ID (84532) fallback. chainId is now
MANDATORY on the support sign-in path: if it cannot be resolved to a
finite number, signInWithBridge throws "support sign-in requires a
chainId from the connected wallet" before signing or POSTing.
- The signed message and the POST body both carry the resolved chainId
(number), matching the bridge's 4-field format
${purpose}:${address.toLowerCase()}:${chainId}:${timestamp}.
Tests: assert the POST body chainId equals the connector's chain id;
assert a missing chainId throws (not defaults); symmetric Thirdweb
adapter coverage mirroring Privy; cross-repo GOLDEN-VECTOR assertion of
the exact expected sign-in message string.
Collaborator
Author
|
Pushed
build + 86 node + 69 vitest green. Merge/deploy together with bridge #20. |
…ard-cutover
Re-review fixes on the multi-signer support sign-in path (D-027-v3 §4):
- README §What happens on click: drop the stale 'chainId comes from
signer.chainId defaulting to 84532' text. chainId is resolved live from
the connected wallet via signer.getChainId(), is mandatory, is bound into
the signed message, and sign-in throws before any signature if it cannot
be resolved (no Base Sepolia default).
- bridge/privy/thirdweb chainId guards: Number.isFinite -> Number.isInteger
so a non-integer (e.g. 8453.5) throws instead of being signed/POSTed.
- parsePrivyChainId: reject empty/whitespace tails ('eip155:' and '' both
yield Number === 0 under a finite check); require a positive integer.
- adapters.test.ts: add 'eip155:' and '' cases asserting getChainId rejects.
Collaborator
Author
|
Pushed
build + 71 tests green. Lockstep with bridge #20. |
) The separator before "## Fraud screening (B2B)" was `---cla` (a botched horizontal rule). Replace with a clean `---`. The mixed-case cross-repo golden vector KeccaK also flagged is already present in test/bridge.test.ts (added in the re-review, byte-identical to the bridge suite), so no test change is needed.
gitchadd
marked this pull request as ready for review
June 5, 2026 08:04
Software-Artist-Aash
added a commit
that referenced
this pull request
Jun 7, 2026
…kout gate) #15 bumped package.json to 1.2.1 but didn't update CHANGELOG. Backfill the 1.2.0 entry (offramp v2 #11 + multi-signer sign-in #13, already published) and document 1.2.1 as: support-widget fix (#15) + screening rejection copy + Checkout one-in-flight-order gate. Version stays 1.2.1 (unpublished). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gitchadd
added a commit
that referenced
this pull request
Aug 21, 2026
) The separator before "## Fraud screening (B2B)" was `---cla` (a botched horizontal rule). Replace with a clean `---`. The mixed-case cross-repo golden vector KeccaK also flagged is already present in test/bridge.test.ts (added in the re-review, byte-identical to the bridge suite), so no test change is needed.
vvictor-dev
pushed a commit
to vvictor-dev/widgets
that referenced
this pull request
Aug 22, 2026
* feat(widget): bind chainId into bridge sign-in (multi-signer) — D-027-v3 §4
The bridge is upgrading sign-in to multi-signer (EOA + ERC-1271 + ERC-6492).
ERC-1271 verification is chain-bound, so the signed message must carry the
chainId or a signature from one chain can be replayed against a verifier on
another. Per D-027-v3 §4 the message format becomes:
${purpose}:${address.toLowerCase()}:${chainId}:${timestamp}
and the POST /auth/sign-in body gains a chainId field. This widget half must
match the bridge byte-for-byte or every sign-in 401s.
- SupportSigner gains optional chainId (the wallet's chain).
- signInWithBridge binds chainId into both the signed message and the body,
sourced from signer.chainId, defaulting to 84532 (Base Sepolia) to match
the widget's existing chain default. New buildSignMessage helper mirrors
the bridge's.
- Privy + Thirdweb adapters carry chainId through.
- Tests assert the new message format, the body.chainId field, the default,
and adapter passthrough. README sign-in step updated.
BREAKING, lockstep with the companion bridge multi-signer PR: they must
merge and deploy together or sign-in fails on a message mismatch.
* fix(support): resolve chainId live from connector, drop silent default — D-027-v3 §4
Adversarial-review blockers on the multi-signer sign-in (hard cutover):
- Resolve chainId from the LIVE connector at sign time via a
SupportSigner.getChainId resolver, not a cached optional prop. Privy
parses its CAIP-2 chainId string ("eip155:8453" -> 8453) inside the
adapter; Thirdweb reads the active chain id (getChain().id) inside the
adapter. Both read live on every call.
- Remove the DEFAULT_SUPPORT_CHAIN_ID (84532) fallback. chainId is now
MANDATORY on the support sign-in path: if it cannot be resolved to a
finite number, signInWithBridge throws "support sign-in requires a
chainId from the connected wallet" before signing or POSTing.
- The signed message and the POST body both carry the resolved chainId
(number), matching the bridge's 4-field format
${purpose}:${address.toLowerCase()}:${chainId}:${timestamp}.
Tests: assert the POST body chainId equals the connector's chain id;
assert a missing chainId throws (not defaults); symmetric Thirdweb
adapter coverage mirroring Privy; cross-repo GOLDEN-VECTOR assertion of
the exact expected sign-in message string.
* fix(support): integer chainId guards + reject empty CAIP-2 + README hard-cutover
Re-review fixes on the multi-signer support sign-in path (D-027-v3 §4):
- README §What happens on click: drop the stale 'chainId comes from
signer.chainId defaulting to 84532' text. chainId is resolved live from
the connected wallet via signer.getChainId(), is mandatory, is bound into
the signed message, and sign-in throws before any signature if it cannot
be resolved (no Base Sepolia default).
- bridge/privy/thirdweb chainId guards: Number.isFinite -> Number.isInteger
so a non-integer (e.g. 8453.5) throws instead of being signed/POSTed.
- parsePrivyChainId: reject empty/whitespace tails ('eip155:' and '' both
yield Number === 0 under a finite check); require a positive integer.
- adapters.test.ts: add 'eip155:' and '' cases asserting getChainId rejects.
* docs(widgets): fix malformed `---cla` section rule in README (KeccaK p2pdotme#13)
The separator before "## Fraud screening (B2B)" was `---cla` (a botched
horizontal rule). Replace with a clean `---`. The mixed-case cross-repo golden
vector KeccaK also flagged is already present in test/bridge.test.ts (added in
the re-review, byte-identical to the bridge suite), so no test change is needed.
---------
Co-authored-by: gitchadd <gitchad@icloud.com>
vvictor-dev
pushed a commit
to vvictor-dev/widgets
that referenced
this pull request
Aug 22, 2026
…kout gate) p2pdotme#15 bumped package.json to 1.2.1 but didn't update CHANGELOG. Backfill the 1.2.0 entry (offramp v2 p2pdotme#11 + multi-signer sign-in p2pdotme#13, already published) and document 1.2.1 as: support-widget fix (p2pdotme#15) + screening rejection copy + Checkout one-in-flight-order gate. Version stays 1.2.1 (unpublished). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Binds
chainIdinto the support bridge sign-in, the widget half of the D-027-v3 multi-signer (EOA + ERC-1271 + ERC-6492) upgrade.The bridge is changing its sign-in message format to bind
chainId:where
purposeissupport.p2p.me:sign-in, and thePOST /auth/sign-inbody gains achainIdfield. ERC-1271 verification is chain-bound (it readsisValidSignatureon the wallet contract at a specific chain), so the chainId must be bound into the signed string, not sent as a sidecar, or a Base Sepolia signature replays against the Base verifier. The widget signs and posts that payload, so it MUST match the bridge byte-for-byte or every sign-in 401s.Source of truth:
support/SYMMETRIC_CHATWOOT_DESIGN.md§4.3–4.4.Changes
SupportSignergains optionalchainId(the wallet's chain).signInWithBridgebindschainIdinto both the signed message and the POST body, sourced fromsigner.chainId, defaulting to84532(Base Sepolia) to match the widget's existing chain default acrosscontracts.ts/useOrderStates/ the order machines. New exportedbuildSignMessagehelper mirrors the bridge's, with the address lowercased.chainIdthrough to theSupportSigner....:8453:<ts>),body.chainIdassertion, message-vs-body consistency check, default-to-84532 case, and adapter passthrough. README sign-in step updated to the new format.All customer sign-in callers (
Support,ContactSupport,PaymentHistoryWithSupport) route through the singlesignInWithBridgechokepoint, so binding chainId there covers every path. (The ops sign-in path /opsSessionCache.tsis not onmainyet — it lives onfeat/support-ops-mode— so it is out of scope here and will pick up the same change when that branch lands.)This is a breaking, lockstep change with the companion bridge multi-signer PR. The widget and bridge must merge and deploy together or every sign-in fails on a message mismatch (the bridge recomputes a different message and returns 401). Rollback = revert both.
The bridge side also moves an
eth_callonto the sign-in hot path (1271/6492 verification), which intersects the opensupport bridge rpc fixitem (move off public RPC onto Alchemy).Verification
npm run verifygreen:tsc --noEmit(typecheck) cleantsc --noEmit --project tsconfig.examples.json(examples typecheck) clean