FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

106
node_modules/viem/utils/ens/avatar/parseAvatarRecord.ts generated vendored Normal file
View File

@@ -0,0 +1,106 @@
import type { Client } from '../../../clients/createClient.js'
import type { Transport } from '../../../clients/transports/createTransport.js'
import type { ErrorType } from '../../../errors/utils.js'
import type { Chain } from '../../../types/chain.js'
import type { AssetGatewayUrls } from '../../../types/ens.js'
import {
type GetJsonImageErrorType,
type GetMetadataAvatarUriErrorType,
type GetNftTokenUriErrorType,
getJsonImage,
getMetadataAvatarUri,
getNftTokenUri,
type ParseAvatarUriErrorType,
type ParseNftUriErrorType,
parseAvatarUri,
parseNftUri,
type ResolveAvatarUriErrorType,
resolveAvatarUri,
} from './utils.js'
export type ParseAvatarRecordErrorType =
| ParseNftAvatarUriErrorType
| ParseAvatarUriErrorType
| ErrorType
/*
* @description Parses an ENS avatar record.
*
* @example
* parseAvatarRecord('eip155:1/erc1155:0xb32979486938aa9694bfc898f35dbed459f44424/10063')
* 'https://ipfs.io/ipfs/QmSP4nq9fnN9dAiCj42ug9Wa79rqmQerZXZch82VqpiH7U/image.gif'
*
* @see https://docs.ens.domains/web/avatars
*
*/
export async function parseAvatarRecord<chain extends Chain | undefined>(
client: Client<Transport, chain>,
{
gatewayUrls,
record,
}: {
gatewayUrls?: AssetGatewayUrls | undefined
record: string
},
): Promise<string> {
if (/eip155:/i.test(record))
return parseNftAvatarUri(client, { gatewayUrls, record })
return parseAvatarUri({ uri: record, gatewayUrls })
}
type ParseNftAvatarUriErrorType =
| ParseNftUriErrorType
| GetNftTokenUriErrorType
| ResolveAvatarUriErrorType
| ParseAvatarUriErrorType
| GetJsonImageErrorType
| GetMetadataAvatarUriErrorType
| ErrorType
async function parseNftAvatarUri<chain extends Chain | undefined>(
client: Client<Transport, chain>,
{
gatewayUrls,
record,
}: {
gatewayUrls?: AssetGatewayUrls | undefined
record: string
},
): Promise<string> {
// parse NFT URI into properties
const nft = parseNftUri(record)
// fetch tokenURI from the NFT contract
const nftUri = await getNftTokenUri(client, { nft })
// resolve the URI from the fetched tokenURI
const {
uri: resolvedNftUri,
isOnChain,
isEncoded,
} = resolveAvatarUri({ uri: nftUri, gatewayUrls })
// if the resolved URI is on chain, return the data
if (
isOnChain &&
(resolvedNftUri.includes('data:application/json;base64,') ||
resolvedNftUri.startsWith('{'))
) {
const encodedJson = isEncoded
? // if it is encoded, decode it
atob(resolvedNftUri.replace('data:application/json;base64,', ''))
: // if it isn't encoded assume it is a JSON string, but it could be anything (it will error if it is)
resolvedNftUri
const decoded = JSON.parse(encodedJson)
return parseAvatarUri({ uri: getJsonImage(decoded), gatewayUrls })
}
let uriTokenId = nft.tokenID
if (nft.namespace === 'erc1155')
uriTokenId = uriTokenId.replace('0x', '').padStart(64, '0')
return getMetadataAvatarUri({
gatewayUrls,
uri: resolvedNftUri.replace(/(?:0x)?{id}/, uriTokenId),
})
}

