Page MenuHomePhabricator

D13699.id45133.diff
No OneTemporary

D13699.id45133.diff

diff --git a/lib/components/ens-cache-provider.react.js b/lib/components/ens-cache-provider.react.js
--- a/lib/components/ens-cache-provider.react.js
+++ b/lib/components/ens-cache-provider.react.js
@@ -1,6 +1,10 @@
// @flow
+import { addEnsContracts } from '@ensdomains/ensjs';
import * as React from 'react';
+import { createClient } from 'viem';
+// eslint-disable-next-line import/extensions
+import { mainnet } from 'viem/chains';
import type { EthersProvider } from '../types/ethers-types.js';
import { ENSCache } from '../utils/ens-cache.js';
@@ -8,6 +12,8 @@
getENSNames as baseGetENSNames,
type GetENSNames,
} from '../utils/ens-helpers.js';
+import { ENSWrapper } from '../utils/ens-wrapper.js';
+import { getAlchemyMainnetViemTransport } from '../utils/viem-utils.js';
type BaseUserInfo = { +username?: ?string, ... };
@@ -23,21 +29,28 @@
React.createContext<ENSCacheContextType>(defaultContext);
type Props = {
- +provider: ?EthersProvider,
+ +ethersProvider: ?EthersProvider,
+ +alchemyKey: ?string,
+children: React.Node,
};
function ENSCacheProvider(props: Props): React.Node {
- const { provider, children } = props;
+ const { ethersProvider, alchemyKey, children } = props;
const context = React.useMemo(() => {
- if (!provider) {
+ if (!ethersProvider) {
return defaultContext;
}
- const ensCache = new ENSCache(provider);
+ const viemTransport = getAlchemyMainnetViemTransport(alchemyKey);
+ const viemClient = createClient({
+ chain: addEnsContracts(mainnet),
+ transport: viemTransport,
+ });
+ const ensWrapper = new ENSWrapper(viemClient, ethersProvider);
+ const ensCache = new ENSCache(ensWrapper);
const getENSNames: GetENSNames = <T: ?BaseUserInfo>(
users: $ReadOnlyArray<T>,
): Promise<T[]> => baseGetENSNames(ensCache, users);
return { ensCache, getENSNames };
- }, [provider]);
+ }, [ethersProvider, alchemyKey]);
return (
<ENSCacheContext.Provider value={context}>
{children}
diff --git a/lib/flow-typed/npm/@ensdomains/ensjs_vx.x.x.js b/lib/flow-typed/npm/@ensdomains/ensjs_vx.x.x.js
new file mode 100644
--- /dev/null
+++ b/lib/flow-typed/npm/@ensdomains/ensjs_vx.x.x.js
@@ -0,0 +1,62 @@
+// flow-typed signature: 3c7fa5a7cb8e52f2d81c14972c6d689f
+// flow-typed version: <<STUB>>/@ensdomains/ensjs_v4.0.1/flow_v0.202.1
+
+declare module '@ensdomains/ensjs' {
+ import type { ViemChain } from 'viem';
+
+ declare export function addEnsContracts(chain: ViemChain): ViemChain;
+}
+
+declare module '@ensdomains/ensjs/public' {
+ import type { ViemClient } from 'viem';
+
+ declare export type BatchHandle<T> = {
+ ...
+ };
+
+ declare export type GetNameResult = {
+ +match: boolean,
+ +name: ?string,
+ +reverseResolverAddress: ?string,
+ +resolverAddress: ?string,
+ ...
+ };
+ declare export var getName:
+ & {
+ +batch: ({ +address: string, ... }) => BatchHandle<GetNameResult>,
+ ...
+ }
+ & (ViemClient, { +address: string, ... }) => Promise<GetNameResult>;
+
+ declare export type GetOwnerResult = {
+ +owner: ?string,
+ +registrant: ?string,
+ +ownershipLevel: ?string,
+ ...
+ };
+ declare export var getOwner:
+ & {
+ +batch: ({ +name: string, ... }) => BatchHandle<GetOwnerResult>,
+ ...
+ }
+ & (ViemClient, { +name: string, ... }) => Promise<GetOwnerResult>;
+
+ declare export type GetRecordsResult = {
+ +texts: $ReadOnlyArray<{
+ +key: string,
+ +value: string,
+ }>,
+ ...
+ };
+ declare export var getRecords:
+ & {
+ +batch: ({ +name: string, +texts: $ReadOnlyArray<string>, ... }) => BatchHandle<GetRecordsResult>,
+ ...
+ }
+ & (ViemClient, { +name: string, +texts: $ReadOnlyArray<string>, ... }) => Promise<GetRecordsResult>;
+
+ declare export function batch<T>(
+ client: ViemClient,
+ ...batchCalls: $ReadOnlyArray<BatchHandle<T>>
+ ): Promise<Array<T>>;
+}
diff --git a/lib/flow-typed/npm/viem_vx.x.x.js b/lib/flow-typed/npm/viem_vx.x.x.js
new file mode 100644
--- /dev/null
+++ b/lib/flow-typed/npm/viem_vx.x.x.js
@@ -0,0 +1,32 @@
+// flow-typed signature: 585e2883477434dc7cfd534337e85dce
+// flow-typed version: <<STUB>>/viem_v2.9.5/flow_v0.202.1
+
+declare module 'viem' {
+
+ declare export type ViemTransport = { ... };
+
+ declare export function http(url?: string): ViemTransport;
+
+ declare export type ViemChain = { ... };
+
+ declare export type ViemCreateClientParams = {
+ +chain: ViemChain,
+ +transport: ViemTransport,
+ ...
+ };
+
+ declare export type ViemClient = {
+ ...
+ };
+
+ declare export function createClient(
+ params: ViemCreateClientParams,
+ ): ViemClient;
+}
+
+declare module 'view/chains' {
+ import type { ViemChain } from 'viem';
+
+ declare export var mainnet: ViemChain;
+ declare export var sepolia: ViemChain;
+}
diff --git a/lib/package.json b/lib/package.json
--- a/lib/package.json
+++ b/lib/package.json
@@ -36,6 +36,7 @@
},
"dependencies": {
"@commapp/olm": "0.2.1",
+ "@ensdomains/ensjs": "^4.0.1",
"@rainbow-me/rainbowkit": "^2.0.7",
"base-64": "^0.1.0",
"dateformat": "^3.0.3",
diff --git a/lib/utils/ens-cache.js b/lib/utils/ens-cache.js
--- a/lib/utils/ens-cache.js
+++ b/lib/utils/ens-cache.js
@@ -1,16 +1,9 @@
// @flow
import namehash from 'eth-ens-namehash';
-import { Contract } from 'ethers';
-import invariant from 'invariant';
-
-import {
- resolverABI,
- resolverAddresses,
- type ReverseRecordsEthersSmartContract,
-} from './reverse-records.js';
+
+import { ENSWrapper } from './ens-wrapper.js';
import sleep from './sleep.js';
-import type { EthersProvider } from '../types/ethers-types.js';
const cacheTimeout = 24 * 60 * 60 * 1000; // one day
const failedQueryCacheTimeout = 5 * 60 * 1000; // five minutes
@@ -56,9 +49,7 @@
// caching behavior consistent across platforms, we instead introduce this
// vanilla JS class that handles querying and caching ENS for all cases.
class ENSCache {
- provider: EthersProvider;
- batchReverseResolverSmartContract: ?ReverseRecordsEthersSmartContract;
- batchReverseResolverSmartContractPromise: Promise<?ReverseRecordsEthersSmartContract>;
+ ensWrapper: ENSWrapper;
// Maps from normalized ETH address to a cache entry for its name
nameQueryCache: Map<string, ENSNameQueryCacheEntry> = new Map();
@@ -67,22 +58,8 @@
// Maps from normalized ETH address to a cache entry for its avatar
avatarQueryCache: Map<string, ENSAvatarQueryCacheEntry> = new Map();
- constructor(provider: EthersProvider) {
- this.provider = provider;
- this.batchReverseResolverSmartContractPromise = (async () => {
- const { chainId } = await provider.getNetwork();
- const reverseRecordsAddress = resolverAddresses[chainId];
- if (reverseRecordsAddress) {
- this.batchReverseResolverSmartContract = new Contract(
- reverseRecordsAddress,
- resolverABI,
- provider,
- );
- } else {
- this.batchReverseResolverSmartContract = null;
- }
- return this.batchReverseResolverSmartContract;
- })();
+ constructor(ensWrapper: ENSWrapper) {
+ this.ensWrapper = ensWrapper;
}
// Getting a name for an ETH address is referred to as "reverse resolution".
@@ -109,7 +86,7 @@
let ensName;
try {
ensName = await Promise.race([
- this.provider.lookupAddress(normalizedETHAddress),
+ this.ensWrapper.getNameForAddress(normalizedETHAddress),
throwOnTimeout(`${normalizedETHAddress}'s name`),
]);
} catch (e) {
@@ -174,39 +151,16 @@
}
const fetchENSNamesPromise = (async () => {
- const {
- batchReverseResolverSmartContract,
- batchReverseResolverSmartContractPromise,
- } = this;
-
- let smartContract;
- if (batchReverseResolverSmartContract) {
- smartContract = batchReverseResolverSmartContract;
- } else if (batchReverseResolverSmartContract !== null) {
- smartContract = await batchReverseResolverSmartContractPromise;
- }
-
- // ReverseRecords smart contract handles checking forward resolution
let ensNames: $ReadOnlyArray<?string>;
- if (smartContract) {
- try {
- const raceResult = await Promise.race([
- smartContract['getNames(address[])'](needFetch),
- throwOnTimeout(`names for ${JSON.stringify(needFetch)}`),
- ]);
- invariant(
- Array.isArray(raceResult),
- 'ReverseRecords smart contract should return array',
- );
- ensNames = raceResult;
- } catch (e) {
- console.log(e);
- ensNames = new Array<?string>(needFetch.length).fill(null);
- }
- } else {
- ensNames = await Promise.all(
- needFetch.map(ethAddress => this.getNameForAddress(ethAddress)),
- );
+ try {
+ const raceResult = await Promise.race([
+ this.ensWrapper.getNamesForAddresses(needFetch),
+ throwOnTimeout(`names for ${JSON.stringify(needFetch)}`),
+ ]);
+ ensNames = raceResult;
+ } catch (e) {
+ console.log(e);
+ ensNames = new Array<?string>(needFetch.length).fill(null);
}
const resultMap = new Map<string, ?string>();
@@ -315,7 +269,7 @@
let ethAddress;
try {
ethAddress = await Promise.race([
- this.provider.resolveName(normalizedENSName),
+ this.ensWrapper.getAddressForName(normalizedENSName),
throwOnTimeout(`${normalizedENSName}'s address`),
]);
} catch (e) {
@@ -398,7 +352,7 @@
let ensAvatar;
try {
ensAvatar = await Promise.race([
- this.provider.getAvatar(ensName),
+ this.ensWrapper.getAvatarURIForName(ensName),
throwOnTimeout(`${normalizedETHAddress}'s avatar`),
]);
} catch (e) {
diff --git a/lib/utils/ens-cache.test.js b/lib/utils/ens-cache.test.js
--- a/lib/utils/ens-cache.test.js
+++ b/lib/utils/ens-cache.test.js
@@ -1,32 +1,74 @@
// @flow
+import { addEnsContracts } from '@ensdomains/ensjs';
import { AlchemyProvider } from 'ethers';
+import { createClient } from 'viem';
+// eslint-disable-next-line import/extensions
+import { mainnet, sepolia } from 'viem/chains';
import { ENSCache } from './ens-cache.js';
+import { ENSWrapper } from './ens-wrapper.js';
+import {
+ getAlchemyMainnetViemTransport,
+ getAlchemySepoliaViemTransport,
+} from './viem-utils.js';
-const provider = new AlchemyProvider('sepolia', process.env.ALCHEMY_API_KEY);
-const ensCache = new ENSCache(provider);
-
-const baseLookupAddress = provider.lookupAddress.bind(provider);
-let timesLookupAddressCalled = 0;
-provider.lookupAddress = (ethAddress: string) => {
- timesLookupAddressCalled++;
- return baseLookupAddress(ethAddress);
-};
-
-const baseResolveName = provider.resolveName.bind(provider);
-let timesResolveNameCalled = 0;
-provider.resolveName = (ensName: string) => {
- timesResolveNameCalled++;
- return baseResolveName(ensName);
-};
-
-const baseGetAvatar = provider.getAvatar.bind(provider);
-let timesGetAvatarCalled = 0;
-provider.getAvatar = (ethAddress: string) => {
- timesGetAvatarCalled++;
- return baseGetAvatar(ethAddress);
-};
+const sepoliaEthersProvider = new AlchemyProvider(
+ 'sepolia',
+ process.env.ALCHEMY_API_KEY,
+);
+
+const sepoliaViemTransport = getAlchemySepoliaViemTransport(
+ process.env.ALCHEMY_API_KEY,
+);
+const sepoliaViemClient = createClient({
+ chain: addEnsContracts(sepolia),
+ transport: sepoliaViemTransport,
+});
+
+const baseSepoliaENSWrapper = new ENSWrapper(
+ sepoliaViemClient,
+ sepoliaEthersProvider,
+);
+
+let timesGetAddressForNameCalled = 0;
+let timesGetNameForAddressCalled = 0;
+let timesGetAvatarURIForNameCalled = 0;
+const sepoliaENSWrapper: ENSWrapper = ({
+ ...baseSepoliaENSWrapper,
+ getAddressForName: (ethAddress: string) => {
+ timesGetAddressForNameCalled++;
+ return baseSepoliaENSWrapper.getAddressForName(ethAddress);
+ },
+ getNameForAddress: (ensName: string) => {
+ timesGetNameForAddressCalled++;
+ return baseSepoliaENSWrapper.getNameForAddress(ensName);
+ },
+ getAvatarURIForName: (ensName: string) => {
+ timesGetAvatarURIForNameCalled++;
+ return baseSepoliaENSWrapper.getAvatarURIForName(ensName);
+ },
+}: any);
+const ensCache = new ENSCache(sepoliaENSWrapper);
+
+const mainnetEthersProvider = new AlchemyProvider(
+ 'mainnet',
+ process.env.ALCHEMY_API_KEY,
+);
+
+const mainnetViemTransport = getAlchemyMainnetViemTransport(
+ process.env.ALCHEMY_API_KEY,
+);
+const mainnetViemClient = createClient({
+ chain: addEnsContracts(mainnet),
+ transport: mainnetViemTransport,
+});
+
+const mainnetENSWrapper = new ENSWrapper(
+ mainnetViemClient,
+ mainnetEthersProvider,
+);
+const mainnetENSCache = new ENSCache(mainnetENSWrapper);
if (!process.env.ALCHEMY_API_KEY) {
// Test only works if we can query blockchain
@@ -52,9 +94,13 @@
const noENSNameAddr = '0xcF986104d869967381dFfAb3A4127bCe6a404362';
+const nfthreatBaseName = 'nfthreat.base.eth';
+const nfthreatBaseAddr = '0x598C91d70e16177defB34CbA95C2f80F551A4ccB';
+
describe('getNameForAddress', () => {
beforeAll(() => {
ensCache.clearCache();
+ mainnetENSCache.clearCache();
});
it('should fail to return ashoat.eth if not in cache', async () => {
if (!process.env.ALCHEMY_API_KEY) {
@@ -70,6 +116,14 @@
const ashoatEthResult = await ensCache.getNameForAddress(ashoatAddr);
expect(ashoatEthResult).toBe(ashoatDotEth);
});
+ it('should return nfthreat.base.eth', async () => {
+ if (!process.env.ALCHEMY_API_KEY) {
+ return;
+ }
+ const nfthreatBaseResult =
+ await mainnetENSCache.getNameForAddress(nfthreatBaseAddr);
+ expect(nfthreatBaseResult).toBe(nfthreatBaseName);
+ }, 10000);
it('should return ashoat.eth if in cache', async () => {
if (!process.env.ALCHEMY_API_KEY) {
return;
@@ -81,12 +135,12 @@
if (!process.env.ALCHEMY_API_KEY) {
return;
}
- const timesLookupAddressCalledBefore = timesLookupAddressCalled;
+ const timesLookupAddressCalledBefore = timesGetAddressForNameCalled;
const ashoatEthResult = await ensCache.getNameForAddress(
ashoatAddr.toUpperCase(),
);
expect(ashoatEthResult).toBe(ashoatDotEth);
- expect(timesLookupAddressCalled).toBe(timesLookupAddressCalledBefore);
+ expect(timesGetAddressForNameCalled).toBe(timesLookupAddressCalledBefore);
});
it('should dedup simultaneous fetches', async () => {
if (!process.env.ALCHEMY_API_KEY) {
@@ -94,14 +148,16 @@
}
ensCache.clearCache();
- const timesLookupAddressCalledBeforeSingleFetch = timesLookupAddressCalled;
+ const timesLookupAddressCalledBeforeSingleFetch =
+ timesGetAddressForNameCalled;
const ashoatEthResult1 = await ensCache.getNameForAddress(ashoatAddr);
expect(ashoatEthResult1).toBe(ashoatDotEth);
const timesLookupAddressCalledForSingleFetch =
- timesLookupAddressCalled - timesLookupAddressCalledBeforeSingleFetch;
+ timesGetAddressForNameCalled - timesLookupAddressCalledBeforeSingleFetch;
ensCache.clearCache();
- const timesLookupAddressCalledBeforeDoubleFetch = timesLookupAddressCalled;
+ const timesLookupAddressCalledBeforeDoubleFetch =
+ timesGetAddressForNameCalled;
const [ashoatEthResult2, ashoatEthResult3] = await Promise.all([
ensCache.getNameForAddress(ashoatAddr),
ensCache.getNameForAddress(ashoatAddr.toUpperCase()),
@@ -109,7 +165,7 @@
expect(ashoatEthResult2).toBe(ashoatDotEth);
expect(ashoatEthResult3).toBe(ashoatDotEth);
const timesLookupAddressCalledForDoubleFetch =
- timesLookupAddressCalled - timesLookupAddressCalledBeforeDoubleFetch;
+ timesGetAddressForNameCalled - timesLookupAddressCalledBeforeDoubleFetch;
expect(timesLookupAddressCalledForDoubleFetch).toBe(
timesLookupAddressCalledForSingleFetch,
@@ -120,6 +176,7 @@
describe('getNamesForAddresses', () => {
beforeAll(() => {
ensCache.clearCache();
+ mainnetENSCache.clearCache();
});
it('should fail to return ashoat.eth if not in cache', async () => {
if (!process.env.ALCHEMY_API_KEY) {
@@ -135,6 +192,15 @@
const [ashoatEthResult] = await ensCache.getNamesForAddresses([ashoatAddr]);
expect(ashoatEthResult).toBe(ashoatDotEth);
});
+ it('should return nfthreat.base.eth', async () => {
+ if (!process.env.ALCHEMY_API_KEY) {
+ return;
+ }
+ const [nfthreatBaseResult] = await mainnetENSCache.getNamesForAddresses([
+ nfthreatBaseAddr,
+ ]);
+ expect(nfthreatBaseResult).toBe(nfthreatBaseName);
+ }, 10000);
it('should return ashoat.eth if in cache', async () => {
if (!process.env.ALCHEMY_API_KEY) {
return;
@@ -162,7 +228,7 @@
}
ensCache.clearCache();
- const timesLookupAddressCalledBefore = timesLookupAddressCalled;
+ const timesLookupAddressCalledBefore = timesGetAddressForNameCalled;
const [
[ashoatEthResult1, commalphaEthResult1, commbetaEthResult1],
@@ -180,17 +246,11 @@
ensCache.getNameForAddress(commbetaEthAddr),
]);
- const timesLookupAddressCalledAfter = timesLookupAddressCalled;
+ const timesLookupAddressCalledAfter = timesGetAddressForNameCalled;
const timesLookupAddressCalledDuringTest =
timesLookupAddressCalledAfter - timesLookupAddressCalledBefore;
- // These tests are run on the Sepolia testnet, where the ReverseRecords
- // smart contract is not deployed. As a result, we end up needing to call
- // the lookupAddress method (single lookup) once for each address. On
- // mainnet (outside of these tests) this is 0, since the ReverseRecords
- // smart contract lets us batch up our requests, and avoid calling
- // lookupAddress entirely.
- expect(timesLookupAddressCalledDuringTest).toBe(3);
+ expect(timesLookupAddressCalledDuringTest).toBe(0);
expect(ashoatEthResult1).toBe(ashoatDotEth);
expect(commalphaEthResult1).toBe(commalphaDotEth);
@@ -211,6 +271,7 @@
describe('getAddressForName', () => {
beforeAll(() => {
ensCache.clearCache();
+ mainnetENSCache.clearCache();
});
it("should fail to return ashoat.eth's address if not in cache", async () => {
if (!process.env.ALCHEMY_API_KEY) {
@@ -237,10 +298,10 @@
if (!process.env.ALCHEMY_API_KEY) {
return;
}
- const timesResolveNameCalledBefore = timesResolveNameCalled;
+ const timesResolveNameCalledBefore = timesGetNameForAddressCalled;
const ashoatAddrResult = await ensCache.getAddressForName(ashoatDotEth);
expect(ashoatAddrResult).toBe(ashoatAddr);
- expect(timesResolveNameCalled).toBe(timesResolveNameCalledBefore);
+ expect(timesGetNameForAddressCalled).toBe(timesResolveNameCalledBefore);
});
it('should dedup simultaneous fetches', async () => {
if (!process.env.ALCHEMY_API_KEY) {
@@ -248,14 +309,16 @@
}
ensCache.clearCache();
- const timesResolveNameCalledBeforeSingleFetch = timesResolveNameCalled;
+ const timesResolveNameCalledBeforeSingleFetch =
+ timesGetNameForAddressCalled;
const ashoatAddrResult1 = await ensCache.getAddressForName(ashoatDotEth);
expect(ashoatAddrResult1).toBe(ashoatAddr);
const timesResolveNameCalledForSingleFetch =
- timesResolveNameCalled - timesResolveNameCalledBeforeSingleFetch;
+ timesGetNameForAddressCalled - timesResolveNameCalledBeforeSingleFetch;
ensCache.clearCache();
- const timesResolveNameCalledBeforeDoubleFetch = timesResolveNameCalled;
+ const timesResolveNameCalledBeforeDoubleFetch =
+ timesGetNameForAddressCalled;
const [ashoatAddrResult2, ashoatAddrResult3] = await Promise.all([
ensCache.getAddressForName(ashoatDotEth),
ensCache.getAddressForName(ashoatDotEth),
@@ -263,7 +326,7 @@
expect(ashoatAddrResult2).toBe(ashoatAddr);
expect(ashoatAddrResult3).toBe(ashoatAddr);
const timesResolveNamesCalledForDoubleFetch =
- timesResolveNameCalled - timesResolveNameCalledBeforeDoubleFetch;
+ timesGetNameForAddressCalled - timesResolveNameCalledBeforeDoubleFetch;
expect(timesResolveNamesCalledForDoubleFetch).toBe(
timesResolveNameCalledForSingleFetch,
@@ -303,11 +366,11 @@
if (!process.env.ALCHEMY_API_KEY) {
return;
}
- const timesGetAvatarCalledBefore = timesGetAvatarCalled;
+ const timesGetAvatarCalledBefore = timesGetAvatarURIForNameCalled;
const ashoatAvatarResult =
await ensCache.getAvatarURIForAddress(ashoatAddr);
expect(ashoatAvatarResult).toBe(ashoatAvatar);
- expect(timesGetAvatarCalled).toBe(timesGetAvatarCalledBefore);
+ expect(timesGetAvatarURIForNameCalled).toBe(timesGetAvatarCalledBefore);
});
it('should dedup simultaneous fetches', async () => {
if (!process.env.ALCHEMY_API_KEY) {
@@ -315,15 +378,17 @@
}
ensCache.clearCache();
- const timesGetAvatarCalledBeforeSingleFetch = timesGetAvatarCalled;
+ const timesGetAvatarCalledBeforeSingleFetch =
+ timesGetAvatarURIForNameCalled;
const ashoatAvatarResult1 =
await ensCache.getAvatarURIForAddress(ashoatAddr);
expect(ashoatAvatarResult1).toBe(ashoatAvatar);
const timesGetAvatarCalledForSingleFetch =
- timesGetAvatarCalled - timesGetAvatarCalledBeforeSingleFetch;
+ timesGetAvatarURIForNameCalled - timesGetAvatarCalledBeforeSingleFetch;
ensCache.clearCache();
- const timesGetAvatarCalledBeforeDoubleFetch = timesGetAvatarCalled;
+ const timesGetAvatarCalledBeforeDoubleFetch =
+ timesGetAvatarURIForNameCalled;
const [ashoatAvatarResult2, ashoatAvatarResult3] = await Promise.all([
ensCache.getAvatarURIForAddress(ashoatAddr),
ensCache.getAvatarURIForAddress(ashoatAddr),
@@ -331,7 +396,7 @@
expect(ashoatAvatarResult2).toBe(ashoatAvatar);
expect(ashoatAvatarResult3).toBe(ashoatAvatar);
const timesGetAvatarCalledForDoubleFetch =
- timesGetAvatarCalled - timesGetAvatarCalledBeforeDoubleFetch;
+ timesGetAvatarURIForNameCalled - timesGetAvatarCalledBeforeDoubleFetch;
expect(timesGetAvatarCalledForDoubleFetch).toBe(
timesGetAvatarCalledForSingleFetch,
diff --git a/lib/utils/ens-wrapper.js b/lib/utils/ens-wrapper.js
new file mode 100644
--- /dev/null
+++ b/lib/utils/ens-wrapper.js
@@ -0,0 +1,49 @@
+// @flow
+
+import {
+ batch,
+ getName,
+ type GetNameResult,
+ getOwner,
+ // eslint-disable-next-line import/extensions
+} from '@ensdomains/ensjs/public';
+import type { ViemClient } from 'viem';
+
+import type { EthersProvider } from '../types/ethers-types.js';
+
+class ENSWrapper {
+ viemClient: ViemClient;
+ ethersProvider: EthersProvider;
+
+ constructor(viemClient: ViemClient, ethersProvider: EthersProvider) {
+ this.viemClient = viemClient;
+ this.ethersProvider = ethersProvider;
+ }
+
+ getNameForAddress: string => Promise<?string> = async ethAddress => {
+ const result = await getName(this.viemClient, { address: ethAddress });
+ return result ? result.name : undefined;
+ };
+
+ getNamesForAddresses: ($ReadOnlyArray<string>) => Promise<Array<?string>> =
+ async ethAddresses => {
+ const results = await batch<GetNameResult>(
+ this.viemClient,
+ ...ethAddresses.map(address => getName.batch({ address })),
+ );
+ return results.map(result => (result ? result.name : undefined));
+ };
+
+ getAddressForName: string => Promise<?string> = async ensName => {
+ const result = await getOwner(this.viemClient, { name: ensName });
+ return result ? result.owner : undefined;
+ };
+
+ // @ensdomains/ensjs doesn't handle resolving ipfs and eip155 URIs to HTTP
+ // URIs, so we use Ethers.js instead
+ getAvatarURIForName: string => Promise<?string> = async ensName => {
+ return await this.ethersProvider.getAvatar(ensName);
+ };
+}
+
+export { ENSWrapper };
diff --git a/lib/utils/reverse-records.js b/lib/utils/reverse-records.js
deleted file mode 100644
--- a/lib/utils/reverse-records.js
+++ /dev/null
@@ -1,45 +0,0 @@
-// @flow
-
-type ABIParam = {
- +internalType: string,
- +name: string,
- +type: string,
-};
-type EthereumSmartContractABI = $ReadOnlyArray<{
- +inputs: $ReadOnlyArray<ABIParam>,
- +stateMutability: string,
- +type: string,
- +name?: ?string,
- +outputs?: ?$ReadOnlyArray<ABIParam>,
-}>;
-
-const resolverABI: EthereumSmartContractABI = [
- {
- inputs: [{ internalType: 'contract ENS', name: '_ens', type: 'address' }],
- stateMutability: 'nonpayable',
- type: 'constructor',
- },
- {
- inputs: [
- { internalType: 'address[]', name: 'addresses', type: 'address[]' },
- ],
- name: 'getNames',
- outputs: [{ internalType: 'string[]', name: 'r', type: 'string[]' }],
- stateMutability: 'view',
- type: 'function',
- },
-];
-
-const mainnetChainID = 1;
-const goerliChainID = 5;
-const resolverAddresses: { +[chainID: number]: string } = {
- [mainnetChainID]: '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C',
- [goerliChainID]: '0x333Fc8f550043f239a2CF79aEd5e9cF4A20Eb41e',
-};
-
-export type ReverseRecordsEthersSmartContract = {
- +'getNames(address[])': ($ReadOnlyArray<string>) => Promise<string[]>,
- ...
-};
-
-export { resolverABI, resolverAddresses };
diff --git a/lib/utils/viem-utils.js b/lib/utils/viem-utils.js
new file mode 100644
--- /dev/null
+++ b/lib/utils/viem-utils.js
@@ -0,0 +1,15 @@
+// @flow
+
+import { http, type ViemTransport } from 'viem';
+
+function getAlchemyMainnetViemTransport(alchemyKey: ?string): ViemTransport {
+ const key = alchemyKey ?? 'demo';
+ return http(`https://eth-mainnet.g.alchemy.com/v2/${key}`);
+}
+
+function getAlchemySepoliaViemTransport(alchemyKey: ?string): ViemTransport {
+ const key = alchemyKey ?? 'demo';
+ return http(`https://eth-sepolia.g.alchemy.com/v2/${key}`);
+}
+
+export { getAlchemyMainnetViemTransport, getAlchemySepoliaViemTransport };
diff --git a/lib/utils/wagmi-utils.js b/lib/utils/wagmi-utils.js
--- a/lib/utils/wagmi-utils.js
+++ b/lib/utils/wagmi-utils.js
@@ -67,7 +67,10 @@
function AlchemyENSCacheProvider(props: Props): React.Node {
const { children } = props;
return (
- <ENSCacheProvider provider={ethersAlchemyProvider}>
+ <ENSCacheProvider
+ ethersProvider={ethersAlchemyProvider}
+ alchemyKey={alchemyKey}
+ >
{children}
</ENSCacheProvider>
);
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -103,7 +103,7 @@
import { useLoadCommFonts } from './themes/fonts.js';
import { DarkTheme, LightTheme } from './themes/navigation.js';
import ThemeHandler from './themes/theme-handler.react.js';
-import { provider } from './utils/ethers-utils.js';
+import { alchemyKey, ethersProvider } from './utils/ethers-utils.js';
import { neynarKey } from './utils/neynar-utils.js';
// Add custom items to expo-dev-menu
@@ -353,7 +353,10 @@
initialMetrics={initialWindowMetrics}
>
<ActionSheetProvider>
- <ENSCacheProvider provider={provider}>
+ <ENSCacheProvider
+ ethersProvider={ethersProvider}
+ alchemyKey={alchemyKey}
+ >
<NeynarClientProvider apiKey={neynarKey}>
<MediaCacheProvider
persistence={filesystemMediaCache}
diff --git a/native/utils/ethers-utils.js b/native/utils/ethers-utils.js
--- a/native/utils/ethers-utils.js
+++ b/native/utils/ethers-utils.js
@@ -6,16 +6,16 @@
import type { EthersProvider } from 'lib/types/ethers-types.js';
-let alchemyKey;
+let alchemyKey: ?string;
try {
// $FlowExpectedError: file might not exist
const { key } = require('../facts/alchemy.json');
alchemyKey = key;
} catch {}
-let provider: ?EthersProvider;
+let ethersProvider: ?EthersProvider;
if (alchemyKey) {
- provider = new AlchemyProvider('mainnet', alchemyKey);
+ ethersProvider = new AlchemyProvider('mainnet', alchemyKey);
}
-export { provider };
+export { alchemyKey, ethersProvider };
diff --git a/yarn.lock b/yarn.lock
--- a/yarn.lock
+++ b/yarn.lock
@@ -2090,6 +2090,57 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
+"@ensdomains/address-encoder@1.0.0-rc.3":
+ version "1.0.0-rc.3"
+ resolved "https://registry.yarnpkg.com/@ensdomains/address-encoder/-/address-encoder-1.0.0-rc.3.tgz#78a8081bed834661e7fd21a9c9f67f927100fce5"
+ integrity sha512-8o6zH69rObIqDY4PusEWuN9jvVOct+9jj9AOPO7ifc3ev8nmsly0e8TE1sHkhk0iKFbd3DlSsUnJ+yuRWmdLCQ==
+ dependencies:
+ "@noble/curves" "^1.2.0"
+ "@noble/hashes" "^1.3.2"
+ "@scure/base" "^1.1.5"
+
+"@ensdomains/address-encoder@1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@ensdomains/address-encoder/-/address-encoder-1.1.1.tgz#5cbec1fc6e2435b109c058426d7c222cb5a5d8de"
+ integrity sha512-yg7s+suCuKRhaGsgLu57W/jxIs/Lnqs/SU7jT7UwS4ATSnW94jbUCbmyyZ82CQwKsmwaUE8uYvvVb4N6lfz29A==
+ dependencies:
+ "@noble/curves" "^1.2.0"
+ "@noble/hashes" "^1.3.2"
+ "@scure/base" "^1.1.5"
+
+"@ensdomains/content-hash@3.1.0-rc.1":
+ version "3.1.0-rc.1"
+ resolved "https://registry.yarnpkg.com/@ensdomains/content-hash/-/content-hash-3.1.0-rc.1.tgz#f22220df19be2f60683070a683f5760a9a7134d8"
+ integrity sha512-OzdkXgdFmduzcJKU4KRkkJkQHnm5p7m7FkX8k+bHOEoOIzc0ueGT/Jay4nnb60wNk1wSHRmzY+hHBMpFDiGReg==
+ dependencies:
+ "@ensdomains/address-encoder" "1.0.0-rc.3"
+ "@noble/curves" "^1.2.0"
+ "@scure/base" "^1.1.5"
+
+"@ensdomains/dnsprovejs@^0.5.1":
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/@ensdomains/dnsprovejs/-/dnsprovejs-0.5.1.tgz#7b09121580d3224736567e680697fe179f0288af"
+ integrity sha512-nfm4ggpK5YBVwVwLZKF9WPjRGRTL9aUxX2O4pqv/AnQCz3WeGHsW7VhVFLj2s4EoWSzCXwR1E6nuqgUwnH692w==
+ dependencies:
+ "@noble/hashes" "^1.3.2"
+ dns-packet "^5.6.1"
+ typescript-logging "^1.0.1"
+
+"@ensdomains/ensjs@^4.0.1":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@ensdomains/ensjs/-/ensjs-4.0.1.tgz#49a8930c1fe0f8fbd4c7fbb9aea517e71d41652f"
+ integrity sha512-s7/cM+7h0tZrDHLVHhmcg9qpXMkReWTU98EUGAZiJAx4xP+5C+GJhmv2jplAzHESXCIEzOt4K0QOwLfMUJl4Bg==
+ dependencies:
+ "@adraffy/ens-normalize" "1.10.1"
+ "@ensdomains/address-encoder" "1.1.1"
+ "@ensdomains/content-hash" "3.1.0-rc.1"
+ "@ensdomains/dnsprovejs" "^0.5.1"
+ abitype "^1.0.0"
+ dns-packet "^5.3.1"
+ graphql "^16.3.0"
+ graphql-request "6.1.0"
+ pako "^2.1.0"
+
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@@ -2735,7 +2786,7 @@
"@graphql-typed-document-node/core" "^3.1.1"
tslib "^2.4.0"
-"@graphql-typed-document-node/core@^3.1.0", "@graphql-typed-document-node/core@^3.1.1":
+"@graphql-typed-document-node/core@^3.1.0", "@graphql-typed-document-node/core@^3.1.1", "@graphql-typed-document-node/core@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
@@ -3588,6 +3639,13 @@
dependencies:
"@noble/hashes" "1.3.3"
+"@noble/curves@^1.2.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b"
+ integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==
+ dependencies:
+ "@noble/hashes" "1.5.0"
+
"@noble/hashes@1.3.2", "@noble/hashes@^1.1.2", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
@@ -3598,6 +3656,11 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699"
integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==
+"@noble/hashes@1.5.0", "@noble/hashes@^1.3.2":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0"
+ integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==
+
"@noble/hashes@^1.3.1":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
@@ -4534,6 +4597,11 @@
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d"
integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==
+"@scure/base@^1.1.5":
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1"
+ integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==
+
"@scure/base@~1.1.0", "@scure/base@~1.1.2":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f"
@@ -6164,6 +6232,11 @@
resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97"
integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==
+abitype@^1.0.0:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.6.tgz#76410903e1d88e34f1362746e2d407513c38565b"
+ integrity sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==
+
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@@ -10172,6 +10245,13 @@
dependencies:
"@leichtgewicht/ip-codec" "^2.0.1"
+dns-packet@^5.3.1, dns-packet@^5.6.1:
+ version "5.6.1"
+ resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f"
+ integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==
+ dependencies:
+ "@leichtgewicht/ip-codec" "^2.0.1"
+
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@@ -10626,6 +10706,13 @@
dependencies:
is-arrayish "^0.2.1"
+error-stack-parser@^1.3.6:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-1.3.6.tgz#e0e73b93e417138d1cd7c0b746b1a4a14854c292"
+ integrity sha512-xhuSYd8wLgOXwNgjcPeXMPL/IiiA1Huck+OPvClpJViVNNlJVtM41o+1emp7bPvlCJwCatFX2DWc05/DgfbWzA==
+ dependencies:
+ stackframe "^0.3.1"
+
error-stack-parser@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8"
@@ -13166,6 +13253,14 @@
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+graphql-request@6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.1.0.tgz#f4eb2107967af3c7a5907eb3131c671eac89be4f"
+ integrity sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==
+ dependencies:
+ "@graphql-typed-document-node/core" "^3.2.0"
+ cross-fetch "^3.1.5"
+
graphql-tag@^2.10.1:
version "2.12.6"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"
@@ -13178,6 +13273,11 @@
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38"
integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==
+graphql@^16.3.0:
+ version "16.9.0"
+ resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f"
+ integrity sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==
+
graphql@^16.6.0:
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
@@ -19012,7 +19112,7 @@
registry-url "^5.0.0"
semver "^6.2.0"
-pako@^2.0.4:
+pako@^2.0.4, pako@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
@@ -22418,6 +22518,11 @@
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
+source-map@0.5.6:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
+ integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==
+
source-map@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
@@ -22594,6 +22699,13 @@
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
+stack-generator@^1.0.7:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-1.1.0.tgz#36f6a920751a6c10f499a13c32cbb5f51a0b8b25"
+ integrity sha512-sZDVjwC56vZoo+a5t0LH/1sMQLWYLi/r+Z2ztyCAOhOX3QBP34GWxK0FWf2eU1TIU2CJKCKBAtDZycUh/ZKMlw==
+ dependencies:
+ stackframe "^1.0.2"
+
stack-utils@^1.0.1:
version "1.0.5"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.5.tgz#a19b0b01947e0029c8e451d5d61a498f5bb1471b"
@@ -22608,11 +22720,38 @@
dependencies:
escape-string-regexp "^2.0.0"
+stackframe@^0.3.1, stackframe@~0.3:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4"
+ integrity sha512-XmoiF4T5nuWEp2x2w92WdGjdHGY/cZa6LIbRsDRQR/Xlk4uW0PAUlH1zJYVffocwKpCdwyuypIp25xsSXEtZHw==
+
+stackframe@^1.0.2:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310"
+ integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==
+
stackframe@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303"
integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==
+stacktrace-gps@^2.4.3:
+ version "2.4.4"
+ resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-2.4.4.tgz#69c827e9d6d6f41cf438d7f195e2e3cbfcf28c44"
+ integrity sha512-msFhuMEEklQLUtaJ+GeCDjzUN+PamfHWQiK3C1LnbHjoxSeF5dAxiE+aJkptNMmMNOropGFJ7G3ZT7dPZHgDaQ==
+ dependencies:
+ source-map "0.5.6"
+ stackframe "~0.3"
+
+stacktrace-js@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-1.3.1.tgz#67cab2589af5c417b962f7369940277bb3b6a18b"
+ integrity sha512-b+5voFnXqg9TWdOE50soXL+WuOreYUm1Ukg9U7rzEWGL4+gcVxIcFasNBtOffVX0I1lYqVZj0PZXZvTt5e3YRQ==
+ dependencies:
+ error-stack-parser "^1.3.6"
+ stack-generator "^1.0.7"
+ stacktrace-gps "^2.4.3"
+
stacktrace-parser@^0.1.10, stacktrace-parser@^0.1.3:
version "0.1.10"
resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a"
@@ -23911,6 +24050,13 @@
resolved "https://registry.yarnpkg.com/typescript-compiler/-/typescript-compiler-1.4.1-2.tgz#ba4f7db22d91534a1929d90009dce161eb72fd3f"
integrity sha1-uk99si2RU0oZKdkACdzhYety/T8=
+typescript-logging@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/typescript-logging/-/typescript-logging-1.0.1.tgz#e0f8157943780cf5943aacd53b04cb73d108a0f9"
+ integrity sha512-zp28ABme0m5q/nXabBaY9Hv/35N8lMH4FsvhpUO0zVi4vFs3uKlb5br2it61HAZF5k+U0aP6E67j0VD0IzXGpQ==
+ dependencies:
+ stacktrace-js "1.3.1"
+
typescript@^3.4:
version "3.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"

File Metadata

Mime Type
text/plain
Expires
Fri, Oct 18, 12:22 AM (22 h, 2 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2314195
Default Alt Text
D13699.id45133.diff (40 KB)

Event Timeline