-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathresolver.ts
More file actions
167 lines (151 loc) · 5.95 KB
/
resolver.ts
File metadata and controls
167 lines (151 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import config from "@/config";
import { type ResolveCursorConnectionArgs, resolveCursorConnection } from "@pothos/plugin-relay";
import { and, eq } from "drizzle-orm";
import { namehash } from "viem";
import { makePermissionsId, makeResolverRecordsId, type ResolverId } from "@ensnode/ensnode-sdk";
import { isBridgedResolver } from "@ensnode/ensnode-sdk/internal";
import { builder } from "@/graphql-api/builder";
import { orderPaginationBy, paginateBy } from "@/graphql-api/lib/connection-helpers";
import { resolveFindEvents } from "@/graphql-api/lib/find-events/find-events-resolver";
import { getModelId } from "@/graphql-api/lib/get-model-id";
import { lazyConnection } from "@/graphql-api/lib/lazy-connection";
import { AccountIdInput, AccountIdRef } from "@/graphql-api/schema/account-id";
import { ID_PAGINATED_CONNECTION_ARGS } from "@/graphql-api/schema/constants";
import { EventRef, EventsWhereInput } from "@/graphql-api/schema/event";
import { NameOrNodeInput } from "@/graphql-api/schema/name-or-node";
import { PermissionsRef } from "@/graphql-api/schema/permissions";
import { ResolverRecordsRef } from "@/graphql-api/schema/resolver-records";
import { ensDb, ensIndexerSchema } from "@/lib/ensdb/singleton";
/**
* Note that this indexed Resolver entity represents not _all_ Resolver contracts that exist onchain,
* but the set of Resolver contracts that have emitted at least one event that we are able to index.
*
* This means that if one were to access a Resolver contract that _does_ exist on-chain, but hasn't
* emitted any events (ex: BasenamesL1Resolver), this API (which retrieves data from the index)
* would say that it doesn't exist.
*
* This limitation has always been the case, including for the legacy ENS Subgraph, and would require
* an RPC call to the chain be performed in the case that a Resolver doesn't exist in the index, which
* is prohibitive in both cost and latency. As such we acknowledge this limitation here, for now.
*/
export const ResolverRef = builder.loadableObjectRef("Resolver", {
load: (ids: ResolverId[]) =>
ensDb.query.resolver.findMany({
where: (t, { inArray }) => inArray(t.id, ids),
}),
toKey: getModelId,
cacheResolved: true,
sort: true,
});
export type Resolver = Exclude<typeof ResolverRef.$inferType, ResolverId>;
////////////
// Resolver
////////////
ResolverRef.implement({
description: "A Resolver represents a Resolver contract on-chain.",
fields: (t) => ({
///////////////
// Resolver.id
///////////////
id: t.field({
description: "A unique reference to this Resolver.",
type: "ResolverId",
nullable: false,
resolve: (parent) => parent.id,
}),
/////////////////////
// Resolver.contract
/////////////////////
contract: t.field({
description: "Contract metadata for this Resolver.",
type: AccountIdRef,
nullable: false,
resolve: ({ chainId, address }) => ({ chainId, address }),
}),
////////////////////
// Resolver.records
////////////////////
records: t.connection({
description: "ResolverRecords issued by this Resolver.",
type: ResolverRecordsRef,
resolve: (parent, args, context) => {
const scope = and(
eq(ensIndexerSchema.resolverRecords.chainId, parent.chainId),
eq(ensIndexerSchema.resolverRecords.address, parent.address),
);
return lazyConnection({
totalCount: () => ensDb.$count(ensIndexerSchema.resolverRecords, scope),
connection: () =>
resolveCursorConnection(
{ ...ID_PAGINATED_CONNECTION_ARGS, args },
({ before, after, limit, inverted }: ResolveCursorConnectionArgs) =>
ensDb.query.resolverRecords.findMany({
where: and(scope, paginateBy(ensIndexerSchema.resolverRecords.id, before, after)),
orderBy: orderPaginationBy(ensIndexerSchema.resolverRecords.id, inverted),
limit,
with: { textRecords: true, addressRecords: true },
}),
),
});
},
}),
////////////////////////////////////
// Resolver.records by Name or Node
////////////////////////////////////
records_: t.field({
description: "Identify a ResolverRecord by `name` or `node`.",
type: ResolverRecordsRef,
args: { for: t.arg({ type: NameOrNodeInput, required: true }) },
nullable: true,
resolve: async ({ chainId, address }, args) => {
const node = args.for.node ?? namehash(args.for.name);
return makeResolverRecordsId({ chainId, address }, node);
},
}),
////////////////////
// Resolver.bridged
////////////////////
bridged: t.field({
description: "Whether Resolver is a BridgedResolver.",
type: AccountIdRef,
nullable: true,
resolve: (parent) => isBridgedResolver(config.namespace, parent),
}),
////////////////////////
// Resolver.permissions
////////////////////////
permissions: t.field({
description: "Permissions granted by this Resolver.",
type: PermissionsRef,
resolve: ({ chainId, address }) => makePermissionsId({ chainId, address }),
}),
////////////////////
// Resolver.events
////////////////////
events: t.connection({
description: "All Events associated with this Resolver.",
type: EventRef,
args: {
where: t.arg({ type: EventsWhereInput }),
},
resolve: (parent, args) =>
resolveFindEvents(args, {
through: {
table: ensIndexerSchema.resolverEvent,
scope: eq(ensIndexerSchema.resolverEvent.resolverId, parent.id),
},
}),
}),
}),
});
/////////////////////
// Inputs
/////////////////////
export const ResolverIdInput = builder.inputType("ResolverIdInput", {
description: "Address a Resolver by ID or AccountId.",
isOneOf: true,
fields: (t) => ({
id: t.field({ type: "ResolverId" }),
contract: t.field({ type: AccountIdInput }),
}),
});