297
node_modules/viem/utils/ens/avatar/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,297 @@
import type { Address } from 'abitype'
import {
type ReadContractErrorType,
readContract,
} from '../../../actions/public/readContract.js'
import type { Client } from '../../../clients/createClient.js'
import type { Transport } from '../../../clients/transports/createTransport.js'
import {
EnsAvatarInvalidMetadataError,
type EnsAvatarInvalidMetadataErrorType,
EnsAvatarInvalidNftUriError,
type EnsAvatarInvalidNftUriErrorType,
EnsAvatarUnsupportedNamespaceError,
type EnsAvatarUnsupportedNamespaceErrorType,
EnsAvatarUriResolutionError,
type EnsAvatarUriResolutionErrorType,
} from '../../../errors/ens.js'
import type { ErrorType } from '../../../errors/utils.js'
import type { Chain } from '../../../types/chain.js'
import type { AssetGatewayUrls } from '../../../types/ens.js'
type UriItem = {
uri: string
isOnChain: boolean
isEncoded: boolean
}
const networkRegex =
/(?<protocol>https?:\/\/[^/]*|ipfs:\/|ipns:\/|ar:\/)?(?<root>\/)?(?<subpath>ipfs\/|ipns\/)?(?<target>[\w\-.]+)(?<subtarget>\/.*)?/
const ipfsHashRegex =
/^(Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,})(\/(?<target>[\w\-.]+))?(?<subtarget>\/.*)?$/
const base64Regex = /^data:([a-zA-Z\-/+]*);base64,([^"].*)/
const dataURIRegex = /^data:([a-zA-Z\-/+]*)?(;[a-zA-Z0-9].*?)?(,)/
type IsImageUriErrorType = ErrorType
/** @internal */
export async function isImageUri(uri: string) {
try {
const res = await fetch(uri, { method: 'HEAD' })
// retrieve content type header to check if content is image
if (res.status === 200) {
const contentType = res.headers.get('content-type')
return contentType?.startsWith('image/')
}
return false
} catch (error: any) {
// if error is not cors related then fail
if (typeof error === 'object' && typeof error.response !== 'undefined') {
return false
}
// fail in NodeJS, since the error is not cors but any other network issue
if (!Object.hasOwn(globalThis, 'Image')) return false
// in case of cors, use image api to validate if given url is an actual image
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
resolve(true)
}
img.onerror = () => {
resolve(false)
}
img.src = uri
})
}
}
type GetGatewayErrorType = ErrorType
/** @internal */
export function getGateway(custom: string | undefined, defaultGateway: string) {
if (!custom) return defaultGateway
if (custom.endsWith('/')) return custom.slice(0, -1)
return custom
}
export type ResolveAvatarUriErrorType =
| GetGatewayErrorType
| EnsAvatarUriResolutionErrorType
| ErrorType
export function resolveAvatarUri({
uri,
gatewayUrls,
}: {
uri: string
gatewayUrls?: AssetGatewayUrls | undefined
}): UriItem {
const isEncoded = base64Regex.test(uri)
if (isEncoded) return { uri, isOnChain: true, isEncoded }
const ipfsGateway = getGateway(gatewayUrls?.ipfs, 'https://ipfs.io')
const arweaveGateway = getGateway(gatewayUrls?.arweave, 'https://arweave.net')
const networkRegexMatch = uri.match(networkRegex)
const {
protocol,
subpath,
target,
subtarget = '',
} = networkRegexMatch?.groups || {}
const isIPNS = protocol === 'ipns:/' || subpath === 'ipns/'
const isIPFS =
protocol === 'ipfs:/' || subpath === 'ipfs/' || ipfsHashRegex.test(uri)
if (uri.startsWith('http') && !isIPNS && !isIPFS) {
let replacedUri = uri
if (gatewayUrls?.arweave)
replacedUri = uri.replace(/https:\/\/arweave.net/g, gatewayUrls?.arweave)
return { uri: replacedUri, isOnChain: false, isEncoded: false }
}
if ((isIPNS || isIPFS) && target) {
return {
uri: `${ipfsGateway}/${isIPNS ? 'ipns' : 'ipfs'}/${target}${subtarget}`,
isOnChain: false,
isEncoded: false,
}
}
if (protocol === 'ar:/' && target) {
return {
uri: `${arweaveGateway}/${target}${subtarget || ''}`,
isOnChain: false,
isEncoded: false,
}
}
let parsedUri = uri.replace(dataURIRegex, '')
if (parsedUri.startsWith('<svg')) {
// if svg, base64 encode
parsedUri = `data:image/svg+xml;base64,${btoa(parsedUri)}`
}
if (parsedUri.startsWith('data:') || parsedUri.startsWith('{')) {
return {
uri: parsedUri,
isOnChain: true,
isEncoded: false,
}
}
throw new EnsAvatarUriResolutionError({ uri })
}
export type GetJsonImageErrorType =
| EnsAvatarInvalidMetadataErrorType
| ErrorType
export function getJsonImage(data: any) {
// validation check for json data, must include one of theses properties
if (
typeof data !== 'object' ||
(!('image' in data) && !('image_url' in data) && !('image_data' in data))
) {
throw new EnsAvatarInvalidMetadataError({ data })
}
return data.image || data.image_url || data.image_data
}
export type GetMetadataAvatarUriErrorType =
| EnsAvatarUriResolutionErrorType
| ParseAvatarUriErrorType
| GetJsonImageErrorType
| ErrorType
export async function getMetadataAvatarUri({
gatewayUrls,
uri,
}: {
gatewayUrls?: AssetGatewayUrls | undefined
uri: string
}): Promise<string> {
try {
const res = await fetch(uri).then((res) => res.json())
const image = await parseAvatarUri({
gatewayUrls,
uri: getJsonImage(res),
})
return image
} catch {
throw new EnsAvatarUriResolutionError({ uri })
}
}
export type ParseAvatarUriErrorType =
| ResolveAvatarUriErrorType
| IsImageUriErrorType
| EnsAvatarUriResolutionErrorType
| ErrorType
export async function parseAvatarUri({
gatewayUrls,
uri,
}: {
gatewayUrls?: AssetGatewayUrls | undefined
uri: string
}): Promise<string> {
const { uri: resolvedURI, isOnChain } = resolveAvatarUri({ uri, gatewayUrls })
if (isOnChain) return resolvedURI
// check if resolvedURI is an image, if it is return the url
const isImage = await isImageUri(resolvedURI)
if (isImage) return resolvedURI
throw new EnsAvatarUriResolutionError({ uri })
}
type ParsedNft = {
chainID: number
namespace: string
contractAddress: Address
tokenID: string
}
export type ParseNftUriErrorType = EnsAvatarInvalidNftUriErrorType | ErrorType
export function parseNftUri(uri_: string): ParsedNft {
let uri = uri_
// parse valid nft spec (CAIP-22/CAIP-29)
// @see: https://github.com/ChainAgnostic/CAIPs/tree/master/CAIPs
if (uri.startsWith('did:nft:')) {
// convert DID to CAIP
uri = uri.replace('did:nft:', '').replace(/_/g, '/')
}
const [reference, asset_namespace, tokenID] = uri.split('/')
const [eip_namespace, chainID] = reference.split(':')
const [erc_namespace, contractAddress] = asset_namespace.split(':')
if (!eip_namespace || eip_namespace.toLowerCase() !== 'eip155')
throw new EnsAvatarInvalidNftUriError({ reason: 'Only EIP-155 supported' })
if (!chainID)
throw new EnsAvatarInvalidNftUriError({ reason: 'Chain ID not found' })
if (!contractAddress)
throw new EnsAvatarInvalidNftUriError({
reason: 'Contract address not found',
})
if (!tokenID)
throw new EnsAvatarInvalidNftUriError({ reason: 'Token ID not found' })
if (!erc_namespace)
throw new EnsAvatarInvalidNftUriError({ reason: 'ERC namespace not found' })
return {
chainID: Number.parseInt(chainID, 10),
namespace: erc_namespace.toLowerCase(),
contractAddress: contractAddress as Address,
tokenID,
}
}
export type GetNftTokenUriErrorType =
| ReadContractErrorType
| EnsAvatarUnsupportedNamespaceErrorType
| ErrorType
export async function getNftTokenUri<chain extends Chain | undefined>(
client: Client<Transport, chain>,
{ nft }: { nft: ParsedNft },
) {
if (nft.namespace === 'erc721') {
return readContract(client, {
address: nft.contractAddress,
abi: [
{
name: 'tokenURI',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'tokenId', type: 'uint256' }],
outputs: [{ name: '', type: 'string' }],
},
],
functionName: 'tokenURI',
args: [BigInt(nft.tokenID)],
})
}
if (nft.namespace === 'erc1155') {
return readContract(client, {
address: nft.contractAddress,
abi: [
{
name: 'uri',
type: 'function',
stateMutability: 'view',
inputs: [{ name: '_id', type: 'uint256' }],
outputs: [{ name: '', type: 'string' }],
},
],
functionName: 'uri',
args: [BigInt(nft.tokenID)],
})
}
throw new EnsAvatarUnsupportedNamespaceError({ namespace: nft.namespace })
}

