diff --git a/packages/connect/src/config/defaultTransports.ts b/packages/connect/src/config/defaultTransports.ts index 05596b76f..46662368b 100644 --- a/packages/connect/src/config/defaultTransports.ts +++ b/packages/connect/src/config/defaultTransports.ts @@ -1,5 +1,8 @@ import { http, type Chain } from 'viem' +import { normalizeSequenceNodesUrl } from '../utils/helpers.js' +import { getNetwork } from '../utils/networks.js' + const isSequenceNodeUrl = (url: string): boolean => { return url.includes('sequence.app') } @@ -8,10 +11,32 @@ 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)) { + 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, hasTemplate: boolean): string => { + const cleanUrl = url.endsWith('/') ? url.slice(0, -1) : url + + if (hasTemplate) { + 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 +46,16 @@ 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 normalizedNodesUrl = normalizeSequenceNodesUrl(nodesUrl) ?? nodesUrl + const networkName = resolveNetworkName(chain, normalizedNodesUrl) + const hasTemplate = normalizedNodesUrl.includes('{network}') + const templatedUrl = applyTemplate(normalizedNodesUrl, { network: networkName }) + + return isSequenceNodeUrl(normalizedNodesUrl) + ? withSequenceNetworkPath(templatedUrl, networkName, hasTemplate) + : 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..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' @@ -94,14 +95,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, @@ -269,7 +272,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 +706,15 @@ 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 hasTemplate = nodesUrl.includes('{network}') + const withNetwork = hasTemplate ? cleanUrl : `${cleanUrl}/${networkName}` + + if (withNetwork.endsWith(`/${projectAccessKey}`)) { + return withNetwork + } + + url = `${withNetwork}/${projectAccessKey}` } return url 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}` +}