Skip to content

Commit 1e03bda

Browse files
authored
Jt/app registry subgraph 2 - identity / reputation (#4547)
### Description Subgraph Entities to support App Identity and Reputation, from AppRegistry facets. ### Changes <!-- List the specific changes made in this PR, for example: - Added/modified feature X - Fixed bug in component Y - Refactored module Z - Updated documentation --> ### Checklist - [ ] Tests added where required - [ ] Documentation updated where applicable - [ ] Changes adhere to the repository's contribution guidelines
1 parent 0b1431a commit 1e03bda

File tree

4 files changed

+884
-12
lines changed

4 files changed

+884
-12
lines changed

packages/subgraph/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"find-paths": "tsx utils/findContractsPath.ts",
1212
"format": "prettier --write \"src/**/*.{ts,tsx}\" \"ponder.config.ts\" \"ponder.schema.ts\"",
1313
"lint": "eslint \"src/**/*.{ts,tsx}\" \"ponder.config.ts\" \"ponder.schema.ts\"",
14+
"lint:fix": "eslint --fix \"src/**/*.{ts,tsx}\" \"ponder.config.ts\" \"ponder.schema.ts\"",
1415
"serve": "yarn typings && ponder serve",
1516
"start": "yarn typings && ponder start",
1617
"test:unit": "yarn typings && sh -c 'tsc --noEmit'",

packages/subgraph/ponder.schema.ts

Lines changed: 222 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export const analyticsEventType = onchainEnum('analytics_event_type', [
77
'review',
88
])
99

10+
// Actor type enum for tip events
11+
export type ActorType = 'Member' | 'Bot'
12+
1013
// Type definitions for analytics event data
1114
export type SwapEventData = {
1215
type: 'swap'
@@ -21,12 +24,14 @@ export type SwapEventData = {
2124
export type TipEventData = {
2225
type: 'tip'
2326
sender: string
27+
senderType: ActorType
2428
receiver: string
29+
recipientType: ActorType
2530
currency: string
2631
amount: string
27-
tokenId: string
28-
messageId: string
29-
channelId: string
32+
tokenId?: string
33+
messageId?: string
34+
channelId?: string
3035
}
3136

3237
export type JoinEventData = {
@@ -60,6 +65,7 @@ export const space = onchainTable(
6065
totalAmountStaked: t.bigint().default(0n),
6166
swapVolume: t.bigint().default(0n),
6267
tipVolume: t.bigint().default(0n),
68+
botTipVolume: t.bigint().default(0n),
6369
joinVolume: t.bigint().default(0n),
6470
memberCount: t.bigint().default(0n),
6571
reviewCount: t.bigint().default(0n),
@@ -256,8 +262,73 @@ export const app = onchainTable('apps', (t) => ({
256262
isRegistered: t.boolean().default(false),
257263
isBanned: t.boolean().default(false),
258264
installedIn: t.hex().array().notNull(),
265+
lastUpdatedAt: t.bigint(),
266+
currentVersionId: t.hex(),
267+
tipsCount: t.bigint().default(0n),
268+
tipsVolume: t.bigint().default(0n),
259269
}))
260270

271+
// app installations - tracks which apps are installed in which spaces/accounts
272+
export const appInstallation = onchainTable(
273+
'app_installations',
274+
(t) => ({
275+
// Composite key: app + account uniquely identifies an installation
276+
app: t.hex().notNull(),
277+
account: t.hex().notNull(),
278+
// versionId
279+
appId: t.hex().notNull(),
280+
281+
// Lifecycle timestamps
282+
installedAt: t.bigint().notNull(),
283+
lastUpdatedAt: t.bigint(),
284+
lastRenewedAt: t.bigint(),
285+
uninstalledAt: t.bigint(),
286+
287+
// Transaction metadata
288+
installTxHash: t.hex().notNull(),
289+
installLogIndex: t.integer().notNull(),
290+
291+
// Status
292+
isActive: t.boolean().default(true).notNull(),
293+
}),
294+
(table) => ({
295+
pk: primaryKey({ columns: [table.app, table.account] }),
296+
accountIdx: index().on(table.account),
297+
activeIdx: index().on(table.isActive),
298+
appIdIdx: index().on(table.appId),
299+
installedAtIdx: index().on(table.installedAt),
300+
}),
301+
)
302+
303+
// app versions - tracks version history for apps
304+
export const appVersion = onchainTable(
305+
'app_versions',
306+
(t) => ({
307+
appId: t.hex().notNull(),
308+
app: t.hex().notNull(),
309+
310+
// Version metadata
311+
createdAt: t.bigint().notNull(),
312+
upgradedFromId: t.hex(),
313+
314+
// Transaction metadata
315+
txHash: t.hex().notNull(),
316+
logIndex: t.integer().notNull(),
317+
blockNumber: t.bigint().notNull(),
318+
319+
// Status
320+
isLatest: t.boolean().default(false).notNull(),
321+
isCurrent: t.boolean().default(true).notNull(),
322+
}),
323+
(table) => ({
324+
pk: primaryKey({ columns: [table.app, table.appId] }),
325+
appIdx: index().on(table.app),
326+
appIdIdx: index().on(table.appId),
327+
latestIdx: index().on(table.app, table.isLatest),
328+
currentIdx: index().on(table.isCurrent),
329+
}),
330+
)
331+
261332
// reviews
262333
export const review = onchainTable(
263334
'reviews',
@@ -350,6 +421,36 @@ export const failureToSubscription = relations(subscriptionFailure, ({ one }) =>
350421
}),
351422
}))
352423

424+
// each app has many installations
425+
export const appToInstallations = relations(app, ({ many }) => ({
426+
installations: many(appInstallation),
427+
}))
428+
429+
// each installation belongs to an app
430+
export const installationToApp = relations(appInstallation, ({ one }) => ({
431+
app: one(app, { fields: [appInstallation.app], references: [app.address] }),
432+
}))
433+
434+
// each installation belongs to a space
435+
export const installationToSpace = relations(appInstallation, ({ one }) => ({
436+
space: one(space, { fields: [appInstallation.account], references: [space.id] }),
437+
}))
438+
439+
// each space has many app installations
440+
export const spaceToInstallations = relations(space, ({ many }) => ({
441+
appInstallations: many(appInstallation),
442+
}))
443+
444+
// each app has many versions
445+
export const appToVersions = relations(app, ({ many }) => ({
446+
versions: many(appVersion),
447+
}))
448+
449+
// each version belongs to an app
450+
export const versionToApp = relations(appVersion, ({ one }) => ({
451+
app: one(app, { fields: [appVersion.app], references: [app.address] }),
452+
}))
453+
353454
// tip leaderboard - per-space tip stats per user (senders only)
354455
export const tipLeaderboard = onchainTable(
355456
'tip_leaderboard',
@@ -358,10 +459,128 @@ export const tipLeaderboard = onchainTable(
358459
spaceId: t.hex().notNull(),
359460
totalSent: t.bigint().default(0n),
360461
tipsSentCount: t.integer().default(0),
462+
memberTipsSent: t.integer().default(0),
463+
memberTotalSent: t.bigint().default(0n),
464+
botTipsSent: t.integer().default(0),
465+
botTotalSent: t.bigint().default(0n),
361466
lastActivity: t.bigint().notNull(),
362467
}),
363468
(table) => ({
364469
pk: primaryKey({ columns: [table.user, table.spaceId] }),
365470
spaceSentIdx: index().on(table.spaceId, table.totalSent),
366471
}),
367472
)
473+
474+
// Agent Identity Registry - ERC-721 based agent identities for apps
475+
export const agentIdentity = onchainTable(
476+
'agent_identities',
477+
(t) => ({
478+
app: t.hex().notNull(), // FK to apps.address
479+
agentId: t.bigint().notNull(),
480+
agentUri: t.text(),
481+
registeredAt: t.bigint().notNull(),
482+
registeredAtBlock: t.bigint().notNull(),
483+
updatedAt: t.bigint(),
484+
transactionHash: t.hex().notNull(),
485+
}),
486+
(table) => ({
487+
pk: primaryKey({ columns: [table.app, table.agentId] }),
488+
appIdx: index().on(table.app),
489+
agentIdIdx: index().on(table.agentId),
490+
}),
491+
)
492+
493+
// Agent Metadata - key-value metadata storage for agents
494+
export const agentMetadata = onchainTable(
495+
'agent_metadata',
496+
(t) => ({
497+
app: t.hex().notNull(), // FK to apps.address
498+
metadataKey: t.text().notNull(),
499+
metadataValue: t.hex().notNull(), // bytes from Solidity -> hex string
500+
setAt: t.bigint().notNull(),
501+
transactionHash: t.hex().notNull(),
502+
}),
503+
(table) => ({
504+
pk: primaryKey({ columns: [table.app, table.metadataKey] }),
505+
appIdx: index().on(table.app),
506+
}),
507+
)
508+
509+
// Agent Feedback - reviews/feedback for agents (ERC-8004 compliant)
510+
export const agentFeedback = onchainTable(
511+
'agent_feedback',
512+
(t) => ({
513+
app: t.hex().notNull(), // FK to apps.address
514+
agentId: t.bigint().notNull(),
515+
reviewerAddress: t.hex().notNull(),
516+
feedbackIndex: t.bigint().notNull(),
517+
rating: t.integer().notNull(),
518+
tag1: t.hex(),
519+
tag2: t.hex(),
520+
comment: t.text(),
521+
commentHash: t.hex(),
522+
isRevoked: t.boolean().default(false).notNull(),
523+
createdAt: t.bigint().notNull(),
524+
revokedAt: t.bigint(),
525+
transactionHash: t.hex().notNull(),
526+
}),
527+
(table) => ({
528+
pk: primaryKey({ columns: [table.agentId, table.reviewerAddress, table.feedbackIndex] }),
529+
appIdx: index().on(table.app),
530+
agentIdIdx: index().on(table.agentId),
531+
reviewerIdx: index().on(table.reviewerAddress),
532+
ratingIdx: index().on(table.agentId, table.rating),
533+
activeIdx: index().on(table.agentId, table.isRevoked),
534+
}),
535+
)
536+
537+
// Feedback Responses - responses to agent feedback
538+
export const feedbackResponse = onchainTable(
539+
'feedback_responses',
540+
(t) => ({
541+
app: t.hex().notNull(), // FK to apps.address
542+
agentId: t.bigint().notNull(),
543+
reviewerAddress: t.hex().notNull(),
544+
feedbackIndex: t.bigint().notNull(),
545+
responderAddress: t.hex().notNull(),
546+
comment: t.text(),
547+
commentHash: t.hex(),
548+
createdAt: t.bigint().notNull(),
549+
transactionHash: t.hex().notNull(),
550+
}),
551+
(table) => ({
552+
pk: primaryKey({
553+
columns: [
554+
table.agentId,
555+
table.reviewerAddress,
556+
table.feedbackIndex,
557+
table.responderAddress,
558+
table.createdAt,
559+
],
560+
}),
561+
appIdx: index().on(table.app),
562+
agentIdIdx: index().on(table.agentId),
563+
feedbackIdx: index().on(table.agentId, table.reviewerAddress, table.feedbackIndex),
564+
responderIdx: index().on(table.responderAddress),
565+
}),
566+
)
567+
568+
// Agent Reputation Summary - precomputed aggregated stats
569+
export const agentReputationSummary = onchainTable(
570+
'agent_reputation_summary',
571+
(t) => ({
572+
app: t.hex().primaryKey(), // FK to apps.address
573+
agentId: t.bigint().notNull(),
574+
totalFeedback: t.integer().notNull().default(0),
575+
activeFeedback: t.integer().notNull().default(0),
576+
revokedFeedback: t.integer().notNull().default(0),
577+
averageRating: t.real(),
578+
totalResponses: t.integer().notNull().default(0),
579+
uniqueReviewers: t.integer().notNull().default(0),
580+
lastFeedbackAt: t.bigint(),
581+
}),
582+
(table) => ({
583+
agentIdIdx: index().on(table.agentId),
584+
ratingIdx: index().on(table.averageRating),
585+
}),
586+
)

0 commit comments

Comments
 (0)