8
node_modules/viem/utils/ens/encodeLabelhash.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
import type { ErrorType } from '../../errors/utils.js'
import type { Hex } from '../../types/misc.js'
export type EncodeLabelhashErrorType = ErrorType
export function encodeLabelhash(hash: Hex): `[${string}]` {
return `[${hash.slice(2)}]`
}

14
node_modules/viem/utils/ens/encodedLabelToLabelhash.ts generated vendored Normal file
View File

@@ -0,0 +1,14 @@
import type { ErrorType } from '../../errors/utils.js'
import type { Hex } from '../../types/misc.js'
import { type IsHexErrorType, isHex } from '../data/isHex.js'
export type EncodedLabelToLabelhashErrorType = IsHexErrorType | ErrorType
export function encodedLabelToLabelhash(label: string): Hex | null {
if (label.length !== 66) return null
if (label.indexOf('[') !== 0) return null
if (label.indexOf(']') !== 65) return null
const hash = `0x${label.slice(1, 65)}`
if (!isHex(hash)) return null
return hash
}

24
node_modules/viem/utils/ens/errors.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
import { BaseError } from '../../errors/base.js'
import { ContractFunctionRevertedError } from '../../errors/contract.js'
import type { ErrorType } from '../../errors/utils.js'
/** @internal */
export type IsNullUniversalResolverErrorErrorType = ErrorType
/*
* @description Checks if error is a valid null result UniversalResolver error
*/
export function isNullUniversalResolverError(err: unknown): boolean {
if (!(err instanceof BaseError)) return false
const cause = err.walk((e) => e instanceof ContractFunctionRevertedError)
if (!(cause instanceof ContractFunctionRevertedError)) return false
if (cause.data?.errorName === 'HttpError') return true
if (cause.data?.errorName === 'ResolverError') return true
if (cause.data?.errorName === 'ResolverNotContract') return true
if (cause.data?.errorName === 'ResolverNotFound') return true
if (cause.data?.errorName === 'ReverseAddressMismatch') return true
if (cause.data?.errorName === 'UnsupportedResolverProfile') return true
return false
}

