From c716c51724d7c0955c30bb49e303cd6a2fe28140 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Wed, 8 Apr 2026 16:22:41 +0300 Subject: [PATCH 1/3] fix(connect): normalize v3 sequence nodes URLs --- .../connect/src/config/defaultTransports.ts | 34 +++++++++++++++-- .../wagmiConnectors/sequenceV3Connector.ts | 38 +++++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/packages/connect/src/config/defaultTransports.ts b/packages/connect/src/config/defaultTransports.ts index 05596b76f..07bce6da0 100644 --- a/packages/connect/src/config/defaultTransports.ts +++ b/packages/connect/src/config/defaultTransports.ts @@ -1,5 +1,7 @@ import { http, type Chain } from 'viem' +import { getNetwork } from '../utils/networks.js' + const isSequenceNodeUrl = (url: string): boolean => { return url.includes('sequence.app') } @@ -8,10 +10,31 @@ const applyTemplate = (template: string, params: Record): string return Object.entries(params).reduce((result, [key, value]) => result.replaceAll(`{${key}}`, value), template) } +const resolveNetworkName = (chain: Chain, nodesUrl: string): string => { + if (isSequenceNodeUrl(nodesUrl)) { + return getNetwork(chain.id).name + } + + return (chain as any).shortName ?? chain.name +} + +const withSequenceNetworkPath = (url: string, networkName: string): string => { + const cleanUrl = url.endsWith('/') ? url.slice(0, -1) : url + const pathSegments = cleanUrl.split('/').filter(Boolean) + const hasNetworkPath = + pathSegments[pathSegments.length - 1] === networkName || pathSegments[pathSegments.length - 2] === networkName + + if (hasNetworkPath) { + return cleanUrl + } + + return `${cleanUrl}/${networkName}` +} + const appendAccessKey = (url: string, accessKey: string): string => { const cleanUrl = url.endsWith('/') ? url.slice(0, -1) : url - if (url.endsWith(accessKey)) { - return url + if (cleanUrl.endsWith(accessKey)) { + return cleanUrl } return `${cleanUrl}/${accessKey}` @@ -21,7 +44,12 @@ export const getDefaultTransports = (chains: readonly [Chain, ...Chain[]], proje return Object.fromEntries( chains.map(chain => { const resolvedNodesUrl = nodesUrl - ? applyTemplate(nodesUrl, { network: (chain as any).shortName ?? chain.name }) + ? (() => { + const networkName = resolveNetworkName(chain, nodesUrl) + const templatedUrl = applyTemplate(nodesUrl, { network: networkName }) + + return isSequenceNodeUrl(nodesUrl) ? withSequenceNetworkPath(templatedUrl, networkName) : templatedUrl + })() : chain.rpcUrls.default.http[0] if (projectAccessKey && resolvedNodesUrl && isSequenceNodeUrl(resolvedNodesUrl)) { diff --git a/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts b/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts index 61eb980fd..d92f403bd 100644 --- a/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts +++ b/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts @@ -94,14 +94,16 @@ export function sequenceV3Wallet(params: BaseSequenceV3ConnectorOptions) { [LocalStorageKey.V3ActiveLoginType]: string } + const normalizedNodesUrl = normalizeSequenceNodesUrl(params.nodesUrl) + const client = new DappClient(params.walletUrl, params.dappOrigin, params.projectAccessKey, { - nodesUrl: params.nodesUrl, + nodesUrl: normalizedNodesUrl, relayerUrl: params.relayerUrl }) const provider = new SequenceV3Provider( client, params.defaultNetwork, - params.nodesUrl, + normalizedNodesUrl, params.projectAccessKey, params.loginType, params.explicitSessionParams ? createExplicitSessionConfig(params.explicitSessionParams) : undefined, @@ -240,6 +242,24 @@ export function sequenceV3Wallet(params: BaseSequenceV3ConnectorOptions) { }) } +const normalizeSequenceNodesUrl = (nodesUrl?: string) => { + if (!nodesUrl || !nodesUrl.includes('sequence.app')) { + return nodesUrl + } + + const cleanUrl = nodesUrl.endsWith('/') ? nodesUrl.slice(0, -1) : nodesUrl + const pathSegments = cleanUrl.split('/').filter(Boolean) + const hasTemplate = cleanUrl.includes('{network}') + const hasNetworkPath = + pathSegments[pathSegments.length - 1] === '{network}' || pathSegments[pathSegments.length - 2] === '{network}' + + if (hasTemplate || hasNetworkPath) { + return cleanUrl + } + + return `${cleanUrl}/{network}` +} + sequenceV3Wallet.type = 'sequence-v3-wallet' as const export class SequenceV3Provider implements EIP1193Provider { @@ -269,7 +289,7 @@ export class SequenceV3Provider implements EIP1193Provider { constructor( private client: DappClient, defaultNetwork: number, - nodesUrl = 'https://nodes.sequence.app', + nodesUrl = 'https://nodes.sequence.app/{network}', projectAccessKey: string, loginType?: SequenceV3LoginType, initialSessionConfig?: ExplicitSessionConfig, @@ -703,7 +723,17 @@ const getRpcUrl = (nodesUrl: string, projectAccessKey: string, networkName: stri let url = applyTemplate(nodesUrl, { network: networkName }) if (nodesUrl.includes('sequence')) { - url = `${url}/${projectAccessKey}` + const cleanUrl = url.endsWith('/') ? url.slice(0, -1) : url + const pathSegments = cleanUrl.split('/').filter(Boolean) + const hasNetworkPath = + pathSegments[pathSegments.length - 1] === networkName || pathSegments[pathSegments.length - 2] === networkName + const withNetwork = hasNetworkPath ? cleanUrl : `${cleanUrl}/${networkName}` + + if (withNetwork.endsWith(`/${projectAccessKey}`)) { + return withNetwork + } + + url = `${withNetwork}/${projectAccessKey}` } return url From e43c0dfb3564449b75f5f8148ed2941537ad9e19 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Wed, 8 Apr 2026 16:38:08 +0300 Subject: [PATCH 2/3] fix(connect): validate sequence nodes url contract --- .../connect/src/config/defaultTransports.ts | 24 +++++++++------ .../wagmiConnectors/sequenceV3Connector.ts | 25 ++-------------- .../wagmiConnectors/sequenceWaasConnector.ts | 4 +-- packages/connect/src/utils/helpers.ts | 29 +++++++++++++++++++ 4 files changed, 49 insertions(+), 33 deletions(-) diff --git a/packages/connect/src/config/defaultTransports.ts b/packages/connect/src/config/defaultTransports.ts index 07bce6da0..46662368b 100644 --- a/packages/connect/src/config/defaultTransports.ts +++ b/packages/connect/src/config/defaultTransports.ts @@ -1,5 +1,6 @@ import { http, type Chain } from 'viem' +import { normalizeSequenceNodesUrl } from '../utils/helpers.js' import { getNetwork } from '../utils/networks.js' const isSequenceNodeUrl = (url: string): boolean => { @@ -12,19 +13,20 @@ const applyTemplate = (template: string, params: Record): string const resolveNetworkName = (chain: Chain, nodesUrl: string): string => { if (isSequenceNodeUrl(nodesUrl)) { - return getNetwork(chain.id).name + try { + return getNetwork(chain.id).name + } catch { + return (chain as any).shortName ?? chain.name + } } return (chain as any).shortName ?? chain.name } -const withSequenceNetworkPath = (url: string, networkName: string): string => { +const withSequenceNetworkPath = (url: string, networkName: string, hasTemplate: boolean): string => { const cleanUrl = url.endsWith('/') ? url.slice(0, -1) : url - const pathSegments = cleanUrl.split('/').filter(Boolean) - const hasNetworkPath = - pathSegments[pathSegments.length - 1] === networkName || pathSegments[pathSegments.length - 2] === networkName - if (hasNetworkPath) { + if (hasTemplate) { return cleanUrl } @@ -45,10 +47,14 @@ export const getDefaultTransports = (chains: readonly [Chain, ...Chain[]], proje chains.map(chain => { const resolvedNodesUrl = nodesUrl ? (() => { - const networkName = resolveNetworkName(chain, nodesUrl) - const templatedUrl = applyTemplate(nodesUrl, { network: networkName }) + const normalizedNodesUrl = normalizeSequenceNodesUrl(nodesUrl) ?? nodesUrl + const networkName = resolveNetworkName(chain, normalizedNodesUrl) + const hasTemplate = normalizedNodesUrl.includes('{network}') + const templatedUrl = applyTemplate(normalizedNodesUrl, { network: networkName }) - return isSequenceNodeUrl(nodesUrl) ? withSequenceNetworkPath(templatedUrl, networkName) : templatedUrl + return isSequenceNodeUrl(normalizedNodesUrl) + ? withSequenceNetworkPath(templatedUrl, networkName, hasTemplate) + : templatedUrl })() : chain.rpcUrls.default.http[0] diff --git a/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts b/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts index d92f403bd..b8651591c 100644 --- a/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts +++ b/packages/connect/src/connectors/wagmiConnectors/sequenceV3Connector.ts @@ -24,6 +24,7 @@ import { createConnector, type Connector } from 'wagmi' import { LocalStorageKey } from '../../constants/localStorage.js' import type { EthAuthSettings } from '../../types.js' +import { normalizeSequenceNodesUrl } from '../../utils/helpers.js' import { getNetwork } from '../../utils/networks.js' import { SEQUENCE_VALUE_FORWARDER } from '../../utils/session/constants.js' import { createContractPermission, createExplicitSessionConfig } from '../../utils/session/index.js' @@ -242,24 +243,6 @@ export function sequenceV3Wallet(params: BaseSequenceV3ConnectorOptions) { }) } -const normalizeSequenceNodesUrl = (nodesUrl?: string) => { - if (!nodesUrl || !nodesUrl.includes('sequence.app')) { - return nodesUrl - } - - const cleanUrl = nodesUrl.endsWith('/') ? nodesUrl.slice(0, -1) : nodesUrl - const pathSegments = cleanUrl.split('/').filter(Boolean) - const hasTemplate = cleanUrl.includes('{network}') - const hasNetworkPath = - pathSegments[pathSegments.length - 1] === '{network}' || pathSegments[pathSegments.length - 2] === '{network}' - - if (hasTemplate || hasNetworkPath) { - return cleanUrl - } - - return `${cleanUrl}/{network}` -} - sequenceV3Wallet.type = 'sequence-v3-wallet' as const export class SequenceV3Provider implements EIP1193Provider { @@ -724,10 +707,8 @@ const getRpcUrl = (nodesUrl: string, projectAccessKey: string, networkName: stri if (nodesUrl.includes('sequence')) { const cleanUrl = url.endsWith('/') ? url.slice(0, -1) : url - const pathSegments = cleanUrl.split('/').filter(Boolean) - const hasNetworkPath = - pathSegments[pathSegments.length - 1] === networkName || pathSegments[pathSegments.length - 2] === networkName - const withNetwork = hasNetworkPath ? cleanUrl : `${cleanUrl}/${networkName}` + const hasTemplate = nodesUrl.includes('{network}') + const withNetwork = hasTemplate ? cleanUrl : `${cleanUrl}/${networkName}` if (withNetwork.endsWith(`/${projectAccessKey}`)) { return withNetwork diff --git a/packages/connect/src/connectors/wagmiConnectors/sequenceWaasConnector.ts b/packages/connect/src/connectors/wagmiConnectors/sequenceWaasConnector.ts index 376674dc5..ef1130004 100644 --- a/packages/connect/src/connectors/wagmiConnectors/sequenceWaasConnector.ts +++ b/packages/connect/src/connectors/wagmiConnectors/sequenceWaasConnector.ts @@ -21,7 +21,7 @@ import { import { createConnector } from 'wagmi' import { LocalStorageKey } from '../../constants/localStorage.js' -import { normalizeChainId } from '../../utils/helpers.js' +import { normalizeChainId, normalizeSequenceNodesUrl } from '../../utils/helpers.js' import { allNetworks } from '../../utils/networks.js' import { getPkcePair, getXOauthUrl } from '../X/XAuth.js' @@ -87,7 +87,7 @@ export function sequenceWaasWallet(params: BaseSequenceWaasConnectorOptions) { [LocalStorageKey.WaasSignInEmail]: string } - const nodesUrl = params.nodesUrl ?? 'https://nodes.sequence.app' + const nodesUrl = normalizeSequenceNodesUrl(params.nodesUrl) ?? 'https://nodes.sequence.app' const showConfirmationModal = params.enableConfirmationModal ?? false diff --git a/packages/connect/src/utils/helpers.ts b/packages/connect/src/utils/helpers.ts index 93022c2ff..1c7ab011c 100644 --- a/packages/connect/src/utils/helpers.ts +++ b/packages/connect/src/utils/helpers.ts @@ -146,3 +146,32 @@ export const normalizeChainId = (chainId: string | number | bigint | { chainId: } return chainId } + +export const isSequenceUrl = (url?: string): url is string => { + return !!url && url.includes('sequence.app') +} + +export const normalizeSequenceNodesUrl = (nodesUrl?: string) => { + if (!nodesUrl) { + return nodesUrl + } + + const cleanUrl = nodesUrl.endsWith('/') ? nodesUrl.slice(0, -1) : nodesUrl + + if (!isSequenceUrl(cleanUrl)) { + return cleanUrl + } + + if (cleanUrl.includes('{network}')) { + return cleanUrl + } + + const pathSegments = cleanUrl.split('/').filter(Boolean) + const hasPathBeyondHost = pathSegments.length > 2 + + if (hasPathBeyondHost) { + throw new Error('Invalid nodesUrl: Sequence nodesUrl must be a bare host or include "{network}".') + } + + return `${cleanUrl}/{network}` +} From 9d618e791877affecf97669262540dbd8c7bf016 Mon Sep 17 00:00:00 2001 From: Tolgahan Date: Wed, 8 Apr 2026 16:57:50 +0300 Subject: [PATCH 3/3] fix(connect): preserve waas nodes url handling --- .../src/connectors/wagmiConnectors/sequenceWaasConnector.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/connect/src/connectors/wagmiConnectors/sequenceWaasConnector.ts b/packages/connect/src/connectors/wagmiConnectors/sequenceWaasConnector.ts index ef1130004..376674dc5 100644 --- a/packages/connect/src/connectors/wagmiConnectors/sequenceWaasConnector.ts +++ b/packages/connect/src/connectors/wagmiConnectors/sequenceWaasConnector.ts @@ -21,7 +21,7 @@ import { import { createConnector } from 'wagmi' import { LocalStorageKey } from '../../constants/localStorage.js' -import { normalizeChainId, normalizeSequenceNodesUrl } from '../../utils/helpers.js' +import { normalizeChainId } from '../../utils/helpers.js' import { allNetworks } from '../../utils/networks.js' import { getPkcePair, getXOauthUrl } from '../X/XAuth.js' @@ -87,7 +87,7 @@ export function sequenceWaasWallet(params: BaseSequenceWaasConnectorOptions) { [LocalStorageKey.WaasSignInEmail]: string } - const nodesUrl = normalizeSequenceNodesUrl(params.nodesUrl) ?? 'https://nodes.sequence.app' + const nodesUrl = params.nodesUrl ?? 'https://nodes.sequence.app' const showConfirmationModal = params.enableConfirmationModal ?? false