33
node_modules/viem/utils/ens/labelhash.ts generated vendored Normal file
View File

@@ -0,0 +1,33 @@
import type { ErrorType } from '../../errors/utils.js'
import {
type StringToBytesErrorType,
stringToBytes,
} from '../encoding/toBytes.js'
import { type BytesToHexErrorType, bytesToHex } from '../encoding/toHex.js'
import { type Keccak256ErrorType, keccak256 } from '../hash/keccak256.js'
import {
type EncodedLabelToLabelhashErrorType,
encodedLabelToLabelhash,
} from './encodedLabelToLabelhash.js'
export type LabelhashErrorType =
| BytesToHexErrorType
| EncodedLabelToLabelhashErrorType
| Keccak256ErrorType
| StringToBytesErrorType
| ErrorType
/**
* @description Hashes ENS label
*
* - Since ENS labels prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS labels](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `labelhash`. You can use the built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this.
*
* @example
* labelhash('eth')
* '0x4f5b812789fc606be1b3b16908db13fc7a9adf7ca72641f84d75b47069d3d7f0'
*/
export function labelhash(label: string) {
const result = new Uint8Array(32).fill(0)
if (!label) return bytesToHex(result)
return encodedLabelToLabelhash(label) || keccak256(stringToBytes(label))
}

View File

@@ -0,0 +1,62 @@
import { batchGatewayAbi } from '../../constants/abis.js'
import { solidityError } from '../../constants/solidity.js'
import type { Hex } from '../../types/misc.js'
import { decodeFunctionData } from '../abi/decodeFunctionData.js'
import { encodeErrorResult } from '../abi/encodeErrorResult.js'
import { encodeFunctionResult } from '../abi/encodeFunctionResult.js'
import type {
CcipRequestErrorType,
CcipRequestParameters,
CcipRequestReturnType,
} from '../ccip.js'
export const localBatchGatewayUrl = 'x-batch-gateway:true'
export async function localBatchGatewayRequest(parameters: {
data: Hex
ccipRequest: (
parameters: CcipRequestParameters,
) => Promise<CcipRequestReturnType>
}): Promise<Hex> {
const { data, ccipRequest } = parameters
const {
args: [queries],
} = decodeFunctionData({ abi: batchGatewayAbi, data })
const failures: boolean[] = []
const responses: Hex[] = []
await Promise.all(
queries.map(async (query, i) => {
try {
responses[i] = query.urls.includes(localBatchGatewayUrl)
? await localBatchGatewayRequest({ data: query.data, ccipRequest })
: await ccipRequest(query)
failures[i] = false
} catch (err) {
failures[i] = true
responses[i] = encodeError(err as CcipRequestErrorType)
}
}),
)
return encodeFunctionResult({
abi: batchGatewayAbi,
functionName: 'query',
result: [failures, responses],
})
}
function encodeError(error: CcipRequestErrorType): Hex {
if (error.name === 'HttpRequestError' && error.status)
return encodeErrorResult({
abi: batchGatewayAbi,
errorName: 'HttpError',
args: [error.status, error.shortMessage],
})
return encodeErrorResult({
abi: [solidityError],
errorName: 'Error',
args: ['shortMessage' in error ? error.shortMessage : error.message],
})
}

52
node_modules/viem/utils/ens/namehash.ts generated vendored Normal file
View File

@@ -0,0 +1,52 @@
import type { ErrorType } from '../../errors/utils.js'
import type { ByteArray } from '../../types/misc.js'
import { type ConcatErrorType, concat } from '../data/concat.js'
import {
type StringToBytesErrorType,
stringToBytes,
type ToBytesErrorType,
toBytes,
} from '../encoding/toBytes.js'
import { type BytesToHexErrorType, bytesToHex } from '../encoding/toHex.js'
import { type Keccak256ErrorType, keccak256 } from '../hash/keccak256.js'
import {
type EncodedLabelToLabelhashErrorType,
encodedLabelToLabelhash,
} from './encodedLabelToLabelhash.js'
export type NamehashErrorType =
| BytesToHexErrorType
| EncodedLabelToLabelhashErrorType
| ToBytesErrorType
| Keccak256ErrorType
| StringToBytesErrorType
| ConcatErrorType
| ErrorType
/**
* @description Hashes ENS name
*
* - Since ENS names prohibit certain forbidden characters (e.g. underscore) and have other validation rules, you likely want to [normalize ENS names](https://docs.ens.domains/contract-api-reference/name-processing#normalising-names) with [UTS-46 normalization](https://unicode.org/reports/tr46) before passing them to `namehash`. You can use the built-in [`normalize`](https://viem.sh/docs/ens/utilities/normalize) function for this.
*
* @example
* namehash('wevm.eth')
* '0x08c85f2f4059e930c45a6aeff9dcd3bd95dc3c5c1cddef6a0626b31152248560'
*
* @link https://eips.ethereum.org/EIPS/eip-137
*/
export function namehash(name: string) {
let result = new Uint8Array(32).fill(0) as ByteArray
if (!name) return bytesToHex(result)
const labels = name.split('.')
// Iterate in reverse order building up hash
for (let i = labels.length - 1; i >= 0; i -= 1) {
const hashFromEncodedLabel = encodedLabelToLabelhash(labels[i])
const hashed = hashFromEncodedLabel
? toBytes(hashFromEncodedLabel)
: keccak256(stringToBytes(labels[i]), 'bytes')
result = keccak256(concat([result, hashed]), 'bytes')
}
return bytesToHex(result)
}

18
node_modules/viem/utils/ens/normalize.ts generated vendored Normal file
View File

@@ -0,0 +1,18 @@
import * as Ens from 'ox/Ens'
import type { ErrorType } from '../../errors/utils.js'
export type NormalizeErrorType = ErrorType
/**
* @description Normalizes ENS name according to ENSIP-15.
*
* @example
* normalize('wevm.eth')
* 'wevm.eth'
*
* @see https://docs.ens.domains/contract-api-reference/name-processing#normalising-names
* @see https://github.com/ensdomains/docs/blob/9edf9443de4333a0ea7ec658a870672d5d180d53/ens-improvement-proposals/ensip-15-normalization-standard.md
*/
export function normalize(name: string) {
return Ens.normalize(name)
}

53
node_modules/viem/utils/ens/packetToBytes.ts generated vendored Normal file
View File

@@ -0,0 +1,53 @@
// Adapted from https://github.com/mafintosh/dns-packet
import type { ErrorType } from '../../errors/utils.js'
import type { ByteArray } from '../../types/misc.js'
import {
type StringToBytesErrorType,
stringToBytes,
} from '../encoding/toBytes.js'
import {
type EncodeLabelhashErrorType,
encodeLabelhash,
} from './encodeLabelhash.js'
import { type LabelhashErrorType, labelhash } from './labelhash.js'
export type PacketToBytesErrorType =
| EncodeLabelhashErrorType
| LabelhashErrorType
| StringToBytesErrorType
| ErrorType
/*
* @description Encodes a DNS packet into a ByteArray containing a UDP payload.
*
* @example
* packetToBytes('awkweb.eth')
* '0x0661776b7765620365746800'
*
* @see https://docs.ens.domains/resolution/names#dns
*
*/
export function packetToBytes(packet: string): ByteArray {
// strip leading and trailing `.`
const value = packet.replace(/^\.|\.$/gm, '')
if (value.length === 0) return new Uint8Array(1)
const bytes = new Uint8Array(stringToBytes(value).byteLength + 2)
let offset = 0
const list = value.split('.')
for (let i = 0; i < list.length; i++) {
let encoded = stringToBytes(list[i])
// if the length is > 255, make the encoded label value a labelhash
// this is compatible with the universal resolver
if (encoded.byteLength > 255)
encoded = stringToBytes(encodeLabelhash(labelhash(list[i])))
bytes[offset] = encoded.length
bytes.set(encoded, offset + 1)
offset += encoded.length + 1
}
if (bytes.byteLength !== offset + 1) return bytes.slice(0, offset + 1)
return bytes
}

23
node_modules/viem/utils/ens/toCoinType.ts generated vendored Normal file
View File

@@ -0,0 +1,23 @@
import {
EnsInvalidChainIdError,
type EnsInvalidChainIdErrorType,
} from '../../errors/ens.js'
import type { ErrorType } from '../../errors/utils.js'
export type ToCoinTypeError = EnsInvalidChainIdErrorType | ErrorType
const SLIP44_MSB = 0x80000000
/**
* @description Converts a chainId to a ENSIP-9 compliant coinType
*
* @example
* toCoinType(10)
* 2147483658n
*/
export function toCoinType(chainId: number): bigint {
if (chainId === 1) return 60n
if (chainId >= SLIP44_MSB || chainId < 0)
throw new EnsInvalidChainIdError({ chainId })
return BigInt((0x80000000 | chainId) >>> 0)
}