diff --git a/apps/dispatcher/src/app.ts b/apps/dispatcher/src/app.ts index cb45e975..d5a9582f 100644 --- a/apps/dispatcher/src/app.ts +++ b/apps/dispatcher/src/app.ts @@ -5,6 +5,7 @@ import { SubscriptionClient } from './services/subscription-client.service'; import { NotificationClientFactory } from './services/notification/notification-factory.service'; import { RabbitMQNotificationService } from './services/notification/rabbitmq-notification.service'; import { NewProposalTriggerHandler } from './services/triggers/new-proposal-trigger.service'; +import { NewOffchainProposalTriggerHandler } from './services/triggers/new-offchain-proposal-trigger.service'; import { VotingPowerTriggerHandler } from './services/triggers/voting-power-trigger.service'; import { ProposalFinishedTriggerHandler } from './services/triggers/proposal-finished-trigger.service'; import { NonVotingHandler } from './services/triggers/non-voting-handler.service'; @@ -63,6 +64,11 @@ export class App { new NewProposalTriggerHandler(subscriptionClient, notificationFactory, anticaptureClient) ); + triggerProcessorService.addHandler( + 'new-offchain-proposal', + new NewOffchainProposalTriggerHandler(subscriptionClient, notificationFactory) + ); + triggerProcessorService.addHandler( 'voting-power-changed', new VotingPowerTriggerHandler(subscriptionClient, notificationFactory) diff --git a/apps/dispatcher/src/services/triggers/new-offchain-proposal-trigger.service.test.ts b/apps/dispatcher/src/services/triggers/new-offchain-proposal-trigger.service.test.ts new file mode 100644 index 00000000..96e7428f --- /dev/null +++ b/apps/dispatcher/src/services/triggers/new-offchain-proposal-trigger.service.test.ts @@ -0,0 +1,225 @@ +import { describe, it, expect, beforeEach } from '@jest/globals'; +import { NewOffchainProposalTriggerHandler } from './new-offchain-proposal-trigger.service'; +import { ISubscriptionClient, User, Notification } from '../../interfaces/subscription-client.interface'; +import { NotificationClientFactory } from '../notification/notification-factory.service'; +import { INotificationClient, NotificationPayload } from '../../interfaces/notification-client.interface'; +import { DispatcherMessage } from '../../interfaces/dispatcher-message.interface'; + +class SimpleSubscriptionClient implements ISubscriptionClient { + subscribers: Map = new Map(); + sentNotifications: Notification[] = []; + + async getDaoSubscribers(daoId: string): Promise { + return this.subscribers.get(daoId) || []; + } + + async shouldSend(subscribers: User[], eventId: string, daoId: string): Promise { + return subscribers.map(s => ({ user_id: s.id, event_id: eventId, dao_id: daoId })); + } + + async shouldSendBatch(): Promise { return []; } + + async markAsSent(notifications: Notification[]): Promise { + this.sentNotifications.push(...notifications); + } + + async getWalletOwners(): Promise { return []; } + async getWalletOwnersBatch(): Promise> { return {}; } + async getFollowedAddresses(): Promise { return []; } +} + +class SimpleNotificationClient implements INotificationClient { + sentPayloads: NotificationPayload[] = []; + + async sendNotification(payload: NotificationPayload): Promise { + this.sentPayloads.push(payload); + } +} + +function createHandler( + subscriptionClient: SimpleSubscriptionClient, + notificationClient: SimpleNotificationClient, +) { + const factory = new NotificationClientFactory(); + factory.addClient('telegram', notificationClient); + factory.addClient('slack', notificationClient); + return new NewOffchainProposalTriggerHandler(subscriptionClient, factory); +} + +describe('NewOffchainProposalTriggerHandler', () => { + let subscriptionClient: SimpleSubscriptionClient; + let notificationClient: SimpleNotificationClient; + let handler: NewOffchainProposalTriggerHandler; + + const testUser: User = { + id: 'user-1', + channel: 'telegram', + channel_user_id: '123', + created_at: new Date(), + }; + + beforeEach(() => { + subscriptionClient = new SimpleSubscriptionClient(); + notificationClient = new SimpleNotificationClient(); + handler = createHandler(subscriptionClient, notificationClient); + }); + + describe('handleMessage', () => { + it('should produce correct message text for single proposal', async () => { + subscriptionClient.subscribers.set('test-dao', [testUser]); + + const message: DispatcherMessage = { + triggerId: 'new-offchain-proposal', + events: [{ + daoId: 'test-dao', id: 'snap-1', title: 'Grant Program', + created: 1700000000, discussion: '', link: 'https://snapshot.org/#/test-dao/proposal/snap-1', state: 'active', + }], + }; + + await handler.handleMessage(message); + + expect(notificationClient.sentPayloads).toHaveLength(1); + expect(notificationClient.sentPayloads[0].message).toContain("Grant Program"); + }); + + it('should use "Untitled Proposal" when title is empty', async () => { + subscriptionClient.subscribers.set('test-dao', [testUser]); + + const message: DispatcherMessage = { + triggerId: 'new-offchain-proposal', + events: [{ + daoId: 'test-dao', id: 'snap-1', title: '', + created: 1700000000, discussion: '', link: 'https://snapshot.org/#/test-dao/proposal/snap-1', state: 'active', + }], + }; + + await handler.handleMessage(message); + + expect(notificationClient.sentPayloads[0].message).toContain('Untitled Proposal'); + }); + + it('should set correct eventId as offchain-{proposalId}', async () => { + subscriptionClient.subscribers.set('test-dao', [testUser]); + + const message: DispatcherMessage = { + triggerId: 'new-offchain-proposal', + events: [{ + daoId: 'test-dao', id: 'snap-123', title: 'Test', + created: 1700000000, discussion: '', link: 'https://snapshot.org/#/test-dao/proposal/snap-123', state: 'active', + }], + }; + + await handler.handleMessage(message); + + expect(subscriptionClient.sentNotifications[0].event_id).toBe('offchain-snap-123'); + }); + + it('should always include CTA button "Cast your vote"', async () => { + subscriptionClient.subscribers.set('test-dao', [testUser]); + + const message: DispatcherMessage = { + triggerId: 'new-offchain-proposal', + events: [{ + daoId: 'test-dao', id: 'snap-1', title: 'Test', + created: 1700000000, discussion: '', link: 'https://snapshot.org/#/test-dao/proposal/snap-1', state: 'active', + }], + }; + + await handler.handleMessage(message); + + const buttons = notificationClient.sentPayloads[0].metadata?.buttons; + expect(buttons).toBeDefined(); + expect(buttons[0].text).toBe('Cast your vote'); + expect(buttons[0].url).toBe('https://snapshot.org/#/test-dao/proposal/snap-1'); + }); + + it('should include "View Discussion" button when discussion URL is provided', async () => { + subscriptionClient.subscribers.set('test-dao', [testUser]); + + const message: DispatcherMessage = { + triggerId: 'new-offchain-proposal', + events: [{ + daoId: 'test-dao', id: 'snap-1', title: 'Test', + created: 1700000000, discussion: 'https://forum.example.com/123', link: 'https://snapshot.org/#/test-dao/proposal/snap-1', state: 'active', + }], + }; + + await handler.handleMessage(message); + + const buttons = notificationClient.sentPayloads[0].metadata?.buttons; + expect(buttons).toHaveLength(2); + expect(buttons[1].text).toBe('View Discussion'); + expect(buttons[1].url).toBe('https://forum.example.com/123'); + }); + + it('should omit "View Discussion" button when discussion is empty', async () => { + subscriptionClient.subscribers.set('test-dao', [testUser]); + + const message: DispatcherMessage = { + triggerId: 'new-offchain-proposal', + events: [{ + daoId: 'test-dao', id: 'snap-1', title: 'Test', + created: 1700000000, discussion: '', link: 'https://snapshot.org/#/test-dao/proposal/snap-1', state: 'active', + }], + }; + + await handler.handleMessage(message); + + const buttons = notificationClient.sentPayloads[0].metadata?.buttons; + expect(buttons).toHaveLength(1); + }); + + it('should process multiple proposals and notify all subscribers', async () => { + const user2: User = { id: 'user-2', channel: 'telegram', channel_user_id: '456', created_at: new Date() }; + subscriptionClient.subscribers.set('dao-a', [testUser, user2]); + subscriptionClient.subscribers.set('dao-b', [testUser]); + + const message: DispatcherMessage = { + triggerId: 'new-offchain-proposal', + events: [ + { daoId: 'dao-a', id: 'snap-1', title: 'Proposal A', created: 1700000000, discussion: '', link: 'https://snapshot.org/#/dao-a/proposal/snap-1', state: 'active' }, + { daoId: 'dao-b', id: 'snap-2', title: 'Proposal B', created: 1700000100, discussion: '', link: 'https://snapshot.org/#/dao-b/proposal/snap-2', state: 'active' }, + ], + }; + + await handler.handleMessage(message); + + expect(notificationClient.sentPayloads).toHaveLength(3); + }); + + it('should not perform lookups when events array is empty', async () => { + subscriptionClient.subscribers.set('test-dao', [testUser]); + + const message: DispatcherMessage = { + triggerId: 'new-offchain-proposal', + events: [], + }; + + await handler.handleMessage(message); + + expect(notificationClient.sentPayloads).toHaveLength(0); + expect(subscriptionClient.sentNotifications).toHaveLength(0); + }); + + it('should mark notifications as sent after successful delivery', async () => { + subscriptionClient.subscribers.set('test-dao', [testUser]); + + const message: DispatcherMessage = { + triggerId: 'new-offchain-proposal', + events: [{ + daoId: 'test-dao', id: 'snap-1', title: 'Test', + created: 1700000000, discussion: '', link: 'https://snapshot.org/#/test-dao/proposal/snap-1', state: 'active', + }], + }; + + await handler.handleMessage(message); + + expect(subscriptionClient.sentNotifications).toHaveLength(1); + expect(subscriptionClient.sentNotifications[0]).toEqual({ + user_id: 'user-1', + event_id: 'offchain-snap-1', + dao_id: 'test-dao', + }); + }); + }); +}); diff --git a/apps/dispatcher/src/services/triggers/new-offchain-proposal-trigger.service.ts b/apps/dispatcher/src/services/triggers/new-offchain-proposal-trigger.service.ts new file mode 100644 index 00000000..7eaa2a2e --- /dev/null +++ b/apps/dispatcher/src/services/triggers/new-offchain-proposal-trigger.service.ts @@ -0,0 +1,57 @@ +import { DispatcherMessage, MessageProcessingResult } from "../../interfaces/dispatcher-message.interface"; +import { ISubscriptionClient } from "../../interfaces/subscription-client.interface"; +import { NotificationClientFactory } from "../notification/notification-factory.service"; +import { BaseTriggerHandler } from "./base-trigger.service"; +import { newOffchainProposalMessages, replacePlaceholders, buildButtons } from '@notification-system/messages'; +import crypto from 'crypto'; + +/** + * Handler for processing "new-offchain-proposal" trigger messages (Snapshot proposals) + */ +export class NewOffchainProposalTriggerHandler extends BaseTriggerHandler { + constructor( + subscriptionClient: ISubscriptionClient, + notificationFactory: NotificationClientFactory, + ) { + super(subscriptionClient, notificationFactory); + } + + /** + * Handle a new offchain proposal message + * @param message The message containing offchain proposal data + */ + async handleMessage(message: DispatcherMessage): Promise { + for (const proposal of message.events) { + const { daoId, id: proposalId, title, created, discussion, link } = proposal; + const eventId = `offchain-${proposalId}`; + const proposalTimestamp = String(created); + + const subscribers = await this.getSubscribers(daoId, eventId, proposalTimestamp); + const notificationMessage = replacePlaceholders(newOffchainProposalMessages.notification, { + daoId, + title: title || 'Untitled Proposal' + }); + + // Build buttons — include discussion link if available, no txHash for offchain + const buttons = buildButtons({ + triggerType: 'newOffchainProposal', + proposalUrl: link || undefined, + discussionUrl: discussion || undefined, + }); + + await this.sendNotificationsToSubscribers( + subscribers, + notificationMessage, + eventId, + daoId, + undefined, + buttons + ); + } + + return { + messageId: crypto.randomUUID(), + timestamp: new Date().toISOString() + }; + } +} diff --git a/apps/integrated-tests/src/fixtures/factories/offchain-proposal-factory.ts b/apps/integrated-tests/src/fixtures/factories/offchain-proposal-factory.ts new file mode 100644 index 00000000..566fc370 --- /dev/null +++ b/apps/integrated-tests/src/fixtures/factories/offchain-proposal-factory.ts @@ -0,0 +1,63 @@ +/** + * @notice Data structure for offchain (Snapshot) proposals in integration tests + * @dev Matches the shape returned by AnticaptureClient.listOffchainProposals + */ +export interface OffchainProposalData { + id: string; + daoId: string; + title: string; + discussion: string; + state: string; + created: number; +} + +/** + * @notice Factory class for creating test offchain proposal data + * @dev Provides methods to generate Snapshot-style proposal objects for integration testing + */ +export class OffchainProposalFactory { + /** + * @notice Creates a single offchain proposal with default or custom data + * @param daoId The DAO ID this proposal belongs to + * @param proposalId Unique identifier for the proposal + * @param overrides Optional partial data to override defaults + * @return Complete OffchainProposalData object ready for testing + */ + static createProposal( + daoId: string, + proposalId: string, + overrides?: Partial, + ): OffchainProposalData { + const futureTimestamp = Math.floor(Date.now() / 1000) + 5; + + return { + id: proposalId, + daoId, + title: `Test Snapshot Proposal ${proposalId}`, + discussion: '', + state: 'active', + created: futureTimestamp, + ...overrides, + }; + } + + /** + * @notice Creates multiple offchain proposals for the same DAO + * @param daoId The DAO ID all proposals belong to + * @param count Number of proposals to create + * @param baseId Base string for proposal IDs (will append -1, -2, etc.) + * @return Array of OffchainProposalData objects with sequential IDs + */ + static createMultipleProposals( + daoId: string, + count: number, + baseId: string = 'snap-proposal', + ): OffchainProposalData[] { + const baseTime = Math.floor(Date.now() / 1000) + 100; + return Array.from({ length: count }, (_, index) => + this.createProposal(daoId, `${baseId}-${index + 1}`, { + created: baseTime + index * 10, + }), + ); + } +} diff --git a/apps/integrated-tests/src/fixtures/index.ts b/apps/integrated-tests/src/fixtures/index.ts index 67c6b146..51e995ed 100644 --- a/apps/integrated-tests/src/fixtures/index.ts +++ b/apps/integrated-tests/src/fixtures/index.ts @@ -6,4 +6,5 @@ export * from './factories/user-factory'; export * from './factories/proposal-factory'; export * from './factories/voting-power-factory'; export * from './factories/vote-factory'; -export * from './factories/workspace-factory'; \ No newline at end of file +export * from './factories/workspace-factory'; +export * from './factories/offchain-proposal-factory'; \ No newline at end of file diff --git a/apps/integrated-tests/src/mocks/graphql-mock-setup.ts b/apps/integrated-tests/src/mocks/graphql-mock-setup.ts index f7e45b3b..3dac31fd 100644 --- a/apps/integrated-tests/src/mocks/graphql-mock-setup.ts +++ b/apps/integrated-tests/src/mocks/graphql-mock-setup.ts @@ -1,4 +1,4 @@ -import { ProposalData } from '../fixtures'; +import { ProposalData, OffchainProposalData } from '../fixtures'; import { ProcessedVotingPowerHistory } from '@notification-system/anticapture-client'; import { getAddress, isAddressEqual } from 'viem'; @@ -39,7 +39,7 @@ export class GraphQLMockSetup { /** * @notice Generic mock implementation that handles all query types */ - private static createMockImplementation(proposals: ProposalData[] = [], votingPowerData: ProcessedVotingPowerHistory[] = [], daoChainMapping: Record = {}, votesData: any[] = []) { + private static createMockImplementation(proposals: ProposalData[] = [], votingPowerData: ProcessedVotingPowerHistory[] = [], daoChainMapping: Record = {}, votesData: any[] = [], offchainProposalsData: OffchainProposalData[] = []) { return (url: string, data: any, config: any) => { // Handle proposals if (data.query?.includes('ListProposals')) { @@ -69,6 +69,20 @@ export class GraphQLMockSetup { }); } + // Handle offchain proposals + if (data.query?.includes('ListOffchainProposals')) { + let filtered = offchainProposalsData; + if (data.variables?.fromDate) { + filtered = filtered.filter(p => p.created >= data.variables.fromDate); + } + if (config?.headers?.['anticapture-dao-id']) { + filtered = filtered.filter(p => p.daoId === config.headers['anticapture-dao-id']); + } + return Promise.resolve({ + data: { data: { offchainProposals: { items: filtered.map(p => ({ id: p.id, title: p.title, discussion: p.discussion, state: p.state, created: p.created })), totalCount: filtered.length } } } + }); + } + // Handle single proposal if (data.query?.includes('GetProposalById')) { const proposalId = data.variables?.id; @@ -170,7 +184,8 @@ export class GraphQLMockSetup { const uniqueDaoIds = [...new Set([ ...proposals.map(p => p.daoId).filter(Boolean), ...votingPowerData.map(vp => vp.daoId).filter(Boolean), - ...votesData.map((v: any) => v.daoId).filter(Boolean) + ...votesData.map((v: any) => v.daoId).filter(Boolean), + ...offchainProposalsData.map(p => p.daoId).filter(Boolean) ])]; return Promise.resolve({ data: { data: { daos: { items: uniqueDaoIds.map(id => ({ @@ -201,15 +216,17 @@ export class GraphQLMockSetup { * @param votingPowerData Array of voting power history data * @param daoChainMapping Optional mapping of DAO IDs to chain IDs * @param votesData Array of vote data + * @param offchainProposalsData Array of offchain (Snapshot) proposal data */ static setupMock( mockHttpClient: any, proposals: ProposalData[] = [], votingPowerData: ProcessedVotingPowerHistory[] = [], daoChainMapping: Record = {}, - votesData: any[] = [] + votesData: any[] = [], + offchainProposalsData: OffchainProposalData[] = [] ): void { - mockHttpClient.post.mockImplementation(this.createMockImplementation(proposals, votingPowerData, daoChainMapping, votesData)); + mockHttpClient.post.mockImplementation(this.createMockImplementation(proposals, votingPowerData, daoChainMapping, votesData, offchainProposalsData)); } /** diff --git a/apps/integrated-tests/tests/slack/slack-new-offchain-proposal.test.ts b/apps/integrated-tests/tests/slack/slack-new-offchain-proposal.test.ts new file mode 100644 index 00000000..c4c86c3d --- /dev/null +++ b/apps/integrated-tests/tests/slack/slack-new-offchain-proposal.test.ts @@ -0,0 +1,141 @@ +/** + * Slack New Offchain Proposal Integration Test + * Tests that Snapshot proposal notifications are correctly delivered via Slack + */ + +import { describe, test, expect, beforeEach, beforeAll } from '@jest/globals'; +import { db, TestApps } from '../../src/setup'; +import { HttpClientMockSetup, GraphQLMockSetup } from '../../src/mocks'; +import { UserFactory, OffchainProposalFactory, WorkspaceFactory } from '../../src/fixtures'; +import { SlackTestHelper, DatabaseTestHelper, TestCleanup } from '../../src/helpers'; +import { SlackTestClient } from '../../src/test-clients/slack-test.client'; +import { testConstants, timeouts } from '../../src/config'; + +describe('Slack New Offchain Proposal - Integration Test', () => { + let apps: TestApps; + let httpMockSetup: HttpClientMockSetup; + let slackHelper: SlackTestHelper; + let slackClient: SlackTestClient; + let dbHelper: DatabaseTestHelper; + + const testDaoId = testConstants.daoIds.ens; + + beforeAll(async () => { + apps = TestCleanup.getGlobalApps(); + httpMockSetup = TestCleanup.getGlobalHttpMockSetup(); + + slackClient = new SlackTestClient(global.mockSlackSendMessage); + slackHelper = new SlackTestHelper(global.mockSlackSendMessage, slackClient); + + dbHelper = new DatabaseTestHelper(db); + }); + + beforeEach(async () => { + await TestCleanup.cleanupBetweenTests(); + }); + + test('should send offchain proposal notification to Slack user', async () => { + const channelId = 'C_OFFCHAIN_01'; + const workspaceId = WorkspaceFactory.getWorkspaceId(); + const slackUserId = `${workspaceId}:${channelId}`; + + const { user } = await UserFactory.createUserWithFullSetup( + slackUserId, + `slack_offchain_${channelId}`, + testDaoId, + true, + undefined, + 'slack', + ); + + const proposal = OffchainProposalFactory.createProposal(testDaoId, `snap-slack-${Date.now()}`, { + title: 'Slack Snapshot Proposal', + }); + + GraphQLMockSetup.setupMock( + httpMockSetup.getMockClient(), + [], + [], + {}, + [], + [proposal], + ); + + const message = await slackHelper.waitForMessage( + msg => + msg.text.includes('New Snapshot proposal') && + msg.text.includes(proposal.title) && + msg.channel === channelId, + { + timeout: timeouts.notification.delivery, + errorMessage: 'Slack offchain proposal notification not received', + }, + ); + + expect(message.channel).toBe(channelId); + expect(message.text).toContain('New Snapshot proposal'); + expect(message.text).toContain(proposal.title); + expect(message.text).toContain(testDaoId); + + if (message.text.includes('http')) { + expect(message.text).toMatch(/]+>/); + } + + const notifications = await dbHelper.getNotifications(); + const slackNotification = notifications.find( + n => n.user_id === user.id && n.event_id.includes(proposal.id), + ); + expect(slackNotification).toBeDefined(); + }); + + test('should deliver notification to multiple Slack users subscribed to the same DAO', async () => { + const channel1 = 'C_OFFCHAIN_M1'; + const channel2 = 'C_OFFCHAIN_M2'; + const workspaceId = WorkspaceFactory.getWorkspaceId(); + + await UserFactory.createUserWithFullSetup( + `${workspaceId}:${channel1}`, + `slack_offchain_${channel1}`, + testDaoId, + true, + undefined, + 'slack', + ); + + await UserFactory.createUserWithFullSetup( + `${workspaceId}:${channel2}`, + `slack_offchain_${channel2}`, + testDaoId, + true, + undefined, + 'slack', + ); + + const proposal = OffchainProposalFactory.createProposal(testDaoId, `snap-multi-${Date.now()}`, { + title: 'Multi-User Offchain Proposal', + }); + + GraphQLMockSetup.setupMock( + httpMockSetup.getMockClient(), + [], + [], + {}, + [], + [proposal], + ); + + const messages = await slackHelper.waitForMessageCount(2, { + timeout: timeouts.notification.delivery, + containing: proposal.title, + }); + + const channels = messages.map(m => m.channel); + expect(channels).toContain(channel1); + expect(channels).toContain(channel2); + + messages.forEach(message => { + expect(message.text).toContain('New Snapshot proposal'); + expect(message.text).toContain(proposal.title); + }); + }); +}); diff --git a/apps/integrated-tests/tests/telegram/new-offchain-proposal-trigger.test.ts b/apps/integrated-tests/tests/telegram/new-offchain-proposal-trigger.test.ts new file mode 100644 index 00000000..532f3086 --- /dev/null +++ b/apps/integrated-tests/tests/telegram/new-offchain-proposal-trigger.test.ts @@ -0,0 +1,179 @@ +import { describe, test, expect, beforeEach, beforeAll } from '@jest/globals'; +import { db, TestApps } from '../../src/setup'; +import { HttpClientMockSetup, GraphQLMockSetup } from '../../src/mocks'; +import { UserFactory, OffchainProposalFactory } from '../../src/fixtures'; +import { TelegramTestHelper, DatabaseTestHelper, TestCleanup } from '../../src/helpers'; +import { testConstants, timeouts } from '../../src/config'; + +describe('New Offchain Proposal Trigger - Integration Test', () => { + let apps: TestApps; + let httpMockSetup: HttpClientMockSetup; + let telegramHelper: TelegramTestHelper; + let dbHelper: DatabaseTestHelper; + + const testDaoId = testConstants.daoIds.ens; + + beforeAll(async () => { + apps = TestCleanup.getGlobalApps(); + httpMockSetup = TestCleanup.getGlobalHttpMockSetup(); + telegramHelper = new TelegramTestHelper(global.mockTelegramSendMessage); + dbHelper = new DatabaseTestHelper(db); + }); + + beforeEach(async () => { + await TestCleanup.cleanupBetweenTests(); + }); + + test('should send notification when new offchain proposal is created', async () => { + const testUser = testConstants.profiles.p1; + const pastTimestamp = new Date(Date.now() - timeouts.wait.long).toISOString(); + + await UserFactory.createUserWithFullSetup( + testUser.chatId, + 'offchain-user-basic', + testDaoId, + true, + pastTimestamp, + ); + + const proposal = OffchainProposalFactory.createProposal(testDaoId, `snap-basic-${Date.now()}`, { + title: 'Community Treasury Allocation', + }); + + GraphQLMockSetup.setupMock( + httpMockSetup.getMockClient(), + [], + [], + {}, + [], + [proposal], + ); + + const message = await telegramHelper.waitForMessage( + msg => msg.text.includes('New Snapshot proposal') && msg.text.includes(proposal.title), + { timeout: timeouts.notification.delivery }, + ); + + expect(message.text).toContain('📋'); + expect(message.text).toContain('New Snapshot proposal'); + expect(message.text).toContain(testDaoId); + expect(message.text).toContain(proposal.title); + expect(message.chatId).toBe(testUser.chatId); + }); + + test('should include discussion link when discussion URL is present', async () => { + const testUser = testConstants.profiles.p2; + const pastTimestamp = new Date(Date.now() - timeouts.wait.long).toISOString(); + + await UserFactory.createUserWithFullSetup( + testUser.chatId, + 'offchain-user-discussion', + testDaoId, + true, + pastTimestamp, + ); + + const discussionUrl = 'https://forum.example.com/proposal-discussion'; + const proposal = OffchainProposalFactory.createProposal(testDaoId, `snap-discuss-${Date.now()}`, { + title: 'Proposal With Discussion', + discussion: discussionUrl, + }); + + GraphQLMockSetup.setupMock( + httpMockSetup.getMockClient(), + [], + [], + {}, + [], + [proposal], + ); + + const message = await telegramHelper.waitForMessage( + msg => msg.text.includes('Proposal With Discussion'), + { timeout: timeouts.notification.delivery }, + ); + + expect(message.text).toContain('New Snapshot proposal'); + expect(message.chatId).toBe(testUser.chatId); + + const buttons = message.reply_markup?.inline_keyboard?.flat() ?? []; + const discussionButton = buttons.find((btn: any) => btn.url === discussionUrl); + expect(discussionButton).toBeDefined(); + expect(discussionButton.text).toBe('View Discussion'); + }); + + test('should NOT notify users not subscribed to the DAO', async () => { + const testUser = testConstants.profiles.p3; + const pastTimestamp = new Date(Date.now() - timeouts.wait.long).toISOString(); + + await UserFactory.createUserWithFullSetup( + testUser.chatId, + 'offchain-user-wrong-dao', + 'different-dao', + true, + pastTimestamp, + ); + + const proposal = OffchainProposalFactory.createProposal(testDaoId, `snap-nosub-${Date.now()}`, { + title: 'Proposal For Other DAO', + }); + + GraphQLMockSetup.setupMock( + httpMockSetup.getMockClient(), + [], + [], + {}, + [], + [proposal], + ); + + const messagePromise = telegramHelper.waitForMessage( + msg => msg.text.includes('Proposal For Other DAO'), + { timeout: timeouts.wait.short }, + ); + + await expect(messagePromise).rejects.toThrow('Telegram message not received'); + }); + + test('should NOT send duplicate notifications for the same proposal', async () => { + const testUser = testConstants.profiles.p4; + const pastTimestamp = new Date(Date.now() - timeouts.wait.long).toISOString(); + + await UserFactory.createUserWithFullSetup( + testUser.chatId, + 'offchain-user-dup', + testDaoId, + true, + pastTimestamp, + ); + + const proposal = OffchainProposalFactory.createProposal(testDaoId, `snap-dup-${Date.now()}`, { + title: 'Duplicate Prevention Proposal', + }); + + GraphQLMockSetup.setupMock( + httpMockSetup.getMockClient(), + [], + [], + {}, + [], + [proposal], + ); + + const firstMessage = await telegramHelper.waitForMessage( + msg => msg.text.includes('Duplicate Prevention Proposal'), + { timeout: timeouts.notification.delivery }, + ); + + expect(firstMessage).toBeDefined(); + + apps.logicSystemApp.resetTriggers(); + + const duplicatePromise = telegramHelper.waitForMessage( + msg => msg.text.includes('Duplicate Prevention Proposal'), + { timeout: timeouts.wait.short }, + ); + + await expect(duplicatePromise).rejects.toThrow('Telegram message not received'); + }); +}); diff --git a/apps/logic-system/src/app.ts b/apps/logic-system/src/app.ts index 809a5d1d..fd7d1d36 100644 --- a/apps/logic-system/src/app.ts +++ b/apps/logic-system/src/app.ts @@ -1,9 +1,11 @@ import { NewProposalTrigger } from './triggers/new-proposal-trigger'; +import { NewOffchainProposalTrigger } from './triggers/new-offchain-proposal-trigger'; import { VotingPowerChangedTrigger } from './triggers/voting-power-changed-trigger'; import { ProposalFinishedTrigger } from './triggers/proposal-finished-trigger'; import { VoteConfirmationTrigger } from './triggers/vote-confirmation-trigger'; import { VotingReminderTrigger } from './triggers/voting-reminder-trigger'; import { ProposalRepository } from './repositories/proposal.repository'; +import { OffchainProposalRepository } from './repositories/offchain-proposal.repository'; import { VotingPowerRepository } from './repositories/voting-power.repository'; import { VotesRepository } from './repositories/votes.repository'; import { RabbitMQDispatcherService } from './api-clients/rabbitmq-dispatcher.service'; @@ -14,6 +16,7 @@ import { AxiosInstance } from 'axios'; export class App { private trigger!: NewProposalTrigger; + private offchainProposalTrigger!: NewOffchainProposalTrigger; private votingPowerTrigger!: VotingPowerChangedTrigger; private proposalFinishedTrigger!: ProposalFinishedTrigger; private voteConfirmationTrigger!: VoteConfirmationTrigger; @@ -36,15 +39,17 @@ export class App { const anticaptureClient = new AnticaptureClient(anticaptureHttpClient); const proposalRepository = new ProposalRepository(anticaptureClient); + const offchainProposalRepository = new OffchainProposalRepository(anticaptureClient); const votingPowerRepository = new VotingPowerRepository(anticaptureClient); const votesRepository = new VotesRepository(anticaptureClient); - this.initPromise = this.initializeRabbitMQ(rabbitmqUrl, proposalRepository, votingPowerRepository, votesRepository, triggerInterval, initialTimestamp); + this.initPromise = this.initializeRabbitMQ(rabbitmqUrl, proposalRepository, offchainProposalRepository, votingPowerRepository, votesRepository, triggerInterval, initialTimestamp); } private async initializeRabbitMQ( - rabbitmqUrl: string, + rabbitmqUrl: string, proposalRepository: ProposalRepository, + offchainProposalRepository: OffchainProposalRepository, votingPowerRepository: VotingPowerRepository, votesRepository: VotesRepository, triggerInterval: number, @@ -63,6 +68,13 @@ export class App { initialTimestamp ); + this.offchainProposalTrigger = new NewOffchainProposalTrigger( + dispatcherService, + offchainProposalRepository, + triggerInterval, + initialTimestamp + ); + this.votingPowerTrigger = new VotingPowerChangedTrigger( dispatcherService, votingPowerRepository, @@ -108,6 +120,7 @@ export class App { async start(): Promise { await this.initPromise; this.trigger.start({ status: this.proposalStatus }); + this.offchainProposalTrigger.start({ status: ['active', 'pending'] }); this.votingPowerTrigger.start(); this.proposalFinishedTrigger.start(); this.voteConfirmationTrigger.start(); @@ -139,10 +152,14 @@ export class App { if (this.voteConfirmationTrigger) { this.voteConfirmationTrigger.reset(initialTimestamp); } + if (this.offchainProposalTrigger) { + this.offchainProposalTrigger.reset(initialTimestamp); + } } async stop(): Promise { await this.trigger.stop(); + await this.offchainProposalTrigger.stop(); await this.votingPowerTrigger.stop(); await this.proposalFinishedTrigger.stop(); await this.voteConfirmationTrigger.stop(); diff --git a/apps/logic-system/src/interfaces/offchain-proposal.interface.ts b/apps/logic-system/src/interfaces/offchain-proposal.interface.ts new file mode 100644 index 00000000..1766412c --- /dev/null +++ b/apps/logic-system/src/interfaces/offchain-proposal.interface.ts @@ -0,0 +1,18 @@ +import type { OffchainProposalItem } from '@notification-system/anticapture-client'; + +export type OffchainProposal = OffchainProposalItem & { daoId: string }; + +export interface ListOffchainProposalsOptions { + /** Filter by state (e.g., "active", "pending") */ + status?: string | string[]; + /** Filter proposals created after this date (timestamp in seconds) */ + fromDate?: number; + /** Maximum number of proposals to return */ + limit?: number; + /** Order direction - asc or desc */ + orderDirection?: string; +} + +export interface OffchainProposalDataSource { + listAll(options?: ListOffchainProposalsOptions): Promise; +} diff --git a/apps/logic-system/src/repositories/offchain-proposal.repository.ts b/apps/logic-system/src/repositories/offchain-proposal.repository.ts new file mode 100644 index 00000000..c371f366 --- /dev/null +++ b/apps/logic-system/src/repositories/offchain-proposal.repository.ts @@ -0,0 +1,32 @@ +import { AnticaptureClient } from '@notification-system/anticapture-client'; +import { OffchainProposalDataSource, OffchainProposal, ListOffchainProposalsOptions } from '../interfaces/offchain-proposal.interface'; + +export class OffchainProposalRepository implements OffchainProposalDataSource { + private anticaptureClient: AnticaptureClient; + + constructor(anticaptureClient: AnticaptureClient) { + this.anticaptureClient = anticaptureClient; + } + + async listAll(options?: ListOffchainProposalsOptions): Promise { + const variables: Record = {}; + + if (options?.status) { + variables.status = options.status; + } + + if (options?.fromDate) { + variables.fromDate = options.fromDate; + } + + if (options?.limit) { + variables.limit = options.limit; + } + + if (options?.orderDirection) { + variables.orderDirection = options.orderDirection; + } + + return await this.anticaptureClient.listOffchainProposals(variables); + } +} diff --git a/apps/logic-system/src/triggers/new-offchain-proposal-trigger.ts b/apps/logic-system/src/triggers/new-offchain-proposal-trigger.ts new file mode 100644 index 00000000..be674b59 --- /dev/null +++ b/apps/logic-system/src/triggers/new-offchain-proposal-trigger.ts @@ -0,0 +1,71 @@ +/** + * @fileoverview Trigger logic for handling new offchain (Snapshot) proposals. + * Monitors for active/pending Snapshot proposals and sends them to the Dispatcher. + */ + +import { Trigger } from './base-trigger'; +import { OffchainProposal, OffchainProposalDataSource, ListOffchainProposalsOptions } from '../interfaces/offchain-proposal.interface'; +import { DispatcherService, DispatcherMessage } from '../interfaces/dispatcher.interface'; + +const triggerId = 'new-offchain-proposal'; + +export class NewOffchainProposalTrigger extends Trigger { + private timestampCursor: number; + + constructor( + private readonly dispatcherService: DispatcherService, + private readonly offchainProposalRepository: OffchainProposalDataSource, + interval: number, + initialTimestamp?: string + ) { + super(triggerId, interval); + if (initialTimestamp) { + this.timestampCursor = parseInt(initialTimestamp, 10); + } else { + this.timestampCursor = Math.floor((Date.now() - 24 * 60 * 60 * 1000) / 1000); // 24 hours ago + } + } + + /** + * Resets the trigger state to initial timestamp + * @param timestamp Optional timestamp to reset to, defaults to 24 hours ago + * @todo This method will be removed when we migrate to Redis for state management + */ + public reset(timestamp?: string): void { + if (timestamp) { + this.timestampCursor = parseInt(timestamp, 10); + } else { + this.timestampCursor = Math.floor((Date.now() - 24 * 60 * 60 * 1000) / 1000); + } + } + + async process(data: OffchainProposal[]) { + if (data.length === 0) { + return; + } + + const message: DispatcherMessage = { + triggerId: this.id, + events: data + }; + await this.dispatcherService.sendMessage(message); + + // Update cursor to the most recent proposal's created timestamp + 1 + // Data comes sorted by created desc, so the first item has the latest timestamp + if (data[0]?.created) { + this.timestampCursor = data[0].created + 1; + } + } + + /** + * Fetches offchain proposals from the API + * @returns Array of offchain proposals + */ + protected async fetchData(options: ListOffchainProposalsOptions): Promise { + return await this.offchainProposalRepository.listAll({ + ...options, + status: options.status, + fromDate: this.timestampCursor + }); + } +} diff --git a/apps/logic-system/tests/new-offchain-proposal-trigger.test.ts b/apps/logic-system/tests/new-offchain-proposal-trigger.test.ts new file mode 100644 index 00000000..2cb79fee --- /dev/null +++ b/apps/logic-system/tests/new-offchain-proposal-trigger.test.ts @@ -0,0 +1,175 @@ +import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; +import { NewOffchainProposalTrigger } from '../src/triggers/new-offchain-proposal-trigger'; +import { + OffchainProposal, + OffchainProposalDataSource, + ListOffchainProposalsOptions +} from '../src/interfaces/offchain-proposal.interface'; +import { DispatcherService, DispatcherMessage } from '../src/interfaces/dispatcher.interface'; + +function createOffchainProposal(overrides?: Partial): OffchainProposal { + return { + id: 'snap-proposal-1', + title: 'Test Snapshot Proposal', + discussion: 'https://forum.example.com/proposal-1', + state: 'active', + created: 1700000000, + daoId: 'test-dao', + ...overrides, + }; +} + +class SimpleOffchainProposalDataSource implements OffchainProposalDataSource { + proposals: OffchainProposal[] = []; + lastOptions?: ListOffchainProposalsOptions; + + async listAll(options?: ListOffchainProposalsOptions): Promise { + this.lastOptions = options; + return this.proposals; + } +} + +class SimpleDispatcherService implements DispatcherService { + sentMessages: DispatcherMessage[] = []; + shouldFail = false; + + async sendMessage(message: DispatcherMessage): Promise { + if (this.shouldFail) throw new Error('Dispatcher failed'); + this.sentMessages.push(message); + } +} + +describe('NewOffchainProposalTrigger', () => { + let dataSource: SimpleOffchainProposalDataSource; + let dispatcher: SimpleDispatcherService; + let trigger: NewOffchainProposalTrigger; + + beforeEach(() => { + dataSource = new SimpleOffchainProposalDataSource(); + dispatcher = new SimpleDispatcherService(); + trigger = new NewOffchainProposalTrigger(dispatcher, dataSource, 60000); + }); + + describe('process()', () => { + it('should not send message when array is empty', async () => { + await trigger.process([]); + + expect(dispatcher.sentMessages).toHaveLength(0); + }); + + it('should send single proposal with correct triggerId and events', async () => { + const proposal = createOffchainProposal(); + + await trigger.process([proposal]); + + expect(dispatcher.sentMessages).toHaveLength(1); + expect(dispatcher.sentMessages[0]).toEqual({ + triggerId: 'new-offchain-proposal', + events: [proposal], + }); + }); + + it('should update timestampCursor to data[0].created + 1', async () => { + const proposal = createOffchainProposal({ created: 1700000000 }); + + await trigger.process([proposal]); + + expect(trigger['timestampCursor']).toBe(1700000001); + }); + + it('should include all proposals in events array', async () => { + const proposals = [ + createOffchainProposal({ id: 'snap-1', created: 1700000200 }), + createOffchainProposal({ id: 'snap-2', created: 1700000100 }), + ]; + + await trigger.process(proposals); + + expect(dispatcher.sentMessages).toHaveLength(1); + expect(dispatcher.sentMessages[0].events).toHaveLength(2); + expect(trigger['timestampCursor']).toBe(1700000201); + }); + + it('should propagate dispatcher errors', async () => { + dispatcher.shouldFail = true; + const proposal = createOffchainProposal(); + + await expect(trigger.process([proposal])).rejects.toThrow('Dispatcher failed'); + }); + }); + + describe('start/stop', () => { + beforeEach(() => { + jest.useFakeTimers(); + dataSource.proposals = [createOffchainProposal()]; + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should start interval, fetch data, and process it', () => { + trigger.start({ status: ['active', 'pending'] }); + jest.advanceTimersByTime(60000); + + expect(dataSource.lastOptions).toBeDefined(); + expect(dataSource.lastOptions!.status).toEqual(['active', 'pending']); + }); + + it('should stop and clear timer', async () => { + trigger.start({ status: ['active', 'pending'] }); + await trigger.stop(); + + expect(trigger['timer']).toBeNull(); + + jest.advanceTimersByTime(120000); + expect(dataSource.lastOptions).toBeUndefined(); + }); + }); + + describe('initialTimestamp & reset', () => { + it('should use custom initialTimestamp', () => { + const customTrigger = new NewOffchainProposalTrigger( + dispatcher, dataSource, 60000, '1234567890' + ); + + expect(customTrigger['timestampCursor']).toBe(1234567890); + }); + + it('should reset to specific timestamp', () => { + trigger.reset('9999999999'); + + expect(trigger['timestampCursor']).toBe(9999999999); + }); + + it('should reset to 24h ago when no argument', () => { + jest.useFakeTimers(); + jest.setSystemTime(new Date('2025-01-15T12:00:00Z')); + + trigger.reset(); + + const expected = Math.floor((Date.now() - 24 * 60 * 60 * 1000) / 1000); + expect(trigger['timestampCursor']).toBe(expected); + + jest.useRealTimers(); + }); + }); + + describe('fetchData', () => { + it('should pass fromDate as current cursor to data source', async () => { + const customTrigger = new NewOffchainProposalTrigger( + dispatcher, dataSource, 60000, '1700000000' + ); + + await customTrigger['fetchData']({ status: ['active'] }); + + expect(dataSource.lastOptions?.fromDate).toBe(1700000000); + }); + + it('should pass status from options', async () => { + await trigger['fetchData']({ status: ['active', 'pending'] }); + + expect(dataSource.lastOptions?.status).toEqual(['active', 'pending']); + }); + }); +}); diff --git a/packages/anticapture-client/dist/anticapture-client.d.ts b/packages/anticapture-client/dist/anticapture-client.d.ts index d38e0f5b..7538f872 100644 --- a/packages/anticapture-client/dist/anticapture-client.d.ts +++ b/packages/anticapture-client/dist/anticapture-client.d.ts @@ -1,7 +1,8 @@ import { AxiosInstance } from 'axios'; import { z } from 'zod'; -import type { GetProposalByIdQuery, ListProposalsQuery, ListProposalsQueryVariables, ListHistoricalVotingPowerQueryVariables, ListVotesQuery, ListVotesQueryVariables } from './gql/graphql'; +import type { GetProposalByIdQuery, ListProposalsQuery, ListProposalsQueryVariables, ListHistoricalVotingPowerQueryVariables, ListVotesQuery, ListVotesQueryVariables, ListOffchainProposalsQueryVariables } from './gql/graphql'; import { SafeProposalNonVotersResponseSchema, ProcessedVotingPowerHistory } from './schemas'; +import type { OffchainProposalItem } from './schemas'; type ProposalItems = NonNullable['items']; type VotingPowerHistoryItems = ProcessedVotingPowerHistory[]; type ProposalNonVoter = z.infer['proposalNonVoters']['items'][0]; @@ -74,5 +75,14 @@ export declare class AnticaptureClient { * @returns Array of votes from all DAOs with daoId included */ listRecentVotesFromAllDaos(timestampGt: string, limit?: number): Promise; + /** + * Lists offchain (Snapshot) proposals from all DAOs or a specific DAO + * @param variables Query variables (skip, limit, orderDirection, status, fromDate) + * @param daoId Optional specific DAO ID. If not provided, queries all DAOs + * @returns Array of offchain proposal items with daoId attached + */ + listOffchainProposals(variables?: ListOffchainProposalsQueryVariables, daoId?: string): Promise<(OffchainProposalItem & { + daoId: string; + })[]>; } export {}; diff --git a/packages/anticapture-client/dist/anticapture-client.js b/packages/anticapture-client/dist/anticapture-client.js index 3a6327f4..edce5364 100644 --- a/packages/anticapture-client/dist/anticapture-client.js +++ b/packages/anticapture-client/dist/anticapture-client.js @@ -291,5 +291,40 @@ class AnticaptureClient { }); return allVotes; } + /** + * Lists offchain (Snapshot) proposals from all DAOs or a specific DAO + * @param variables Query variables (skip, limit, orderDirection, status, fromDate) + * @param daoId Optional specific DAO ID. If not provided, queries all DAOs + * @returns Array of offchain proposal items with daoId attached + */ + async listOffchainProposals(variables, daoId) { + if (!daoId) { + const allDAOs = await this.getDAOs(); + const allProposals = []; + for (const dao of allDAOs) { + try { + const validated = await this.query(graphql_2.ListOffchainProposalsDocument, schemas_1.SafeOffchainProposalsResponseSchema, variables, dao.id); + const items = validated.offchainProposals.items.map(item => ({ ...item, daoId: dao.id })); + if (items.length > 0) { + allProposals.push(...items); + } + } + catch (error) { + console.warn(`Skipping offchain proposals for ${dao.id} due to API error: ${error instanceof Error ? error.message : error}`); + } + } + // Sort by created timestamp desc (most recent first) + allProposals.sort((a, b) => b.created - a.created); + return allProposals; + } + try { + const validated = await this.query(graphql_2.ListOffchainProposalsDocument, schemas_1.SafeOffchainProposalsResponseSchema, variables, daoId); + return validated.offchainProposals.items.map(item => ({ ...item, daoId })); + } + catch (error) { + console.warn(`Error querying offchain proposals for DAO ${daoId}: ${error instanceof Error ? error.message : error}`); + return []; + } + } } exports.AnticaptureClient = AnticaptureClient; diff --git a/packages/anticapture-client/dist/gql/gql.d.ts b/packages/anticapture-client/dist/gql/gql.d.ts index 25771ff3..bd814514 100644 --- a/packages/anticapture-client/dist/gql/gql.d.ts +++ b/packages/anticapture-client/dist/gql/gql.d.ts @@ -13,6 +13,7 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document- */ type Documents = { "query GetDAOs {\n daos {\n items {\n id\n votingDelay\n chainId\n }\n }\n}": typeof types.GetDaOsDocument; + "query ListOffchainProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_offchainProposals_orderDirection, $status: JSON, $fromDate: Float) {\n offchainProposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n ) {\n items {\n id\n title\n discussion\n link\n state\n created\n }\n totalCount\n }\n}": typeof types.ListOffchainProposalsDocument; "query ProposalNonVoters($id: String!, $addresses: JSON) {\n proposalNonVoters(id: $id, addresses: $addresses) {\n items {\n voter\n }\n }\n}": typeof types.ProposalNonVotersDocument; "query GetProposalById($id: String!) {\n proposal(id: $id) {\n id\n daoId\n proposerAccountId\n title\n description\n startBlock\n endBlock\n endTimestamp\n timestamp\n status\n forVotes\n againstVotes\n abstainVotes\n txHash\n }\n}\n\nquery ListProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_proposals_orderDirection, $status: JSON, $fromDate: Float, $fromEndDate: Float, $includeOptimisticProposals: queryInput_proposals_includeOptimisticProposals) {\n proposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n fromEndDate: $fromEndDate\n includeOptimisticProposals: $includeOptimisticProposals\n ) {\n items {\n id\n daoId\n proposerAccountId\n title\n description\n startBlock\n endBlock\n endTimestamp\n timestamp\n status\n forVotes\n againstVotes\n abstainVotes\n txHash\n }\n totalCount\n }\n}": typeof types.GetProposalByIdDocument; "query ListVotes($voterAddressIn: JSON, $fromDate: Float, $toDate: Float, $limit: Float, $skip: NonNegativeInt, $orderBy: queryInput_votes_orderBy, $orderDirection: queryInput_votes_orderDirection, $support: Float) {\n votes(\n voterAddressIn: $voterAddressIn\n fromDate: $fromDate\n toDate: $toDate\n limit: $limit\n skip: $skip\n orderBy: $orderBy\n orderDirection: $orderDirection\n support: $support\n ) {\n items {\n transactionHash\n proposalId\n voterAddress\n support\n votingPower\n timestamp\n reason\n proposalTitle\n }\n totalCount\n }\n}": typeof types.ListVotesDocument; @@ -36,6 +37,10 @@ export declare function graphql(source: string): unknown; * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export declare function graphql(source: "query GetDAOs {\n daos {\n items {\n id\n votingDelay\n chainId\n }\n }\n}"): (typeof documents)["query GetDAOs {\n daos {\n items {\n id\n votingDelay\n chainId\n }\n }\n}"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export declare function graphql(source: "query ListOffchainProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_offchainProposals_orderDirection, $status: JSON, $fromDate: Float) {\n offchainProposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n ) {\n items {\n id\n title\n discussion\n link\n state\n created\n }\n totalCount\n }\n}"): (typeof documents)["query ListOffchainProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_offchainProposals_orderDirection, $status: JSON, $fromDate: Float) {\n offchainProposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n ) {\n items {\n id\n title\n discussion\n link\n state\n created\n }\n totalCount\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/anticapture-client/dist/gql/gql.js b/packages/anticapture-client/dist/gql/gql.js index 5a989ed0..59e9b9f7 100644 --- a/packages/anticapture-client/dist/gql/gql.js +++ b/packages/anticapture-client/dist/gql/gql.js @@ -38,6 +38,7 @@ exports.graphql = graphql; const types = __importStar(require("./graphql")); const documents = { "query GetDAOs {\n daos {\n items {\n id\n votingDelay\n chainId\n }\n }\n}": types.GetDaOsDocument, + "query ListOffchainProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_offchainProposals_orderDirection, $status: JSON, $fromDate: Float) {\n offchainProposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n ) {\n items {\n id\n title\n discussion\n link\n state\n created\n }\n totalCount\n }\n}": types.ListOffchainProposalsDocument, "query ProposalNonVoters($id: String!, $addresses: JSON) {\n proposalNonVoters(id: $id, addresses: $addresses) {\n items {\n voter\n }\n }\n}": types.ProposalNonVotersDocument, "query GetProposalById($id: String!) {\n proposal(id: $id) {\n id\n daoId\n proposerAccountId\n title\n description\n startBlock\n endBlock\n endTimestamp\n timestamp\n status\n forVotes\n againstVotes\n abstainVotes\n txHash\n }\n}\n\nquery ListProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_proposals_orderDirection, $status: JSON, $fromDate: Float, $fromEndDate: Float, $includeOptimisticProposals: queryInput_proposals_includeOptimisticProposals) {\n proposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n fromEndDate: $fromEndDate\n includeOptimisticProposals: $includeOptimisticProposals\n ) {\n items {\n id\n daoId\n proposerAccountId\n title\n description\n startBlock\n endBlock\n endTimestamp\n timestamp\n status\n forVotes\n againstVotes\n abstainVotes\n txHash\n }\n totalCount\n }\n}": types.GetProposalByIdDocument, "query ListVotes($voterAddressIn: JSON, $fromDate: Float, $toDate: Float, $limit: Float, $skip: NonNegativeInt, $orderBy: queryInput_votes_orderBy, $orderDirection: queryInput_votes_orderDirection, $support: Float) {\n votes(\n voterAddressIn: $voterAddressIn\n fromDate: $fromDate\n toDate: $toDate\n limit: $limit\n skip: $skip\n orderBy: $orderBy\n orderDirection: $orderDirection\n support: $support\n ) {\n items {\n transactionHash\n proposalId\n voterAddress\n support\n votingPower\n timestamp\n reason\n proposalTitle\n }\n totalCount\n }\n}": types.ListVotesDocument, diff --git a/packages/anticapture-client/dist/gql/graphql.d.ts b/packages/anticapture-client/dist/gql/graphql.d.ts index 7e9fc76b..12c94066 100644 --- a/packages/anticapture-client/dist/gql/graphql.d.ts +++ b/packages/anticapture-client/dist/gql/graphql.d.ts @@ -151,6 +151,14 @@ export type Query = { delegationPercentageByDay?: Maybe; /** Get current delegators of an account */ delegations?: Maybe; + /** Get current delegators of an account with voting power */ + delegators?: Maybe; + /** Get feed events */ + feedEvents?: Maybe; + /** Returns label information from Arkham, ENS data, and whether the address is an EOA or contract. Arkham data is stored permanently. ENS data is cached with a configurable TTL. */ + getAddress?: Maybe; + /** Returns label information from Arkham, ENS data, and address type for multiple addresses. Maximum 100 addresses per request. Arkham data is stored permanently. ENS data is cached with a configurable TTL. */ + getAddresses?: Maybe; /** Get historical DAO Token Treasury value (governance token quantity × token price) */ getDaoTokenTreasury?: Maybe; /** Get historical Liquid Treasury (treasury without DAO tokens) from external providers (DefiLlama/Dune) */ @@ -169,6 +177,10 @@ export type Query = { historicalVotingPowerByAccountId?: Maybe; /** Get the last update time */ lastUpdate?: Maybe; + /** Returns a single offchain (Snapshot) proposal by its ID */ + offchainProposalById?: Maybe; + /** Returns a list of offchain (Snapshot) proposals */ + offchainProposals?: Maybe; /** Returns a single proposal by its ID */ proposal?: Maybe; /** Returns the active delegates that did not vote on a given proposal */ @@ -189,6 +201,10 @@ export type Query = { votes?: Maybe; /** Returns a paginated list of votes cast on a specific proposal */ votesByProposalId?: Maybe; + /** Returns a list of offchain (Snapshot) votes */ + votesOffchain?: Maybe; + /** Returns a paginated list of offchain (Snapshot) votes for a specific proposal */ + votesOffchainByProposalId?: Maybe; /** Returns voting power information for a specific address (account) */ votingPowerByAccountId?: Maybe; /** Returns a mapping of the voting power changes within a time frame for the given addresses */ @@ -287,6 +303,29 @@ export type QueryDelegationPercentageByDayArgs = { export type QueryDelegationsArgs = { address: Scalars['String']['input']; }; +export type QueryDelegatorsArgs = { + address: Scalars['String']['input']; + limit?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; +}; +export type QueryFeedEventsArgs = { + fromDate?: InputMaybe; + limit?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + relevance?: InputMaybe; + skip?: InputMaybe; + toDate?: InputMaybe; + type?: InputMaybe; +}; +export type QueryGetAddressArgs = { + address: Scalars['String']['input']; +}; +export type QueryGetAddressesArgs = { + addresses: Scalars['JSON']['input']; +}; export type QueryGetDaoTokenTreasuryArgs = { days?: InputMaybe; order?: InputMaybe; @@ -348,6 +387,16 @@ export type QueryHistoricalVotingPowerByAccountIdArgs = { export type QueryLastUpdateArgs = { chart: QueryInput_LastUpdate_Chart; }; +export type QueryOffchainProposalByIdArgs = { + id: Scalars['String']['input']; +}; +export type QueryOffchainProposalsArgs = { + fromDate?: InputMaybe; + limit?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + status?: InputMaybe; +}; export type QueryProposalArgs = { id: Scalars['String']['input']; }; @@ -435,6 +484,25 @@ export type QueryVotesByProposalIdArgs = { toDate?: InputMaybe; voterAddressIn?: InputMaybe; }; +export type QueryVotesOffchainArgs = { + fromDate?: InputMaybe; + limit?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + toDate?: InputMaybe; + voterAddresses?: InputMaybe; +}; +export type QueryVotesOffchainByProposalIdArgs = { + fromDate?: InputMaybe; + id: Scalars['String']['input']; + limit?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + toDate?: InputMaybe; + voterAddresses?: InputMaybe; +}; export type QueryVotingPowerByAccountIdArgs = { accountId: Scalars['String']['input']; }; @@ -464,7 +532,9 @@ export type AccountBalanceByAccountId_200_Response = { __typename?: 'accountBalanceByAccountId_200_response'; address: Scalars['String']['output']; balance: Scalars['String']['output']; + data: Query_AccountBalanceByAccountId_Data; delegate: Scalars['String']['output']; + period: Query_AccountBalanceByAccountId_Period; tokenId: Scalars['String']['output']; }; export type AccountBalanceVariationsByAccountId_200_Response = { @@ -480,6 +550,7 @@ export type AccountBalanceVariations_200_Response = { export type AccountBalances_200_Response = { __typename?: 'accountBalances_200_response'; items: Array>; + period: Query_AccountBalances_Period; totalCount: Scalars['Float']['output']; }; export type AccountInteractions_200_Response = { @@ -573,6 +644,27 @@ export type Delegations_200_Response = { items: Array>; totalCount: Scalars['Float']['output']; }; +export type Delegators_200_Response = { + __typename?: 'delegators_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; +export type FeedEvents_200_Response = { + __typename?: 'feedEvents_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; +export type GetAddress_200_Response = { + __typename?: 'getAddress_200_response'; + address: Scalars['String']['output']; + arkham?: Maybe; + ens?: Maybe; + isContract: Scalars['Boolean']['output']; +}; +export type GetAddresses_200_Response = { + __typename?: 'getAddresses_200_response'; + results: Array>; +}; export type GetDaoTokenTreasury_200_Response = { __typename?: 'getDaoTokenTreasury_200_response'; items: Array>; @@ -615,6 +707,28 @@ export type LastUpdate_200_Response = { __typename?: 'lastUpdate_200_response'; lastUpdate: Scalars['String']['output']; }; +export type OffchainProposalById_200_Response = { + __typename?: 'offchainProposalById_200_response'; + author: Scalars['String']['output']; + body: Scalars['String']['output']; + created: Scalars['Float']['output']; + discussion: Scalars['String']['output']; + end: Scalars['Float']['output']; + flagged: Scalars['Boolean']['output']; + id: Scalars['String']['output']; + link: Scalars['String']['output']; + spaceId: Scalars['String']['output']; + start: Scalars['Float']['output']; + state: Scalars['String']['output']; + title: Scalars['String']['output']; + type: Scalars['String']['output']; + updated: Scalars['Float']['output']; +}; +export type OffchainProposals_200_Response = { + __typename?: 'offchainProposals_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; export type ProposalNonVoters_200_Response = { __typename?: 'proposalNonVoters_200_response'; items: Array>; @@ -663,6 +777,10 @@ export declare enum QueryInput_AccountBalanceVariations_OrderDirection { Asc = "asc", Desc = "desc" } +export declare enum QueryInput_AccountBalances_OrderBy { + Balance = "balance", + Variation = "variation" +} export declare enum QueryInput_AccountBalances_OrderDirection { Asc = "asc", Desc = "desc" @@ -756,6 +874,43 @@ export declare enum QueryInput_DelegationPercentageByDay_OrderDirection { Asc = "asc", Desc = "desc" } +export declare enum QueryInput_Delegations_OrderBy { + Amount = "amount", + Timestamp = "timestamp" +} +export declare enum QueryInput_Delegations_OrderDirection { + Asc = "asc", + Desc = "desc" +} +export declare enum QueryInput_Delegators_OrderBy { + Amount = "amount", + Timestamp = "timestamp" +} +export declare enum QueryInput_Delegators_OrderDirection { + Asc = "asc", + Desc = "desc" +} +export declare enum QueryInput_FeedEvents_OrderBy { + Timestamp = "timestamp", + Value = "value" +} +export declare enum QueryInput_FeedEvents_OrderDirection { + Asc = "asc", + Desc = "desc" +} +export declare enum QueryInput_FeedEvents_Relevance { + High = "HIGH", + Low = "LOW", + Medium = "MEDIUM" +} +export declare enum QueryInput_FeedEvents_Type { + Delegation = "DELEGATION", + DelegationVotesChanged = "DELEGATION_VOTES_CHANGED", + Proposal = "PROPOSAL", + ProposalExtended = "PROPOSAL_EXTENDED", + Transfer = "TRANSFER", + Vote = "VOTE" +} export declare enum QueryInput_GetDaoTokenTreasury_Days { '7d' = "_7d", '30d' = "_30d", @@ -822,6 +977,10 @@ export declare enum QueryInput_LastUpdate_Chart { CostComparison = "cost_comparison", TokenDistribution = "token_distribution" } +export declare enum QueryInput_OffchainProposals_OrderDirection { + Asc = "asc", + Desc = "desc" +} export declare enum QueryInput_ProposalNonVoters_OrderDirection { Asc = "asc", Desc = "desc" @@ -887,6 +1046,22 @@ export declare enum QueryInput_VotesByProposalId_OrderDirection { Asc = "asc", Desc = "desc" } +export declare enum QueryInput_VotesOffchainByProposalId_OrderBy { + Created = "created", + Vp = "vp" +} +export declare enum QueryInput_VotesOffchainByProposalId_OrderDirection { + Asc = "asc", + Desc = "desc" +} +export declare enum QueryInput_VotesOffchain_OrderBy { + Created = "created", + Vp = "vp" +} +export declare enum QueryInput_VotesOffchain_OrderDirection { + Asc = "asc", + Desc = "desc" +} export declare enum QueryInput_Votes_OrderBy { Timestamp = "timestamp", VotingPower = "votingPower" @@ -901,12 +1076,32 @@ export declare enum QueryInput_VotingPowerVariations_OrderDirection { } export declare enum QueryInput_VotingPowers_OrderBy { DelegationsCount = "delegationsCount", + Variation = "variation", VotingPower = "votingPower" } export declare enum QueryInput_VotingPowers_OrderDirection { Asc = "asc", Desc = "desc" } +export type Query_AccountBalanceByAccountId_Data = { + __typename?: 'query_accountBalanceByAccountId_data'; + address: Scalars['String']['output']; + balance: Scalars['String']['output']; + delegate: Scalars['String']['output']; + tokenId: Scalars['String']['output']; + variation: Query_AccountBalanceByAccountId_Data_Variation; +}; +export type Query_AccountBalanceByAccountId_Data_Variation = { + __typename?: 'query_accountBalanceByAccountId_data_variation'; + absoluteChange: Scalars['String']['output']; + percentageChange: Scalars['String']['output']; + previousBalance: Scalars['String']['output']; +}; +export type Query_AccountBalanceByAccountId_Period = { + __typename?: 'query_accountBalanceByAccountId_period'; + endTimestamp: Scalars['String']['output']; + startTimestamp: Scalars['String']['output']; +}; export type Query_AccountBalanceVariationsByAccountId_Data = { __typename?: 'query_accountBalanceVariationsByAccountId_data'; absoluteChange: Scalars['String']['output']; @@ -939,6 +1134,18 @@ export type Query_AccountBalances_Items_Items = { balance: Scalars['String']['output']; delegate: Scalars['String']['output']; tokenId: Scalars['String']['output']; + variation: Query_AccountBalances_Items_Items_Variation; +}; +export type Query_AccountBalances_Items_Items_Variation = { + __typename?: 'query_accountBalances_items_items_variation'; + absoluteChange: Scalars['String']['output']; + percentageChange: Scalars['String']['output']; + previousBalance: Scalars['String']['output']; +}; +export type Query_AccountBalances_Period = { + __typename?: 'query_accountBalances_period'; + endTimestamp: Scalars['String']['output']; + startTimestamp: Scalars['String']['output']; }; export type Query_AccountInteractions_Items_Items = { __typename?: 'query_accountInteractions_items_items'; @@ -971,6 +1178,68 @@ export type Query_Delegations_Items_Items = { timestamp: Scalars['String']['output']; transactionHash: Scalars['String']['output']; }; +export type Query_Delegators_Items_Items = { + __typename?: 'query_delegators_items_items'; + amount: Scalars['String']['output']; + delegatorAddress: Scalars['String']['output']; + timestamp: Scalars['String']['output']; +}; +export type Query_FeedEvents_Items_Items = { + __typename?: 'query_feedEvents_items_items'; + logIndex: Scalars['Float']['output']; + metadata?: Maybe; + relevance: Query_FeedEvents_Items_Items_Relevance; + timestamp: Scalars['Float']['output']; + txHash: Scalars['String']['output']; + type: Query_FeedEvents_Items_Items_Type; + value?: Maybe; +}; +export declare enum Query_FeedEvents_Items_Items_Relevance { + High = "HIGH", + Low = "LOW", + Medium = "MEDIUM" +} +export declare enum Query_FeedEvents_Items_Items_Type { + Delegation = "DELEGATION", + DelegationVotesChanged = "DELEGATION_VOTES_CHANGED", + Proposal = "PROPOSAL", + ProposalExtended = "PROPOSAL_EXTENDED", + Transfer = "TRANSFER", + Vote = "VOTE" +} +export type Query_GetAddress_Arkham = { + __typename?: 'query_getAddress_arkham'; + entity?: Maybe; + entityType?: Maybe; + label?: Maybe; + twitter?: Maybe; +}; +export type Query_GetAddress_Ens = { + __typename?: 'query_getAddress_ens'; + avatar?: Maybe; + banner?: Maybe; + name?: Maybe; +}; +export type Query_GetAddresses_Results_Items = { + __typename?: 'query_getAddresses_results_items'; + address: Scalars['String']['output']; + arkham?: Maybe; + ens?: Maybe; + isContract: Scalars['Boolean']['output']; +}; +export type Query_GetAddresses_Results_Items_Arkham = { + __typename?: 'query_getAddresses_results_items_arkham'; + entity?: Maybe; + entityType?: Maybe; + label?: Maybe; + twitter?: Maybe; +}; +export type Query_GetAddresses_Results_Items_Ens = { + __typename?: 'query_getAddresses_results_items_ens'; + avatar?: Maybe; + banner?: Maybe; + name?: Maybe; +}; export type Query_GetDaoTokenTreasury_Items_Items = { __typename?: 'query_getDaoTokenTreasury_items_items'; /** Unix timestamp in milliseconds */ @@ -1072,6 +1341,23 @@ export type Query_HistoricalVotingPower_Items_Items_Transfer = { to: Scalars['String']['output']; value: Scalars['String']['output']; }; +export type Query_OffchainProposals_Items_Items = { + __typename?: 'query_offchainProposals_items_items'; + author: Scalars['String']['output']; + body: Scalars['String']['output']; + created: Scalars['Float']['output']; + discussion: Scalars['String']['output']; + end: Scalars['Float']['output']; + flagged: Scalars['Boolean']['output']; + id: Scalars['String']['output']; + link: Scalars['String']['output']; + spaceId: Scalars['String']['output']; + start: Scalars['Float']['output']; + state: Scalars['String']['output']; + title: Scalars['String']['output']; + type: Scalars['String']['output']; + updated: Scalars['Float']['output']; +}; export type Query_ProposalNonVoters_Items_Items = { __typename?: 'query_proposalNonVoters_items_items'; lastVoteTimestamp: Scalars['Float']['output']; @@ -1212,6 +1498,26 @@ export type Query_VotesByProposalId_Items_Items = { voterAddress: Scalars['String']['output']; votingPower: Scalars['String']['output']; }; +export type Query_VotesOffchainByProposalId_Items_Items = { + __typename?: 'query_votesOffchainByProposalId_items_items'; + choice?: Maybe; + created: Scalars['Float']['output']; + proposalId: Scalars['String']['output']; + proposalTitle: Scalars['String']['output']; + reason: Scalars['String']['output']; + voter: Scalars['String']['output']; + vp: Scalars['Float']['output']; +}; +export type Query_VotesOffchain_Items_Items = { + __typename?: 'query_votesOffchain_items_items'; + choice?: Maybe; + created: Scalars['Float']['output']; + proposalId: Scalars['String']['output']; + proposalTitle: Scalars['String']['output']; + reason: Scalars['String']['output']; + voter: Scalars['String']['output']; + vp: Scalars['Float']['output']; +}; export type Query_Votes_Items_Items = { __typename?: 'query_votes_items_items'; proposalId: Scalars['String']['output']; @@ -1223,6 +1529,11 @@ export type Query_Votes_Items_Items = { voterAddress: Scalars['String']['output']; votingPower: Scalars['String']['output']; }; +export type Query_VotingPowerByAccountId_Variation = { + __typename?: 'query_votingPowerByAccountId_variation'; + absoluteChange: Scalars['String']['output']; + percentageChange: Scalars['Float']['output']; +}; export type Query_VotingPowerVariationsByAccountId_Data = { __typename?: 'query_votingPowerVariationsByAccountId_data'; absoluteChange: Scalars['String']['output']; @@ -1254,9 +1565,15 @@ export type Query_VotingPowers_Items_Items = { accountId: Scalars['String']['output']; delegationsCount: Scalars['Float']['output']; proposalsCount: Scalars['Float']['output']; + variation: Query_VotingPowers_Items_Items_Variation; votesCount: Scalars['Float']['output']; votingPower: Scalars['String']['output']; }; +export type Query_VotingPowers_Items_Items_Variation = { + __typename?: 'query_votingPowers_items_items_variation'; + absoluteChange: Scalars['String']['output']; + percentageChange: Scalars['Float']['output']; +}; export declare enum Timestamp_Const { Timestamp = "timestamp" } @@ -1294,6 +1611,16 @@ export type VotesByProposalId_200_Response = { items: Array>; totalCount: Scalars['Float']['output']; }; +export type VotesOffchainByProposalId_200_Response = { + __typename?: 'votesOffchainByProposalId_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; +export type VotesOffchain_200_Response = { + __typename?: 'votesOffchain_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; export type Votes_200_Response = { __typename?: 'votes_200_response'; items: Array>; @@ -1304,6 +1631,7 @@ export type VotingPowerByAccountId_200_Response = { accountId: Scalars['String']['output']; delegationsCount: Scalars['Float']['output']; proposalsCount: Scalars['Float']['output']; + variation: Query_VotingPowerByAccountId_Variation; votesCount: Scalars['Float']['output']; votingPower: Scalars['String']['output']; }; @@ -1337,6 +1665,29 @@ export type GetDaOsQuery = { }>; }; }; +export type ListOffchainProposalsQueryVariables = Exact<{ + skip?: InputMaybe; + limit?: InputMaybe; + orderDirection?: InputMaybe; + status?: InputMaybe; + fromDate?: InputMaybe; +}>; +export type ListOffchainProposalsQuery = { + __typename?: 'Query'; + offchainProposals?: { + __typename?: 'offchainProposals_200_response'; + totalCount: number; + items: Array<{ + __typename?: 'query_offchainProposals_items_items'; + id: string; + title: string; + discussion: string; + link: string; + state: string; + created: number; + } | null>; + } | null; +}; export type ProposalNonVotersQueryVariables = Exact<{ id: Scalars['String']['input']; addresses?: InputMaybe; @@ -1474,6 +1825,7 @@ export type ListHistoricalVotingPowerQuery = { } | null; }; export declare const GetDaOsDocument: DocumentNode; +export declare const ListOffchainProposalsDocument: DocumentNode; export declare const ProposalNonVotersDocument: DocumentNode; export declare const GetProposalByIdDocument: DocumentNode; export declare const ListProposalsDocument: DocumentNode; diff --git a/packages/anticapture-client/dist/gql/graphql.js b/packages/anticapture-client/dist/gql/graphql.js index e4b06321..f670e10e 100644 --- a/packages/anticapture-client/dist/gql/graphql.js +++ b/packages/anticapture-client/dist/gql/graphql.js @@ -1,7 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.QueryInput_VotingPowers_OrderDirection = exports.QueryInput_VotingPowers_OrderBy = exports.QueryInput_VotingPowerVariations_OrderDirection = exports.QueryInput_Votes_OrderDirection = exports.QueryInput_Votes_OrderBy = exports.QueryInput_VotesByProposalId_OrderDirection = exports.QueryInput_VotesByProposalId_OrderBy = exports.QueryInput_Transfers_SortOrder = exports.QueryInput_Transfers_SortBy = exports.QueryInput_Transactions_SortOrder = exports.QueryInput_Token_Currency = exports.QueryInput_TokenMetrics_OrderDirection = exports.QueryInput_TokenMetrics_MetricType = exports.QueryInput_Proposals_OrderDirection = exports.QueryInput_Proposals_IncludeOptimisticProposals = exports.QueryInput_ProposalsActivity_UserVoteFilter = exports.QueryInput_ProposalsActivity_OrderDirection = exports.QueryInput_ProposalsActivity_OrderBy = exports.QueryInput_ProposalNonVoters_OrderDirection = exports.QueryInput_LastUpdate_Chart = exports.QueryInput_HistoricalVotingPower_OrderDirection = exports.QueryInput_HistoricalVotingPower_OrderBy = exports.QueryInput_HistoricalVotingPowerByAccountId_OrderDirection = exports.QueryInput_HistoricalVotingPowerByAccountId_OrderBy = exports.QueryInput_HistoricalDelegations_OrderDirection = exports.QueryInput_HistoricalBalances_OrderDirection = exports.QueryInput_HistoricalBalances_OrderBy = exports.QueryInput_GetTotalTreasury_Order = exports.QueryInput_GetTotalTreasury_Days = exports.QueryInput_GetLiquidTreasury_Order = exports.QueryInput_GetLiquidTreasury_Days = exports.QueryInput_GetDaoTokenTreasury_Order = exports.QueryInput_GetDaoTokenTreasury_Days = exports.QueryInput_DelegationPercentageByDay_OrderDirection = exports.QueryInput_CompareVotes_Days = exports.QueryInput_CompareTreasury_Days = exports.QueryInput_CompareTotalSupply_Days = exports.QueryInput_CompareProposals_Days = exports.QueryInput_CompareLendingSupply_Days = exports.QueryInput_CompareDexSupply_Days = exports.QueryInput_CompareDelegatedSupply_Days = exports.QueryInput_CompareCirculatingSupply_Days = exports.QueryInput_CompareCexSupply_Days = exports.QueryInput_CompareAverageTurnout_Days = exports.QueryInput_CompareActiveSupply_Days = exports.QueryInput_AccountInteractions_OrderDirection = exports.QueryInput_AccountInteractions_OrderBy = exports.QueryInput_AccountBalances_OrderDirection = exports.QueryInput_AccountBalanceVariations_OrderDirection = exports.HttpMethod = void 0; -exports.ListHistoricalVotingPowerDocument = exports.ListVotesDocument = exports.ListProposalsDocument = exports.GetProposalByIdDocument = exports.ProposalNonVotersDocument = exports.GetDaOsDocument = exports.Timestamp_Const = void 0; +exports.QueryInput_Token_Currency = exports.QueryInput_TokenMetrics_OrderDirection = exports.QueryInput_TokenMetrics_MetricType = exports.QueryInput_Proposals_OrderDirection = exports.QueryInput_Proposals_IncludeOptimisticProposals = exports.QueryInput_ProposalsActivity_UserVoteFilter = exports.QueryInput_ProposalsActivity_OrderDirection = exports.QueryInput_ProposalsActivity_OrderBy = exports.QueryInput_ProposalNonVoters_OrderDirection = exports.QueryInput_OffchainProposals_OrderDirection = exports.QueryInput_LastUpdate_Chart = exports.QueryInput_HistoricalVotingPower_OrderDirection = exports.QueryInput_HistoricalVotingPower_OrderBy = exports.QueryInput_HistoricalVotingPowerByAccountId_OrderDirection = exports.QueryInput_HistoricalVotingPowerByAccountId_OrderBy = exports.QueryInput_HistoricalDelegations_OrderDirection = exports.QueryInput_HistoricalBalances_OrderDirection = exports.QueryInput_HistoricalBalances_OrderBy = exports.QueryInput_GetTotalTreasury_Order = exports.QueryInput_GetTotalTreasury_Days = exports.QueryInput_GetLiquidTreasury_Order = exports.QueryInput_GetLiquidTreasury_Days = exports.QueryInput_GetDaoTokenTreasury_Order = exports.QueryInput_GetDaoTokenTreasury_Days = exports.QueryInput_FeedEvents_Type = exports.QueryInput_FeedEvents_Relevance = exports.QueryInput_FeedEvents_OrderDirection = exports.QueryInput_FeedEvents_OrderBy = exports.QueryInput_Delegators_OrderDirection = exports.QueryInput_Delegators_OrderBy = exports.QueryInput_Delegations_OrderDirection = exports.QueryInput_Delegations_OrderBy = exports.QueryInput_DelegationPercentageByDay_OrderDirection = exports.QueryInput_CompareVotes_Days = exports.QueryInput_CompareTreasury_Days = exports.QueryInput_CompareTotalSupply_Days = exports.QueryInput_CompareProposals_Days = exports.QueryInput_CompareLendingSupply_Days = exports.QueryInput_CompareDexSupply_Days = exports.QueryInput_CompareDelegatedSupply_Days = exports.QueryInput_CompareCirculatingSupply_Days = exports.QueryInput_CompareCexSupply_Days = exports.QueryInput_CompareAverageTurnout_Days = exports.QueryInput_CompareActiveSupply_Days = exports.QueryInput_AccountInteractions_OrderDirection = exports.QueryInput_AccountInteractions_OrderBy = exports.QueryInput_AccountBalances_OrderDirection = exports.QueryInput_AccountBalances_OrderBy = exports.QueryInput_AccountBalanceVariations_OrderDirection = exports.HttpMethod = void 0; +exports.ListHistoricalVotingPowerDocument = exports.ListVotesDocument = exports.ListProposalsDocument = exports.GetProposalByIdDocument = exports.ProposalNonVotersDocument = exports.ListOffchainProposalsDocument = exports.GetDaOsDocument = exports.Timestamp_Const = exports.Query_FeedEvents_Items_Items_Type = exports.Query_FeedEvents_Items_Items_Relevance = exports.QueryInput_VotingPowers_OrderDirection = exports.QueryInput_VotingPowers_OrderBy = exports.QueryInput_VotingPowerVariations_OrderDirection = exports.QueryInput_Votes_OrderDirection = exports.QueryInput_Votes_OrderBy = exports.QueryInput_VotesOffchain_OrderDirection = exports.QueryInput_VotesOffchain_OrderBy = exports.QueryInput_VotesOffchainByProposalId_OrderDirection = exports.QueryInput_VotesOffchainByProposalId_OrderBy = exports.QueryInput_VotesByProposalId_OrderDirection = exports.QueryInput_VotesByProposalId_OrderBy = exports.QueryInput_Transfers_SortOrder = exports.QueryInput_Transfers_SortBy = exports.QueryInput_Transactions_SortOrder = void 0; var HttpMethod; (function (HttpMethod) { HttpMethod["Connect"] = "CONNECT"; @@ -19,6 +19,11 @@ var QueryInput_AccountBalanceVariations_OrderDirection; QueryInput_AccountBalanceVariations_OrderDirection["Asc"] = "asc"; QueryInput_AccountBalanceVariations_OrderDirection["Desc"] = "desc"; })(QueryInput_AccountBalanceVariations_OrderDirection || (exports.QueryInput_AccountBalanceVariations_OrderDirection = QueryInput_AccountBalanceVariations_OrderDirection = {})); +var QueryInput_AccountBalances_OrderBy; +(function (QueryInput_AccountBalances_OrderBy) { + QueryInput_AccountBalances_OrderBy["Balance"] = "balance"; + QueryInput_AccountBalances_OrderBy["Variation"] = "variation"; +})(QueryInput_AccountBalances_OrderBy || (exports.QueryInput_AccountBalances_OrderBy = QueryInput_AccountBalances_OrderBy = {})); var QueryInput_AccountBalances_OrderDirection; (function (QueryInput_AccountBalances_OrderDirection) { QueryInput_AccountBalances_OrderDirection["Asc"] = "asc"; @@ -127,6 +132,51 @@ var QueryInput_DelegationPercentageByDay_OrderDirection; QueryInput_DelegationPercentageByDay_OrderDirection["Asc"] = "asc"; QueryInput_DelegationPercentageByDay_OrderDirection["Desc"] = "desc"; })(QueryInput_DelegationPercentageByDay_OrderDirection || (exports.QueryInput_DelegationPercentageByDay_OrderDirection = QueryInput_DelegationPercentageByDay_OrderDirection = {})); +var QueryInput_Delegations_OrderBy; +(function (QueryInput_Delegations_OrderBy) { + QueryInput_Delegations_OrderBy["Amount"] = "amount"; + QueryInput_Delegations_OrderBy["Timestamp"] = "timestamp"; +})(QueryInput_Delegations_OrderBy || (exports.QueryInput_Delegations_OrderBy = QueryInput_Delegations_OrderBy = {})); +var QueryInput_Delegations_OrderDirection; +(function (QueryInput_Delegations_OrderDirection) { + QueryInput_Delegations_OrderDirection["Asc"] = "asc"; + QueryInput_Delegations_OrderDirection["Desc"] = "desc"; +})(QueryInput_Delegations_OrderDirection || (exports.QueryInput_Delegations_OrderDirection = QueryInput_Delegations_OrderDirection = {})); +var QueryInput_Delegators_OrderBy; +(function (QueryInput_Delegators_OrderBy) { + QueryInput_Delegators_OrderBy["Amount"] = "amount"; + QueryInput_Delegators_OrderBy["Timestamp"] = "timestamp"; +})(QueryInput_Delegators_OrderBy || (exports.QueryInput_Delegators_OrderBy = QueryInput_Delegators_OrderBy = {})); +var QueryInput_Delegators_OrderDirection; +(function (QueryInput_Delegators_OrderDirection) { + QueryInput_Delegators_OrderDirection["Asc"] = "asc"; + QueryInput_Delegators_OrderDirection["Desc"] = "desc"; +})(QueryInput_Delegators_OrderDirection || (exports.QueryInput_Delegators_OrderDirection = QueryInput_Delegators_OrderDirection = {})); +var QueryInput_FeedEvents_OrderBy; +(function (QueryInput_FeedEvents_OrderBy) { + QueryInput_FeedEvents_OrderBy["Timestamp"] = "timestamp"; + QueryInput_FeedEvents_OrderBy["Value"] = "value"; +})(QueryInput_FeedEvents_OrderBy || (exports.QueryInput_FeedEvents_OrderBy = QueryInput_FeedEvents_OrderBy = {})); +var QueryInput_FeedEvents_OrderDirection; +(function (QueryInput_FeedEvents_OrderDirection) { + QueryInput_FeedEvents_OrderDirection["Asc"] = "asc"; + QueryInput_FeedEvents_OrderDirection["Desc"] = "desc"; +})(QueryInput_FeedEvents_OrderDirection || (exports.QueryInput_FeedEvents_OrderDirection = QueryInput_FeedEvents_OrderDirection = {})); +var QueryInput_FeedEvents_Relevance; +(function (QueryInput_FeedEvents_Relevance) { + QueryInput_FeedEvents_Relevance["High"] = "HIGH"; + QueryInput_FeedEvents_Relevance["Low"] = "LOW"; + QueryInput_FeedEvents_Relevance["Medium"] = "MEDIUM"; +})(QueryInput_FeedEvents_Relevance || (exports.QueryInput_FeedEvents_Relevance = QueryInput_FeedEvents_Relevance = {})); +var QueryInput_FeedEvents_Type; +(function (QueryInput_FeedEvents_Type) { + QueryInput_FeedEvents_Type["Delegation"] = "DELEGATION"; + QueryInput_FeedEvents_Type["DelegationVotesChanged"] = "DELEGATION_VOTES_CHANGED"; + QueryInput_FeedEvents_Type["Proposal"] = "PROPOSAL"; + QueryInput_FeedEvents_Type["ProposalExtended"] = "PROPOSAL_EXTENDED"; + QueryInput_FeedEvents_Type["Transfer"] = "TRANSFER"; + QueryInput_FeedEvents_Type["Vote"] = "VOTE"; +})(QueryInput_FeedEvents_Type || (exports.QueryInput_FeedEvents_Type = QueryInput_FeedEvents_Type = {})); var QueryInput_GetDaoTokenTreasury_Days; (function (QueryInput_GetDaoTokenTreasury_Days) { QueryInput_GetDaoTokenTreasury_Days["7d"] = "_7d"; @@ -207,6 +257,11 @@ var QueryInput_LastUpdate_Chart; QueryInput_LastUpdate_Chart["CostComparison"] = "cost_comparison"; QueryInput_LastUpdate_Chart["TokenDistribution"] = "token_distribution"; })(QueryInput_LastUpdate_Chart || (exports.QueryInput_LastUpdate_Chart = QueryInput_LastUpdate_Chart = {})); +var QueryInput_OffchainProposals_OrderDirection; +(function (QueryInput_OffchainProposals_OrderDirection) { + QueryInput_OffchainProposals_OrderDirection["Asc"] = "asc"; + QueryInput_OffchainProposals_OrderDirection["Desc"] = "desc"; +})(QueryInput_OffchainProposals_OrderDirection || (exports.QueryInput_OffchainProposals_OrderDirection = QueryInput_OffchainProposals_OrderDirection = {})); var QueryInput_ProposalNonVoters_OrderDirection; (function (QueryInput_ProposalNonVoters_OrderDirection) { QueryInput_ProposalNonVoters_OrderDirection["Asc"] = "asc"; @@ -286,6 +341,26 @@ var QueryInput_VotesByProposalId_OrderDirection; QueryInput_VotesByProposalId_OrderDirection["Asc"] = "asc"; QueryInput_VotesByProposalId_OrderDirection["Desc"] = "desc"; })(QueryInput_VotesByProposalId_OrderDirection || (exports.QueryInput_VotesByProposalId_OrderDirection = QueryInput_VotesByProposalId_OrderDirection = {})); +var QueryInput_VotesOffchainByProposalId_OrderBy; +(function (QueryInput_VotesOffchainByProposalId_OrderBy) { + QueryInput_VotesOffchainByProposalId_OrderBy["Created"] = "created"; + QueryInput_VotesOffchainByProposalId_OrderBy["Vp"] = "vp"; +})(QueryInput_VotesOffchainByProposalId_OrderBy || (exports.QueryInput_VotesOffchainByProposalId_OrderBy = QueryInput_VotesOffchainByProposalId_OrderBy = {})); +var QueryInput_VotesOffchainByProposalId_OrderDirection; +(function (QueryInput_VotesOffchainByProposalId_OrderDirection) { + QueryInput_VotesOffchainByProposalId_OrderDirection["Asc"] = "asc"; + QueryInput_VotesOffchainByProposalId_OrderDirection["Desc"] = "desc"; +})(QueryInput_VotesOffchainByProposalId_OrderDirection || (exports.QueryInput_VotesOffchainByProposalId_OrderDirection = QueryInput_VotesOffchainByProposalId_OrderDirection = {})); +var QueryInput_VotesOffchain_OrderBy; +(function (QueryInput_VotesOffchain_OrderBy) { + QueryInput_VotesOffchain_OrderBy["Created"] = "created"; + QueryInput_VotesOffchain_OrderBy["Vp"] = "vp"; +})(QueryInput_VotesOffchain_OrderBy || (exports.QueryInput_VotesOffchain_OrderBy = QueryInput_VotesOffchain_OrderBy = {})); +var QueryInput_VotesOffchain_OrderDirection; +(function (QueryInput_VotesOffchain_OrderDirection) { + QueryInput_VotesOffchain_OrderDirection["Asc"] = "asc"; + QueryInput_VotesOffchain_OrderDirection["Desc"] = "desc"; +})(QueryInput_VotesOffchain_OrderDirection || (exports.QueryInput_VotesOffchain_OrderDirection = QueryInput_VotesOffchain_OrderDirection = {})); var QueryInput_Votes_OrderBy; (function (QueryInput_Votes_OrderBy) { QueryInput_Votes_OrderBy["Timestamp"] = "timestamp"; @@ -304,6 +379,7 @@ var QueryInput_VotingPowerVariations_OrderDirection; var QueryInput_VotingPowers_OrderBy; (function (QueryInput_VotingPowers_OrderBy) { QueryInput_VotingPowers_OrderBy["DelegationsCount"] = "delegationsCount"; + QueryInput_VotingPowers_OrderBy["Variation"] = "variation"; QueryInput_VotingPowers_OrderBy["VotingPower"] = "votingPower"; })(QueryInput_VotingPowers_OrderBy || (exports.QueryInput_VotingPowers_OrderBy = QueryInput_VotingPowers_OrderBy = {})); var QueryInput_VotingPowers_OrderDirection; @@ -311,11 +387,27 @@ var QueryInput_VotingPowers_OrderDirection; QueryInput_VotingPowers_OrderDirection["Asc"] = "asc"; QueryInput_VotingPowers_OrderDirection["Desc"] = "desc"; })(QueryInput_VotingPowers_OrderDirection || (exports.QueryInput_VotingPowers_OrderDirection = QueryInput_VotingPowers_OrderDirection = {})); +var Query_FeedEvents_Items_Items_Relevance; +(function (Query_FeedEvents_Items_Items_Relevance) { + Query_FeedEvents_Items_Items_Relevance["High"] = "HIGH"; + Query_FeedEvents_Items_Items_Relevance["Low"] = "LOW"; + Query_FeedEvents_Items_Items_Relevance["Medium"] = "MEDIUM"; +})(Query_FeedEvents_Items_Items_Relevance || (exports.Query_FeedEvents_Items_Items_Relevance = Query_FeedEvents_Items_Items_Relevance = {})); +var Query_FeedEvents_Items_Items_Type; +(function (Query_FeedEvents_Items_Items_Type) { + Query_FeedEvents_Items_Items_Type["Delegation"] = "DELEGATION"; + Query_FeedEvents_Items_Items_Type["DelegationVotesChanged"] = "DELEGATION_VOTES_CHANGED"; + Query_FeedEvents_Items_Items_Type["Proposal"] = "PROPOSAL"; + Query_FeedEvents_Items_Items_Type["ProposalExtended"] = "PROPOSAL_EXTENDED"; + Query_FeedEvents_Items_Items_Type["Transfer"] = "TRANSFER"; + Query_FeedEvents_Items_Items_Type["Vote"] = "VOTE"; +})(Query_FeedEvents_Items_Items_Type || (exports.Query_FeedEvents_Items_Items_Type = Query_FeedEvents_Items_Items_Type = {})); var Timestamp_Const; (function (Timestamp_Const) { Timestamp_Const["Timestamp"] = "timestamp"; })(Timestamp_Const || (exports.Timestamp_Const = Timestamp_Const = {})); exports.GetDaOsDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GetDAOs" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "daos" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "items" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "votingDelay" } }, { "kind": "Field", "name": { "kind": "Name", "value": "chainId" } }] } }] } }] } }] }; +exports.ListOffchainProposalsDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "ListOffchainProposals" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "skip" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "NonNegativeInt" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "PositiveInt" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "orderDirection" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "queryInput_offchainProposals_orderDirection" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "status" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "JSON" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "fromDate" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Float" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "offchainProposals" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "skip" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "skip" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "orderDirection" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "orderDirection" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "status" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "status" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "fromDate" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "fromDate" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "items" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "title" } }, { "kind": "Field", "name": { "kind": "Name", "value": "discussion" } }, { "kind": "Field", "name": { "kind": "Name", "value": "link" } }, { "kind": "Field", "name": { "kind": "Name", "value": "state" } }, { "kind": "Field", "name": { "kind": "Name", "value": "created" } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "totalCount" } }] } }] } }] }; exports.ProposalNonVotersDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "ProposalNonVoters" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "addresses" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "JSON" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "proposalNonVoters" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "addresses" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "addresses" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "items" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "voter" } }] } }] } }] } }] }; exports.GetProposalByIdDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "GetProposalById" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "proposal" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "daoId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "proposerAccountId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "title" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "startBlock" } }, { "kind": "Field", "name": { "kind": "Name", "value": "endBlock" } }, { "kind": "Field", "name": { "kind": "Name", "value": "endTimestamp" } }, { "kind": "Field", "name": { "kind": "Name", "value": "timestamp" } }, { "kind": "Field", "name": { "kind": "Name", "value": "status" } }, { "kind": "Field", "name": { "kind": "Name", "value": "forVotes" } }, { "kind": "Field", "name": { "kind": "Name", "value": "againstVotes" } }, { "kind": "Field", "name": { "kind": "Name", "value": "abstainVotes" } }, { "kind": "Field", "name": { "kind": "Name", "value": "txHash" } }] } }] } }] }; exports.ListProposalsDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "ListProposals" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "skip" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "NonNegativeInt" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "PositiveInt" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "orderDirection" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "queryInput_proposals_orderDirection" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "status" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "JSON" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "fromDate" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Float" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "fromEndDate" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Float" } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "includeOptimisticProposals" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "queryInput_proposals_includeOptimisticProposals" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "proposals" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "skip" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "skip" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "orderDirection" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "orderDirection" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "status" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "status" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "fromDate" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "fromDate" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "fromEndDate" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "fromEndDate" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "includeOptimisticProposals" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "includeOptimisticProposals" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "items" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "daoId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "proposerAccountId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "title" } }, { "kind": "Field", "name": { "kind": "Name", "value": "description" } }, { "kind": "Field", "name": { "kind": "Name", "value": "startBlock" } }, { "kind": "Field", "name": { "kind": "Name", "value": "endBlock" } }, { "kind": "Field", "name": { "kind": "Name", "value": "endTimestamp" } }, { "kind": "Field", "name": { "kind": "Name", "value": "timestamp" } }, { "kind": "Field", "name": { "kind": "Name", "value": "status" } }, { "kind": "Field", "name": { "kind": "Name", "value": "forVotes" } }, { "kind": "Field", "name": { "kind": "Name", "value": "againstVotes" } }, { "kind": "Field", "name": { "kind": "Name", "value": "abstainVotes" } }, { "kind": "Field", "name": { "kind": "Name", "value": "txHash" } }] } }, { "kind": "Field", "name": { "kind": "Name", "value": "totalCount" } }] } }] } }] }; diff --git a/packages/anticapture-client/dist/index.d.ts b/packages/anticapture-client/dist/index.d.ts index 0adeebe1..37ca6054 100644 --- a/packages/anticapture-client/dist/index.d.ts +++ b/packages/anticapture-client/dist/index.d.ts @@ -2,4 +2,4 @@ export { AnticaptureClient } from './anticapture-client'; export type { VoteWithDaoId } from './anticapture-client'; export type { GetDaOsQuery, GetProposalByIdQuery, GetProposalByIdQueryVariables, ListProposalsQuery, ListProposalsQueryVariables, ListVotesQuery, ListVotesQueryVariables, ListHistoricalVotingPowerQuery, ListHistoricalVotingPowerQueryVariables } from './gql/graphql'; export { QueryInput_Proposals_OrderDirection, QueryInput_HistoricalVotingPower_OrderBy, QueryInput_HistoricalVotingPower_OrderDirection, QueryInput_Votes_OrderBy, QueryInput_Votes_OrderDirection } from './gql/graphql'; -export type { ProcessedVotingPowerHistory } from './schemas'; +export type { ProcessedVotingPowerHistory, OffchainProposalItem } from './schemas'; diff --git a/packages/anticapture-client/dist/schemas.d.ts b/packages/anticapture-client/dist/schemas.d.ts index ca577146..ecca3558 100644 --- a/packages/anticapture-client/dist/schemas.d.ts +++ b/packages/anticapture-client/dist/schemas.d.ts @@ -113,14 +113,14 @@ declare const HistoricalVotingPowerItemSchema: z.ZodObject<{ value: z.ZodString; previousDelegate: z.ZodNullable; }, "strip", z.ZodTypeAny, { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; }, { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; }>>; transfer: z.ZodNullable>; }, "strip", z.ZodTypeAny, { - delta: string; timestamp: string; + delta: string; votingPower: string; daoId: string; transactionHash: string; accountId: string; logIndex: number; delegation: { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; } | null; transfer: { + value: string; from: string; to: string; - value: string; } | null; }, { - delta: string; timestamp: string; + delta: string; votingPower: string; daoId: string; transactionHash: string; accountId: string; logIndex: number; delegation: { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; } | null; transfer: { + value: string; from: string; to: string; - value: string; } | null; }>; export declare const SafeHistoricalVotingPowerResponseSchema: z.ZodEffects; }, "strip", z.ZodTypeAny, { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; }, { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; }>>; transfer: z.ZodNullable>; }, "strip", z.ZodTypeAny, { - delta: string; timestamp: string; + delta: string; votingPower: string; daoId: string; transactionHash: string; accountId: string; logIndex: number; delegation: { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; } | null; transfer: { + value: string; from: string; to: string; - value: string; } | null; }, { - delta: string; timestamp: string; + delta: string; votingPower: string; daoId: string; transactionHash: string; accountId: string; logIndex: number; delegation: { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; } | null; transfer: { + value: string; from: string; to: string; - value: string; } | null; }>>, "many">; totalCount: z.ZodNumber; }, "strip", z.ZodTypeAny, { items: ({ - delta: string; timestamp: string; + delta: string; votingPower: string; daoId: string; transactionHash: string; accountId: string; logIndex: number; delegation: { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; } | null; transfer: { + value: string; from: string; to: string; - value: string; } | null; } | null)[]; totalCount: number; }, { items: ({ - delta: string; timestamp: string; + delta: string; votingPower: string; daoId: string; transactionHash: string; accountId: string; logIndex: number; delegation: { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; } | null; transfer: { + value: string; from: string; to: string; - value: string; } | null; } | null)[]; totalCount: number; @@ -302,23 +302,23 @@ export declare const SafeHistoricalVotingPowerResponseSchema: z.ZodEffects, { historicalVotingPower: { items: { - delta: string; timestamp: string; + delta: string; votingPower: string; daoId: string; transactionHash: string; accountId: string; logIndex: number; delegation: { + value: string; from: string; to: string; - value: string; previousDelegate: string | null; } | null; transfer: { + value: string; from: string; to: string; - value: string; } | null; }[]; totalCount: number; @@ -374,23 +374,23 @@ export declare const SafeHistoricalVotingPowerResponseSchema: z.ZodEffects; +export declare const OffchainProposalItemSchema: z.ZodObject<{ + id: z.ZodString; + title: z.ZodString; + discussion: z.ZodString; + link: z.ZodString; + state: z.ZodString; + created: z.ZodNumber; +}, "strip", z.ZodTypeAny, { + link: string; + created: number; + id: string; + title: string; + discussion: string; + state: string; +}, { + link: string; + created: number; + id: string; + title: string; + discussion: string; + state: string; +}>; +export type OffchainProposalItem = z.infer; +export declare const SafeOffchainProposalsResponseSchema: z.ZodEffects>, "many">; + totalCount: z.ZodNumber; + }, "strip", z.ZodTypeAny, { + items: ({ + link: string; + created: number; + id: string; + title: string; + discussion: string; + state: string; + } | null)[]; + totalCount: number; + }, { + items: ({ + link: string; + created: number; + id: string; + title: string; + discussion: string; + state: string; + } | null)[]; + totalCount: number; + }>>; +}, "strip", z.ZodTypeAny, { + offchainProposals: { + items: ({ + link: string; + created: number; + id: string; + title: string; + discussion: string; + state: string; + } | null)[]; + totalCount: number; + } | null; +}, { + offchainProposals: { + items: ({ + link: string; + created: number; + id: string; + title: string; + discussion: string; + state: string; + } | null)[]; + totalCount: number; + } | null; +}>, { + offchainProposals: { + items: { + link: string; + created: number; + id: string; + title: string; + discussion: string; + state: string; + }[]; + totalCount: number; + }; +}, { + offchainProposals: { + items: ({ + link: string; + created: number; + id: string; + title: string; + discussion: string; + state: string; + } | null)[]; + totalCount: number; + } | null; +}>; type SafeProposalsResponse = z.infer; type SafeHistoricalVotingPowerResponse = z.infer; export type ProcessedVotingPowerHistory = z.infer & { diff --git a/packages/anticapture-client/dist/schemas.js b/packages/anticapture-client/dist/schemas.js index affb1c0a..77ad3d7a 100644 --- a/packages/anticapture-client/dist/schemas.js +++ b/packages/anticapture-client/dist/schemas.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.SafeProposalNonVotersResponseSchema = exports.SafeVotesResponseSchema = exports.SafeHistoricalVotingPowerResponseSchema = exports.SafeProposalByIdResponseSchema = exports.SafeProposalsResponseSchema = exports.SafeDaosResponseSchema = void 0; +exports.SafeOffchainProposalsResponseSchema = exports.OffchainProposalItemSchema = exports.SafeProposalNonVotersResponseSchema = exports.SafeVotesResponseSchema = exports.SafeHistoricalVotingPowerResponseSchema = exports.SafeProposalByIdResponseSchema = exports.SafeProposalsResponseSchema = exports.SafeDaosResponseSchema = void 0; exports.processProposals = processProposals; exports.processVotingPowerHistory = processVotingPowerHistory; const zod_1 = require("zod"); @@ -116,6 +116,31 @@ exports.SafeProposalNonVotersResponseSchema = zod_1.z.object({ } }; }); +exports.OffchainProposalItemSchema = zod_1.z.object({ + id: zod_1.z.string(), + title: zod_1.z.string(), + discussion: zod_1.z.string(), + link: zod_1.z.string(), + state: zod_1.z.string(), + created: zod_1.z.number(), +}); +exports.SafeOffchainProposalsResponseSchema = zod_1.z.object({ + offchainProposals: zod_1.z.object({ + items: zod_1.z.array(exports.OffchainProposalItemSchema.nullable()), + totalCount: zod_1.z.number(), + }).nullable(), +}).transform((data) => { + if (!data.offchainProposals) { + console.warn('OffchainProposalsResponse has null offchainProposals:', data); + return { offchainProposals: { items: [], totalCount: 0 } }; + } + return { + offchainProposals: { + ...data.offchainProposals, + items: data.offchainProposals.items.filter((item) => item !== null), + }, + }; +}); // Internal helper function to process validated proposals function processProposals(validated, daoId) { return validated.proposals.items.reduce((acc, proposal) => { diff --git a/packages/anticapture-client/jest.config.js b/packages/anticapture-client/jest.config.js index 7439fe3d..79ceb218 100644 --- a/packages/anticapture-client/jest.config.js +++ b/packages/anticapture-client/jest.config.js @@ -3,6 +3,13 @@ module.exports = { testEnvironment: 'node', roots: ['/tests'], testMatch: ['**/*.test.ts'], + transformIgnorePatterns: [ + '/node_modules/.pnpm/(?!(msw|@mswjs|until-async|@bundled-es-modules)[@+])', + ], + transform: { + '^.+\\.tsx?$': 'ts-jest', + '^.+\\.jsx?$': ['ts-jest', { tsconfig: { allowJs: true } }], + }, collectCoverageFrom: [ 'src/**/*.{ts,js}', '!src/**/*.d.ts', diff --git a/packages/anticapture-client/package.json b/packages/anticapture-client/package.json index 4c2cdc83..5ed78f54 100644 --- a/packages/anticapture-client/package.json +++ b/packages/anticapture-client/package.json @@ -29,6 +29,7 @@ "@types/node": "^20.17.46", "dotenv-cli": "^8.0.0", "jest": "^29.0.0", + "msw": "^2.12.10", "ts-jest": "^29.0.0", "typescript": "^5.8.3" }, diff --git a/packages/anticapture-client/queries/offchain-proposals.graphql b/packages/anticapture-client/queries/offchain-proposals.graphql new file mode 100644 index 00000000..9eca3ee8 --- /dev/null +++ b/packages/anticapture-client/queries/offchain-proposals.graphql @@ -0,0 +1,13 @@ +query ListOffchainProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_offchainProposals_orderDirection, $status: JSON, $fromDate: Float) { + offchainProposals(skip: $skip, limit: $limit, orderDirection: $orderDirection, status: $status, fromDate: $fromDate) { + items { + id + title + discussion + link + state + created + } + totalCount + } +} diff --git a/packages/anticapture-client/src/anticapture-client.ts b/packages/anticapture-client/src/anticapture-client.ts index 1fe23c3a..a473a094 100644 --- a/packages/anticapture-client/src/anticapture-client.ts +++ b/packages/anticapture-client/src/anticapture-client.ts @@ -12,10 +12,12 @@ import type { ListHistoricalVotingPowerQueryVariables, ListVotesQuery, ListVotesQueryVariables, - ProposalNonVotersQueryVariables + ProposalNonVotersQueryVariables, + ListOffchainProposalsQueryVariables } from './gql/graphql'; -import { GetDaOsDocument, GetProposalByIdDocument, ListProposalsDocument, ListHistoricalVotingPowerDocument, ListVotesDocument, ProposalNonVotersDocument, QueryInput_Votes_OrderBy, QueryInput_Votes_OrderDirection } from './gql/graphql'; -import { SafeDaosResponseSchema, SafeProposalByIdResponseSchema, SafeProposalsResponseSchema, SafeHistoricalVotingPowerResponseSchema, SafeVotesResponseSchema, SafeProposalNonVotersResponseSchema, processProposals, processVotingPowerHistory, ProcessedVotingPowerHistory } from './schemas'; +import { GetDaOsDocument, GetProposalByIdDocument, ListProposalsDocument, ListHistoricalVotingPowerDocument, ListVotesDocument, ProposalNonVotersDocument, ListOffchainProposalsDocument, QueryInput_Votes_OrderBy, QueryInput_Votes_OrderDirection } from './gql/graphql'; +import { SafeDaosResponseSchema, SafeProposalByIdResponseSchema, SafeProposalsResponseSchema, SafeHistoricalVotingPowerResponseSchema, SafeVotesResponseSchema, SafeProposalNonVotersResponseSchema, SafeOffchainProposalsResponseSchema, processProposals, processVotingPowerHistory, ProcessedVotingPowerHistory } from './schemas'; +import type { OffchainProposalItem } from './schemas'; type ProposalItems = NonNullable['items']; type VotingPowerHistoryItems = ProcessedVotingPowerHistory[]; @@ -329,4 +331,44 @@ export class AnticaptureClient { return allVotes; } + + /** + * Lists offchain (Snapshot) proposals from all DAOs or a specific DAO + * @param variables Query variables (skip, limit, orderDirection, status, fromDate) + * @param daoId Optional specific DAO ID. If not provided, queries all DAOs + * @returns Array of offchain proposal items with daoId attached + */ + async listOffchainProposals( + variables?: ListOffchainProposalsQueryVariables, + daoId?: string + ): Promise<(OffchainProposalItem & { daoId: string })[]> { + if (!daoId) { + const allDAOs = await this.getDAOs(); + const allProposals: (OffchainProposalItem & { daoId: string })[] = []; + + for (const dao of allDAOs) { + try { + const validated = await this.query(ListOffchainProposalsDocument, SafeOffchainProposalsResponseSchema, variables, dao.id); + const items = validated.offchainProposals.items.map(item => ({ ...item, daoId: dao.id })); + if (items.length > 0) { + allProposals.push(...items); + } + } catch (error) { + console.warn(`Skipping offchain proposals for ${dao.id} due to API error: ${error instanceof Error ? error.message : error}`); + } + } + + // Sort by created timestamp desc (most recent first) + allProposals.sort((a, b) => b.created - a.created); + return allProposals; + } + + try { + const validated = await this.query(ListOffchainProposalsDocument, SafeOffchainProposalsResponseSchema, variables, daoId); + return validated.offchainProposals.items.map(item => ({ ...item, daoId })); + } catch (error) { + console.warn(`Error querying offchain proposals for DAO ${daoId}: ${error instanceof Error ? error.message : error}`); + return []; + } + } } \ No newline at end of file diff --git a/packages/anticapture-client/src/gql/gql.ts b/packages/anticapture-client/src/gql/gql.ts index 23ec75f5..98775c49 100644 --- a/packages/anticapture-client/src/gql/gql.ts +++ b/packages/anticapture-client/src/gql/gql.ts @@ -15,6 +15,7 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document- */ type Documents = { "query GetDAOs {\n daos {\n items {\n id\n votingDelay\n chainId\n }\n }\n}": typeof types.GetDaOsDocument, + "query ListOffchainProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_offchainProposals_orderDirection, $status: JSON, $fromDate: Float) {\n offchainProposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n ) {\n items {\n id\n title\n discussion\n link\n state\n created\n }\n totalCount\n }\n}": typeof types.ListOffchainProposalsDocument, "query ProposalNonVoters($id: String!, $addresses: JSON) {\n proposalNonVoters(id: $id, addresses: $addresses) {\n items {\n voter\n }\n }\n}": typeof types.ProposalNonVotersDocument, "query GetProposalById($id: String!) {\n proposal(id: $id) {\n id\n daoId\n proposerAccountId\n title\n description\n startBlock\n endBlock\n endTimestamp\n timestamp\n status\n forVotes\n againstVotes\n abstainVotes\n txHash\n }\n}\n\nquery ListProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_proposals_orderDirection, $status: JSON, $fromDate: Float, $fromEndDate: Float, $includeOptimisticProposals: queryInput_proposals_includeOptimisticProposals) {\n proposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n fromEndDate: $fromEndDate\n includeOptimisticProposals: $includeOptimisticProposals\n ) {\n items {\n id\n daoId\n proposerAccountId\n title\n description\n startBlock\n endBlock\n endTimestamp\n timestamp\n status\n forVotes\n againstVotes\n abstainVotes\n txHash\n }\n totalCount\n }\n}": typeof types.GetProposalByIdDocument, "query ListVotes($voterAddressIn: JSON, $fromDate: Float, $toDate: Float, $limit: Float, $skip: NonNegativeInt, $orderBy: queryInput_votes_orderBy, $orderDirection: queryInput_votes_orderDirection, $support: Float) {\n votes(\n voterAddressIn: $voterAddressIn\n fromDate: $fromDate\n toDate: $toDate\n limit: $limit\n skip: $skip\n orderBy: $orderBy\n orderDirection: $orderDirection\n support: $support\n ) {\n items {\n transactionHash\n proposalId\n voterAddress\n support\n votingPower\n timestamp\n reason\n proposalTitle\n }\n totalCount\n }\n}": typeof types.ListVotesDocument, @@ -22,6 +23,7 @@ type Documents = { }; const documents: Documents = { "query GetDAOs {\n daos {\n items {\n id\n votingDelay\n chainId\n }\n }\n}": types.GetDaOsDocument, + "query ListOffchainProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_offchainProposals_orderDirection, $status: JSON, $fromDate: Float) {\n offchainProposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n ) {\n items {\n id\n title\n discussion\n link\n state\n created\n }\n totalCount\n }\n}": types.ListOffchainProposalsDocument, "query ProposalNonVoters($id: String!, $addresses: JSON) {\n proposalNonVoters(id: $id, addresses: $addresses) {\n items {\n voter\n }\n }\n}": types.ProposalNonVotersDocument, "query GetProposalById($id: String!) {\n proposal(id: $id) {\n id\n daoId\n proposerAccountId\n title\n description\n startBlock\n endBlock\n endTimestamp\n timestamp\n status\n forVotes\n againstVotes\n abstainVotes\n txHash\n }\n}\n\nquery ListProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_proposals_orderDirection, $status: JSON, $fromDate: Float, $fromEndDate: Float, $includeOptimisticProposals: queryInput_proposals_includeOptimisticProposals) {\n proposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n fromEndDate: $fromEndDate\n includeOptimisticProposals: $includeOptimisticProposals\n ) {\n items {\n id\n daoId\n proposerAccountId\n title\n description\n startBlock\n endBlock\n endTimestamp\n timestamp\n status\n forVotes\n againstVotes\n abstainVotes\n txHash\n }\n totalCount\n }\n}": types.GetProposalByIdDocument, "query ListVotes($voterAddressIn: JSON, $fromDate: Float, $toDate: Float, $limit: Float, $skip: NonNegativeInt, $orderBy: queryInput_votes_orderBy, $orderDirection: queryInput_votes_orderDirection, $support: Float) {\n votes(\n voterAddressIn: $voterAddressIn\n fromDate: $fromDate\n toDate: $toDate\n limit: $limit\n skip: $skip\n orderBy: $orderBy\n orderDirection: $orderDirection\n support: $support\n ) {\n items {\n transactionHash\n proposalId\n voterAddress\n support\n votingPower\n timestamp\n reason\n proposalTitle\n }\n totalCount\n }\n}": types.ListVotesDocument, @@ -46,6 +48,10 @@ export function graphql(source: string): unknown; * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "query GetDAOs {\n daos {\n items {\n id\n votingDelay\n chainId\n }\n }\n}"): (typeof documents)["query GetDAOs {\n daos {\n items {\n id\n votingDelay\n chainId\n }\n }\n}"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "query ListOffchainProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_offchainProposals_orderDirection, $status: JSON, $fromDate: Float) {\n offchainProposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n ) {\n items {\n id\n title\n discussion\n link\n state\n created\n }\n totalCount\n }\n}"): (typeof documents)["query ListOffchainProposals($skip: NonNegativeInt, $limit: PositiveInt, $orderDirection: queryInput_offchainProposals_orderDirection, $status: JSON, $fromDate: Float) {\n offchainProposals(\n skip: $skip\n limit: $limit\n orderDirection: $orderDirection\n status: $status\n fromDate: $fromDate\n ) {\n items {\n id\n title\n discussion\n link\n state\n created\n }\n totalCount\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/anticapture-client/src/gql/graphql.ts b/packages/anticapture-client/src/gql/graphql.ts index d2f56ebc..dda90737 100644 --- a/packages/anticapture-client/src/gql/graphql.ts +++ b/packages/anticapture-client/src/gql/graphql.ts @@ -117,6 +117,14 @@ export type Query = { delegationPercentageByDay?: Maybe; /** Get current delegators of an account */ delegations?: Maybe; + /** Get current delegators of an account with voting power */ + delegators?: Maybe; + /** Get feed events */ + feedEvents?: Maybe; + /** Returns label information from Arkham, ENS data, and whether the address is an EOA or contract. Arkham data is stored permanently. ENS data is cached with a configurable TTL. */ + getAddress?: Maybe; + /** Returns label information from Arkham, ENS data, and address type for multiple addresses. Maximum 100 addresses per request. Arkham data is stored permanently. ENS data is cached with a configurable TTL. */ + getAddresses?: Maybe; /** Get historical DAO Token Treasury value (governance token quantity × token price) */ getDaoTokenTreasury?: Maybe; /** Get historical Liquid Treasury (treasury without DAO tokens) from external providers (DefiLlama/Dune) */ @@ -135,6 +143,10 @@ export type Query = { historicalVotingPowerByAccountId?: Maybe; /** Get the last update time */ lastUpdate?: Maybe; + /** Returns a single offchain (Snapshot) proposal by its ID */ + offchainProposalById?: Maybe; + /** Returns a list of offchain (Snapshot) proposals */ + offchainProposals?: Maybe; /** Returns a single proposal by its ID */ proposal?: Maybe; /** Returns the active delegates that did not vote on a given proposal */ @@ -155,6 +167,10 @@ export type Query = { votes?: Maybe; /** Returns a paginated list of votes cast on a specific proposal */ votesByProposalId?: Maybe; + /** Returns a list of offchain (Snapshot) votes */ + votesOffchain?: Maybe; + /** Returns a paginated list of offchain (Snapshot) votes for a specific proposal */ + votesOffchainByProposalId?: Maybe; /** Returns voting power information for a specific address (account) */ votingPowerByAccountId?: Maybe; /** Returns a mapping of the voting power changes within a time frame for the given addresses */ @@ -293,6 +309,37 @@ export type QueryDelegationsArgs = { }; +export type QueryDelegatorsArgs = { + address: Scalars['String']['input']; + limit?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; +}; + + +export type QueryFeedEventsArgs = { + fromDate?: InputMaybe; + limit?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + relevance?: InputMaybe; + skip?: InputMaybe; + toDate?: InputMaybe; + type?: InputMaybe; +}; + + +export type QueryGetAddressArgs = { + address: Scalars['String']['input']; +}; + + +export type QueryGetAddressesArgs = { + addresses: Scalars['JSON']['input']; +}; + + export type QueryGetDaoTokenTreasuryArgs = { days?: InputMaybe; order?: InputMaybe; @@ -372,6 +419,20 @@ export type QueryLastUpdateArgs = { }; +export type QueryOffchainProposalByIdArgs = { + id: Scalars['String']['input']; +}; + + +export type QueryOffchainProposalsArgs = { + fromDate?: InputMaybe; + limit?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + status?: InputMaybe; +}; + + export type QueryProposalArgs = { id: Scalars['String']['input']; }; @@ -479,6 +540,29 @@ export type QueryVotesByProposalIdArgs = { }; +export type QueryVotesOffchainArgs = { + fromDate?: InputMaybe; + limit?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + toDate?: InputMaybe; + voterAddresses?: InputMaybe; +}; + + +export type QueryVotesOffchainByProposalIdArgs = { + fromDate?: InputMaybe; + id: Scalars['String']['input']; + limit?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + toDate?: InputMaybe; + voterAddresses?: InputMaybe; +}; + + export type QueryVotingPowerByAccountIdArgs = { accountId: Scalars['String']['input']; }; @@ -515,7 +599,9 @@ export type AccountBalanceByAccountId_200_Response = { __typename?: 'accountBalanceByAccountId_200_response'; address: Scalars['String']['output']; balance: Scalars['String']['output']; + data: Query_AccountBalanceByAccountId_Data; delegate: Scalars['String']['output']; + period: Query_AccountBalanceByAccountId_Period; tokenId: Scalars['String']['output']; }; @@ -534,6 +620,7 @@ export type AccountBalanceVariations_200_Response = { export type AccountBalances_200_Response = { __typename?: 'accountBalances_200_response'; items: Array>; + period: Query_AccountBalances_Period; totalCount: Scalars['Float']['output']; }; @@ -643,6 +730,31 @@ export type Delegations_200_Response = { totalCount: Scalars['Float']['output']; }; +export type Delegators_200_Response = { + __typename?: 'delegators_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; + +export type FeedEvents_200_Response = { + __typename?: 'feedEvents_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; + +export type GetAddress_200_Response = { + __typename?: 'getAddress_200_response'; + address: Scalars['String']['output']; + arkham?: Maybe; + ens?: Maybe; + isContract: Scalars['Boolean']['output']; +}; + +export type GetAddresses_200_Response = { + __typename?: 'getAddresses_200_response'; + results: Array>; +}; + export type GetDaoTokenTreasury_200_Response = { __typename?: 'getDaoTokenTreasury_200_response'; items: Array>; @@ -693,6 +805,30 @@ export type LastUpdate_200_Response = { lastUpdate: Scalars['String']['output']; }; +export type OffchainProposalById_200_Response = { + __typename?: 'offchainProposalById_200_response'; + author: Scalars['String']['output']; + body: Scalars['String']['output']; + created: Scalars['Float']['output']; + discussion: Scalars['String']['output']; + end: Scalars['Float']['output']; + flagged: Scalars['Boolean']['output']; + id: Scalars['String']['output']; + link: Scalars['String']['output']; + spaceId: Scalars['String']['output']; + start: Scalars['Float']['output']; + state: Scalars['String']['output']; + title: Scalars['String']['output']; + type: Scalars['String']['output']; + updated: Scalars['Float']['output']; +}; + +export type OffchainProposals_200_Response = { + __typename?: 'offchainProposals_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; + export type ProposalNonVoters_200_Response = { __typename?: 'proposalNonVoters_200_response'; items: Array>; @@ -746,6 +882,11 @@ export enum QueryInput_AccountBalanceVariations_OrderDirection { Desc = 'desc' } +export enum QueryInput_AccountBalances_OrderBy { + Balance = 'balance', + Variation = 'variation' +} + export enum QueryInput_AccountBalances_OrderDirection { Asc = 'asc', Desc = 'desc' @@ -854,6 +995,51 @@ export enum QueryInput_DelegationPercentageByDay_OrderDirection { Desc = 'desc' } +export enum QueryInput_Delegations_OrderBy { + Amount = 'amount', + Timestamp = 'timestamp' +} + +export enum QueryInput_Delegations_OrderDirection { + Asc = 'asc', + Desc = 'desc' +} + +export enum QueryInput_Delegators_OrderBy { + Amount = 'amount', + Timestamp = 'timestamp' +} + +export enum QueryInput_Delegators_OrderDirection { + Asc = 'asc', + Desc = 'desc' +} + +export enum QueryInput_FeedEvents_OrderBy { + Timestamp = 'timestamp', + Value = 'value' +} + +export enum QueryInput_FeedEvents_OrderDirection { + Asc = 'asc', + Desc = 'desc' +} + +export enum QueryInput_FeedEvents_Relevance { + High = 'HIGH', + Low = 'LOW', + Medium = 'MEDIUM' +} + +export enum QueryInput_FeedEvents_Type { + Delegation = 'DELEGATION', + DelegationVotesChanged = 'DELEGATION_VOTES_CHANGED', + Proposal = 'PROPOSAL', + ProposalExtended = 'PROPOSAL_EXTENDED', + Transfer = 'TRANSFER', + Vote = 'VOTE' +} + export enum QueryInput_GetDaoTokenTreasury_Days { '7d' = '_7d', '30d' = '_30d', @@ -934,6 +1120,11 @@ export enum QueryInput_LastUpdate_Chart { TokenDistribution = 'token_distribution' } +export enum QueryInput_OffchainProposals_OrderDirection { + Asc = 'asc', + Desc = 'desc' +} + export enum QueryInput_ProposalNonVoters_OrderDirection { Asc = 'asc', Desc = 'desc' @@ -1013,6 +1204,26 @@ export enum QueryInput_VotesByProposalId_OrderDirection { Desc = 'desc' } +export enum QueryInput_VotesOffchainByProposalId_OrderBy { + Created = 'created', + Vp = 'vp' +} + +export enum QueryInput_VotesOffchainByProposalId_OrderDirection { + Asc = 'asc', + Desc = 'desc' +} + +export enum QueryInput_VotesOffchain_OrderBy { + Created = 'created', + Vp = 'vp' +} + +export enum QueryInput_VotesOffchain_OrderDirection { + Asc = 'asc', + Desc = 'desc' +} + export enum QueryInput_Votes_OrderBy { Timestamp = 'timestamp', VotingPower = 'votingPower' @@ -1030,6 +1241,7 @@ export enum QueryInput_VotingPowerVariations_OrderDirection { export enum QueryInput_VotingPowers_OrderBy { DelegationsCount = 'delegationsCount', + Variation = 'variation', VotingPower = 'votingPower' } @@ -1038,6 +1250,28 @@ export enum QueryInput_VotingPowers_OrderDirection { Desc = 'desc' } +export type Query_AccountBalanceByAccountId_Data = { + __typename?: 'query_accountBalanceByAccountId_data'; + address: Scalars['String']['output']; + balance: Scalars['String']['output']; + delegate: Scalars['String']['output']; + tokenId: Scalars['String']['output']; + variation: Query_AccountBalanceByAccountId_Data_Variation; +}; + +export type Query_AccountBalanceByAccountId_Data_Variation = { + __typename?: 'query_accountBalanceByAccountId_data_variation'; + absoluteChange: Scalars['String']['output']; + percentageChange: Scalars['String']['output']; + previousBalance: Scalars['String']['output']; +}; + +export type Query_AccountBalanceByAccountId_Period = { + __typename?: 'query_accountBalanceByAccountId_period'; + endTimestamp: Scalars['String']['output']; + startTimestamp: Scalars['String']['output']; +}; + export type Query_AccountBalanceVariationsByAccountId_Data = { __typename?: 'query_accountBalanceVariationsByAccountId_data'; absoluteChange: Scalars['String']['output']; @@ -1074,6 +1308,20 @@ export type Query_AccountBalances_Items_Items = { balance: Scalars['String']['output']; delegate: Scalars['String']['output']; tokenId: Scalars['String']['output']; + variation: Query_AccountBalances_Items_Items_Variation; +}; + +export type Query_AccountBalances_Items_Items_Variation = { + __typename?: 'query_accountBalances_items_items_variation'; + absoluteChange: Scalars['String']['output']; + percentageChange: Scalars['String']['output']; + previousBalance: Scalars['String']['output']; +}; + +export type Query_AccountBalances_Period = { + __typename?: 'query_accountBalances_period'; + endTimestamp: Scalars['String']['output']; + startTimestamp: Scalars['String']['output']; }; export type Query_AccountInteractions_Items_Items = { @@ -1112,6 +1360,77 @@ export type Query_Delegations_Items_Items = { transactionHash: Scalars['String']['output']; }; +export type Query_Delegators_Items_Items = { + __typename?: 'query_delegators_items_items'; + amount: Scalars['String']['output']; + delegatorAddress: Scalars['String']['output']; + timestamp: Scalars['String']['output']; +}; + +export type Query_FeedEvents_Items_Items = { + __typename?: 'query_feedEvents_items_items'; + logIndex: Scalars['Float']['output']; + metadata?: Maybe; + relevance: Query_FeedEvents_Items_Items_Relevance; + timestamp: Scalars['Float']['output']; + txHash: Scalars['String']['output']; + type: Query_FeedEvents_Items_Items_Type; + value?: Maybe; +}; + +export enum Query_FeedEvents_Items_Items_Relevance { + High = 'HIGH', + Low = 'LOW', + Medium = 'MEDIUM' +} + +export enum Query_FeedEvents_Items_Items_Type { + Delegation = 'DELEGATION', + DelegationVotesChanged = 'DELEGATION_VOTES_CHANGED', + Proposal = 'PROPOSAL', + ProposalExtended = 'PROPOSAL_EXTENDED', + Transfer = 'TRANSFER', + Vote = 'VOTE' +} + +export type Query_GetAddress_Arkham = { + __typename?: 'query_getAddress_arkham'; + entity?: Maybe; + entityType?: Maybe; + label?: Maybe; + twitter?: Maybe; +}; + +export type Query_GetAddress_Ens = { + __typename?: 'query_getAddress_ens'; + avatar?: Maybe; + banner?: Maybe; + name?: Maybe; +}; + +export type Query_GetAddresses_Results_Items = { + __typename?: 'query_getAddresses_results_items'; + address: Scalars['String']['output']; + arkham?: Maybe; + ens?: Maybe; + isContract: Scalars['Boolean']['output']; +}; + +export type Query_GetAddresses_Results_Items_Arkham = { + __typename?: 'query_getAddresses_results_items_arkham'; + entity?: Maybe; + entityType?: Maybe; + label?: Maybe; + twitter?: Maybe; +}; + +export type Query_GetAddresses_Results_Items_Ens = { + __typename?: 'query_getAddresses_results_items_ens'; + avatar?: Maybe; + banner?: Maybe; + name?: Maybe; +}; + export type Query_GetDaoTokenTreasury_Items_Items = { __typename?: 'query_getDaoTokenTreasury_items_items'; /** Unix timestamp in milliseconds */ @@ -1226,6 +1545,24 @@ export type Query_HistoricalVotingPower_Items_Items_Transfer = { value: Scalars['String']['output']; }; +export type Query_OffchainProposals_Items_Items = { + __typename?: 'query_offchainProposals_items_items'; + author: Scalars['String']['output']; + body: Scalars['String']['output']; + created: Scalars['Float']['output']; + discussion: Scalars['String']['output']; + end: Scalars['Float']['output']; + flagged: Scalars['Boolean']['output']; + id: Scalars['String']['output']; + link: Scalars['String']['output']; + spaceId: Scalars['String']['output']; + start: Scalars['Float']['output']; + state: Scalars['String']['output']; + title: Scalars['String']['output']; + type: Scalars['String']['output']; + updated: Scalars['Float']['output']; +}; + export type Query_ProposalNonVoters_Items_Items = { __typename?: 'query_proposalNonVoters_items_items'; lastVoteTimestamp: Scalars['Float']['output']; @@ -1378,6 +1715,28 @@ export type Query_VotesByProposalId_Items_Items = { votingPower: Scalars['String']['output']; }; +export type Query_VotesOffchainByProposalId_Items_Items = { + __typename?: 'query_votesOffchainByProposalId_items_items'; + choice?: Maybe; + created: Scalars['Float']['output']; + proposalId: Scalars['String']['output']; + proposalTitle: Scalars['String']['output']; + reason: Scalars['String']['output']; + voter: Scalars['String']['output']; + vp: Scalars['Float']['output']; +}; + +export type Query_VotesOffchain_Items_Items = { + __typename?: 'query_votesOffchain_items_items'; + choice?: Maybe; + created: Scalars['Float']['output']; + proposalId: Scalars['String']['output']; + proposalTitle: Scalars['String']['output']; + reason: Scalars['String']['output']; + voter: Scalars['String']['output']; + vp: Scalars['Float']['output']; +}; + export type Query_Votes_Items_Items = { __typename?: 'query_votes_items_items'; proposalId: Scalars['String']['output']; @@ -1390,6 +1749,12 @@ export type Query_Votes_Items_Items = { votingPower: Scalars['String']['output']; }; +export type Query_VotingPowerByAccountId_Variation = { + __typename?: 'query_votingPowerByAccountId_variation'; + absoluteChange: Scalars['String']['output']; + percentageChange: Scalars['Float']['output']; +}; + export type Query_VotingPowerVariationsByAccountId_Data = { __typename?: 'query_votingPowerVariationsByAccountId_data'; absoluteChange: Scalars['String']['output']; @@ -1425,10 +1790,17 @@ export type Query_VotingPowers_Items_Items = { accountId: Scalars['String']['output']; delegationsCount: Scalars['Float']['output']; proposalsCount: Scalars['Float']['output']; + variation: Query_VotingPowers_Items_Items_Variation; votesCount: Scalars['Float']['output']; votingPower: Scalars['String']['output']; }; +export type Query_VotingPowers_Items_Items_Variation = { + __typename?: 'query_votingPowers_items_items_variation'; + absoluteChange: Scalars['String']['output']; + percentageChange: Scalars['Float']['output']; +}; + export enum Timestamp_Const { Timestamp = 'timestamp' } @@ -1472,6 +1844,18 @@ export type VotesByProposalId_200_Response = { totalCount: Scalars['Float']['output']; }; +export type VotesOffchainByProposalId_200_Response = { + __typename?: 'votesOffchainByProposalId_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; + +export type VotesOffchain_200_Response = { + __typename?: 'votesOffchain_200_response'; + items: Array>; + totalCount: Scalars['Float']['output']; +}; + export type Votes_200_Response = { __typename?: 'votes_200_response'; items: Array>; @@ -1483,6 +1867,7 @@ export type VotingPowerByAccountId_200_Response = { accountId: Scalars['String']['output']; delegationsCount: Scalars['Float']['output']; proposalsCount: Scalars['Float']['output']; + variation: Query_VotingPowerByAccountId_Variation; votesCount: Scalars['Float']['output']; votingPower: Scalars['String']['output']; }; @@ -1510,6 +1895,17 @@ export type GetDaOsQueryVariables = Exact<{ [key: string]: never; }>; export type GetDaOsQuery = { __typename?: 'Query', daos: { __typename?: 'DAOList', items: Array<{ __typename?: 'dao_200_response', id: string, votingDelay: string, chainId: number }> } }; +export type ListOffchainProposalsQueryVariables = Exact<{ + skip?: InputMaybe; + limit?: InputMaybe; + orderDirection?: InputMaybe; + status?: InputMaybe; + fromDate?: InputMaybe; +}>; + + +export type ListOffchainProposalsQuery = { __typename?: 'Query', offchainProposals?: { __typename?: 'offchainProposals_200_response', totalCount: number, items: Array<{ __typename?: 'query_offchainProposals_items_items', id: string, title: string, discussion: string, link: string, state: string, created: number } | null> } | null }; + export type ProposalNonVotersQueryVariables = Exact<{ id: Scalars['String']['input']; addresses?: InputMaybe; @@ -1566,6 +1962,7 @@ export type ListHistoricalVotingPowerQuery = { __typename?: 'Query', historicalV export const GetDaOsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetDAOs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"daos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"votingDelay"}},{"kind":"Field","name":{"kind":"Name","value":"chainId"}}]}}]}}]}}]} as unknown as DocumentNode; +export const ListOffchainProposalsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ListOffchainProposals"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"NonNegativeInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PositiveInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderDirection"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"queryInput_offchainProposals_orderDirection"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"status"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromDate"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"offchainProposals"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"status"}}},{"kind":"Argument","name":{"kind":"Name","value":"fromDate"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromDate"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"discussion"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"created"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; export const ProposalNonVotersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProposalNonVoters"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"addresses"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"proposalNonVoters"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"addresses"},"value":{"kind":"Variable","name":{"kind":"Name","value":"addresses"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"voter"}}]}}]}}]}}]} as unknown as DocumentNode; export const GetProposalByIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProposalById"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"proposal"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"daoId"}},{"kind":"Field","name":{"kind":"Name","value":"proposerAccountId"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"startBlock"}},{"kind":"Field","name":{"kind":"Name","value":"endBlock"}},{"kind":"Field","name":{"kind":"Name","value":"endTimestamp"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"forVotes"}},{"kind":"Field","name":{"kind":"Name","value":"againstVotes"}},{"kind":"Field","name":{"kind":"Name","value":"abstainVotes"}},{"kind":"Field","name":{"kind":"Name","value":"txHash"}}]}}]}}]} as unknown as DocumentNode; export const ListProposalsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ListProposals"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"NonNegativeInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PositiveInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderDirection"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"queryInput_proposals_orderDirection"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"status"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromDate"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromEndDate"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"includeOptimisticProposals"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"queryInput_proposals_includeOptimisticProposals"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"proposals"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderDirection"}}},{"kind":"Argument","name":{"kind":"Name","value":"status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"status"}}},{"kind":"Argument","name":{"kind":"Name","value":"fromDate"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromDate"}}},{"kind":"Argument","name":{"kind":"Name","value":"fromEndDate"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromEndDate"}}},{"kind":"Argument","name":{"kind":"Name","value":"includeOptimisticProposals"},"value":{"kind":"Variable","name":{"kind":"Name","value":"includeOptimisticProposals"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"daoId"}},{"kind":"Field","name":{"kind":"Name","value":"proposerAccountId"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"startBlock"}},{"kind":"Field","name":{"kind":"Name","value":"endBlock"}},{"kind":"Field","name":{"kind":"Name","value":"endTimestamp"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"forVotes"}},{"kind":"Field","name":{"kind":"Name","value":"againstVotes"}},{"kind":"Field","name":{"kind":"Name","value":"abstainVotes"}},{"kind":"Field","name":{"kind":"Name","value":"txHash"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/anticapture-client/src/index.ts b/packages/anticapture-client/src/index.ts index ea29719c..dcde5707 100644 --- a/packages/anticapture-client/src/index.ts +++ b/packages/anticapture-client/src/index.ts @@ -25,4 +25,4 @@ export { QueryInput_Votes_OrderDirection } from './gql/graphql'; -export type { ProcessedVotingPowerHistory } from './schemas'; \ No newline at end of file +export type { ProcessedVotingPowerHistory, OffchainProposalItem } from './schemas'; \ No newline at end of file diff --git a/packages/anticapture-client/src/schemas.ts b/packages/anticapture-client/src/schemas.ts index 3a09c7f3..4fd23d6f 100644 --- a/packages/anticapture-client/src/schemas.ts +++ b/packages/anticapture-client/src/schemas.ts @@ -121,6 +121,37 @@ export const SafeProposalNonVotersResponseSchema = z.object({ }); +export const OffchainProposalItemSchema = z.object({ + id: z.string(), + title: z.string(), + discussion: z.string(), + link: z.string(), + state: z.string(), + created: z.number(), +}); + +export type OffchainProposalItem = z.infer; + +export const SafeOffchainProposalsResponseSchema = z.object({ + offchainProposals: z.object({ + items: z.array(OffchainProposalItemSchema.nullable()), + totalCount: z.number(), + }).nullable(), +}).transform((data) => { + if (!data.offchainProposals) { + console.warn('OffchainProposalsResponse has null offchainProposals:', data); + return { offchainProposals: { items: [], totalCount: 0 } }; + } + return { + offchainProposals: { + ...data.offchainProposals, + items: data.offchainProposals.items.filter( + (item): item is OffchainProposalItem => item !== null + ), + }, + }; +}); + // Internal types for schema validation type SafeProposalsResponse = z.infer; type SafeHistoricalVotingPowerResponse = z.infer; diff --git a/packages/anticapture-client/tests/offchain-proposal.test.ts b/packages/anticapture-client/tests/offchain-proposal.test.ts new file mode 100644 index 00000000..b4be4489 --- /dev/null +++ b/packages/anticapture-client/tests/offchain-proposal.test.ts @@ -0,0 +1,128 @@ +import { describe, it, expect, afterEach, beforeAll, afterAll } from '@jest/globals'; +import { AnticaptureClient } from '../src/anticapture-client'; +import { setupServer } from 'msw/node'; +import { http, HttpResponse } from 'msw'; +import axios from 'axios'; + +const TEST_API_URL = 'http://test-api/graphql'; + +interface OffchainProposalStub { + id: string; + title: string; + discussion: string; + state: string; + created: number; +} + +interface GraphQLScenario { + daos: Array<{ id: string; votingDelay?: string; chainId?: number }>; + proposals?: Record; + errors?: Record; +} + +function handleGraphQL(scenario: GraphQLScenario) { + return http.post(TEST_API_URL, async ({ request }) => { + const body = await request.json() as { query: string }; + + if (body.query.includes('daos')) { + return HttpResponse.json({ + data: { + daos: { + items: scenario.daos.map(d => ({ + id: d.id, + votingDelay: d.votingDelay ?? '0', + chainId: d.chainId ?? 1, + })), + }, + }, + }); + } + + const daoId = request.headers.get('anticapture-dao-id'); + + if (daoId && scenario.errors?.[daoId]) { + return HttpResponse.json({ + data: null, + errors: [{ message: scenario.errors[daoId] }], + }); + } + + const items = (daoId && scenario.proposals?.[daoId]) || []; + return HttpResponse.json({ + data: { + offchainProposals: { items, totalCount: items.length }, + }, + }); + }); +} + +describe('listOffchainProposals', () => { + const server = setupServer(); + + beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + + function createRealClient() { + const httpClient = axios.create({ baseURL: TEST_API_URL }); + return new AnticaptureClient(httpClient, 0, 5000); + } + + it('returns empty array when no DAOs exist', async () => { + server.use(handleGraphQL({ daos: [] })); + + const client = createRealClient(); + const result = await client.listOffchainProposals(); + + expect(result).toEqual([]); + }); + + it('returns proposals with daoId attached', async () => { + server.use(handleGraphQL({ + daos: [{ id: 'ENS' }], + proposals: { + ENS: [{ id: 'snap-1', title: 'Test Proposal', discussion: 'https://forum.example.com', state: 'active', created: 1700000000 }], + }, + })); + + const client = createRealClient(); + const result = await client.listOffchainProposals(); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ id: 'snap-1', daoId: 'ENS' }); + }); + + it('aggregates proposals from multiple DAOs', async () => { + server.use(handleGraphQL({ + daos: [{ id: 'DAO_A' }, { id: 'DAO_B' }], + proposals: { + DAO_A: [{ id: 'snap-a', title: 'From A', discussion: '', state: 'active', created: 1700000100 }], + DAO_B: [{ id: 'snap-b', title: 'From B', discussion: '', state: 'pending', created: 1700000200 }], + }, + })); + + const client = createRealClient(); + const result = await client.listOffchainProposals(); + + expect(result).toHaveLength(2); + expect(result.map(p => p.id)).toEqual(['snap-b', 'snap-a']); + }); + + it('skips DAO on API error and continues with others', async () => { + server.use(handleGraphQL({ + daos: [{ id: 'OK_DAO' }, { id: 'BAD_DAO' }], + proposals: { + OK_DAO: [{ id: 'snap-ok', title: 'OK', discussion: '', state: 'active', created: 1700000000 }], + }, + errors: { + BAD_DAO: 'API exploded', + }, + })); + + const client = createRealClient(); + const result = await client.listOffchainProposals(); + + expect(result).toHaveLength(1); + expect(result[0].id).toBe('snap-ok'); + }); +}); \ No newline at end of file diff --git a/packages/messages/src/index.ts b/packages/messages/src/index.ts index 29bb2e27..3c05ac69 100644 --- a/packages/messages/src/index.ts +++ b/packages/messages/src/index.ts @@ -4,6 +4,7 @@ // Export trigger messages export * from './triggers/new-proposal'; +export * from './triggers/new-offchain-proposal'; export * from './triggers/vote-confirmation'; export * from './triggers/voting-reminder'; export * from './triggers/proposal-finished'; diff --git a/packages/messages/src/triggers/buttons.ts b/packages/messages/src/triggers/buttons.ts index 32cefbc1..7d7c8cd4 100644 --- a/packages/messages/src/triggers/buttons.ts +++ b/packages/messages/src/triggers/buttons.ts @@ -63,6 +63,11 @@ const ctaButtonConfigs: Record = { daoId && proposalId ? `${BASE_URL}/${daoId}/governance/proposal/${proposalId}` : BASE_URL + }, + newOffchainProposal: { + text: 'Cast your vote', + buildUrl: ({ proposalUrl }) => + proposalUrl || BASE_URL } }; @@ -71,6 +76,11 @@ const ctaButtonConfigs: Record = { */ export const scanButtonText = 'View Transaction'; +/** + * Text for forum discussion button + */ +export const discussionButtonText = 'View Discussion'; + /** * Parameters for building notification buttons */ @@ -78,9 +88,11 @@ export interface BuildButtonsParams { triggerType: keyof typeof ctaButtonConfigs; txHash?: string; chainId?: number; + discussionUrl?: string; daoId?: string; address?: string; proposalId?: string; + proposalUrl?: string; } const explorerService = new ExplorerService(); @@ -96,11 +108,17 @@ export function buildButtons(params: BuildButtonsParams): Button[] { const url = config.buildUrl({ daoId: params.daoId, address: params.address, - proposalId: params.proposalId + proposalId: params.proposalId, + proposalUrl: params.proposalUrl }); buttons.push({ text: config.text, url }); + // Add discussion button if forum URL is available + if (params.discussionUrl) { + buttons.push({ text: discussionButtonText, url: params.discussionUrl }); + } + // Add scan button if transaction info is available if (params.txHash && params.chainId) { const scanUrl = explorerService.getTransactionLink(params.chainId, params.txHash); diff --git a/packages/messages/src/triggers/new-offchain-proposal.ts b/packages/messages/src/triggers/new-offchain-proposal.ts new file mode 100644 index 00000000..f692a16f --- /dev/null +++ b/packages/messages/src/triggers/new-offchain-proposal.ts @@ -0,0 +1,3 @@ +export const newOffchainProposalMessages = { + notification: '📋 New Snapshot proposal in {{daoId}}: "{{title}}"' +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99ad685f..928e561b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,10 +160,10 @@ importers: version: 20.17.46 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + version: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)) ts-jest: specifier: ^29.3.2 - version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)))(typescript@5.8.2) + version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)))(typescript@5.8.2) tsx: specifier: ^4.19.4 version: 4.19.4 @@ -284,7 +284,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + version: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)) knex: specifier: ^3.1.0 version: 3.1.0(pg@8.16.3)(sqlite3@5.1.7) @@ -296,7 +296,7 @@ importers: version: 5.1.7 ts-jest: specifier: ^29.3.2 - version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)))(typescript@5.8.3) + version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)))(typescript@5.8.3) typescript: specifier: ^5.8.3 version: 5.8.3 @@ -430,13 +430,13 @@ importers: version: 8.15.2 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)) + version: 29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)) ts-jest: specifier: ^29.3.2 - version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)))(typescript@5.8.3) + version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)))(typescript@5.8.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3) + version: 10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3) tsx: specifier: ^4.19.4 version: 4.19.4 @@ -488,10 +488,13 @@ importers: version: 8.0.0 jest: specifier: ^29.0.0 - version: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + version: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)) + msw: + specifier: ^2.12.10 + version: 2.12.10(@types/node@20.17.46)(typescript@5.8.3) ts-jest: specifier: ^29.0.0 - version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)))(typescript@5.8.3) + version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)))(typescript@5.8.3) typescript: specifier: ^5.8.3 version: 5.8.3 @@ -510,10 +513,10 @@ importers: version: 20.17.46 jest: specifier: ^29.0.0 - version: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + version: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)) ts-jest: specifier: ^29.0.0 - version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)))(typescript@5.8.3) + version: 29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)))(typescript@5.8.3) typescript: specifier: ^5.0.0 version: 5.8.3 @@ -1222,6 +1225,41 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1332,6 +1370,10 @@ packages: resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} + '@mswjs/interceptors@0.41.3': + resolution: {integrity: sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==} + engines: {node: '>=18'} + '@next/env@14.2.35': resolution: {integrity: sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==} @@ -1421,6 +1463,15 @@ packages: engines: {node: '>=10'} deprecated: This functionality has been moved to @npmcli/fs + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1922,6 +1973,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -2465,6 +2519,10 @@ packages: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -3249,16 +3307,18 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@11.0.2: resolution: {integrity: sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==} engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -3326,6 +3386,10 @@ packages: resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + graphql@16.13.0: + resolution: {integrity: sha512-uSisMYERbaB9bkA9M4/4dnqyktaEkf1kMHNKq/7DHyxVeWqHQ2mBmVqm5u6/FVHwF3iCNalKcg82Zfl+tffWoA==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -3352,6 +3416,9 @@ packages: header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -3523,6 +3590,9 @@ packages: is-lower-case@2.0.2: resolution: {integrity: sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -4124,9 +4194,23 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.12.10: + resolution: {integrity: sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -4287,6 +4371,9 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + ox@0.8.7: resolution: {integrity: sha512-W1f0FiMf9NZqtHPEDEAEkyzZDwbIKfmH2qmQx8NNiQ/9JhxrSblmtLJsSfTtQG5YKowLOnBlLVguCyxm/7ztxw==} peerDependencies: @@ -4401,6 +4488,9 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -4835,6 +4925,9 @@ packages: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} + rettime@0.10.1: + resolution: {integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -5063,6 +5156,10 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -5070,6 +5167,9 @@ packages: streamx@2.22.1: resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-env-interpolation@1.0.1: resolution: {integrity: sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==} @@ -5160,6 +5260,10 @@ packages: resolution: {integrity: sha512-c7AfkZ9udatCuAy9RSfiGPpeOKKUAUK5e1cXadLOGUjasdxqYqAK0jTNkM/FSEyJ3a5Ra27j/tw/PS0qLmaF/A==} engines: {node: '>=18'} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + tailwind-merge@3.4.1: resolution: {integrity: sha512-2OA0rFqWOkITEAOFWSBSApYkDeH9t2B3XSJuI4YztKBzK3mX0737A2qtxDZ7xkw9Zfh0bWl+r34sF3HXV+Ig7Q==} @@ -5247,6 +5351,13 @@ packages: title-case@3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} + tldts-core@7.0.23: + resolution: {integrity: sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==} + + tldts@7.0.23: + resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -5274,6 +5385,10 @@ packages: resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -5419,6 +5534,10 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} + type-fest@5.4.4: + resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} + engines: {node: '>=20'} + type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} @@ -5476,6 +5595,9 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -5636,6 +5758,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -6545,6 +6671,34 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@inquirer/ansi@1.0.2': {} + + '@inquirer/confirm@5.1.21(@types/node@20.17.46)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@20.17.46) + '@inquirer/type': 3.0.10(@types/node@20.17.46) + optionalDependencies: + '@types/node': 20.17.46 + + '@inquirer/core@10.3.2(@types/node@20.17.46)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@20.17.46) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 20.17.46 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/type@3.0.10(@types/node@20.17.46)': + optionalDependencies: + '@types/node': 20.17.46 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -6608,6 +6762,41 @@ snapshots: - supports-color - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.46 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3))': dependencies: '@jest/console': 29.7.0 @@ -6643,7 +6832,7 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2))': + '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -6657,7 +6846,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + jest-config: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -6826,6 +7015,15 @@ snapshots: '@lukeed/ms@2.0.2': {} + '@mswjs/interceptors@0.41.3': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@next/env@14.2.35': {} '@next/swc-darwin-arm64@14.2.33': @@ -6887,6 +7085,15 @@ snapshots: rimraf: 3.0.2 optional: true + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -7435,6 +7642,8 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/statuses@2.0.6': {} + '@types/trusted-types@2.0.7': optional: true @@ -8107,6 +8316,8 @@ snapshots: cli-width@3.0.0: {} + cli-width@4.1.0: {} + client-only@0.0.1: {} cliui@8.0.1: @@ -8223,6 +8434,21 @@ snapshots: - supports-color - ts-node + create-jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)): dependencies: '@jest/types': 29.6.3 @@ -8238,13 +8464,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)): + create-jest@29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + jest-config: 29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -9076,6 +9302,8 @@ snapshots: graphql@16.11.0: {} + graphql@16.13.0: {} + has-flag@3.0.0: {} has-flag@4.0.0: {} @@ -9098,6 +9326,8 @@ snapshots: capital-case: 1.0.4 tslib: 2.8.1 + headers-polyfill@4.0.3: {} + html-escaper@2.0.2: {} html2canvas@1.4.1: @@ -9276,6 +9506,8 @@ snapshots: dependencies: tslib: 2.8.1 + is-node-process@1.2.0: {} + is-number@7.0.0: {} is-path-inside@3.0.3: {} @@ -9425,6 +9657,25 @@ snapshots: - supports-color - ts-node + jest-cli@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-cli@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)) @@ -9444,16 +9695,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)): + jest-cli@29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + create-jest: 29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + jest-config: 29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -9525,6 +9776,37 @@ snapshots: - babel-plugin-macros - supports-color + jest-config@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)): + dependencies: + '@babel/core': 7.27.1 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.1) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.17.46 + ts-node: 10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-config@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)): dependencies: '@babel/core': 7.27.1 @@ -9556,7 +9838,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)): + jest-config@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)): dependencies: '@babel/core': 7.27.1 '@jest/test-sequencer': 29.7.0 @@ -9582,7 +9864,38 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.17.46 - ts-node: 10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2) + ts-node: 10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)): + dependencies: + '@babel/core': 7.27.1 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.1) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 24.3.1 + ts-node: 10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -9814,6 +10127,18 @@ snapshots: - supports-color - ts-node + jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)) @@ -9826,12 +10151,12 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)): + jest@29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + jest-cli: 29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -10224,8 +10549,35 @@ snapshots: ms@2.1.3: {} + msw@2.12.10(@types/node@20.17.46)(typescript@5.8.3): + dependencies: + '@inquirer/confirm': 5.1.21(@types/node@20.17.46) + '@mswjs/interceptors': 0.41.3 + '@open-draft/deferred-promise': 2.2.0 + '@types/statuses': 2.0.6 + cookie: 1.0.2 + graphql: 16.13.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.10.1 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.0 + type-fest: 5.4.4 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@types/node' + mute-stream@0.0.8: {} + mute-stream@2.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -10404,6 +10756,8 @@ snapshots: os-tmpdir@1.0.2: {} + outvariant@1.4.3: {} + ox@0.8.7(typescript@4.9.5)(zod@3.24.3): dependencies: '@adraffy/ens-normalize': 1.11.0 @@ -10551,6 +10905,8 @@ snapshots: lru-cache: 11.1.0 minipass: 7.1.2 + path-to-regexp@6.3.0: {} + path-to-regexp@8.3.0: {} path-type@4.0.0: {} @@ -10998,6 +11354,8 @@ snapshots: retry@0.13.1: {} + rettime@0.10.1: {} + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -11257,6 +11615,8 @@ snapshots: statuses@2.0.1: {} + statuses@2.0.2: {} + streamsearch@1.1.0: {} streamx@2.22.1: @@ -11266,6 +11626,8 @@ snapshots: optionalDependencies: bare-events: 2.5.4 + strict-event-emitter@0.5.1: {} + string-env-interpolation@1.0.1: {} string-length@4.0.2: @@ -11351,6 +11713,8 @@ snapshots: timeout-signal: 2.0.0 whatwg-mimetype: 4.0.0 + tagged-tag@1.0.0: {} + tailwind-merge@3.4.1: {} tailwindcss-animate@1.0.7(tailwindcss@3.4.19(tsx@4.19.4)(yaml@2.7.1)): @@ -11507,6 +11871,12 @@ snapshots: dependencies: tslib: 2.8.1 + tldts-core@7.0.23: {} + + tldts@7.0.23: + dependencies: + tldts-core: 7.0.23 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -11525,6 +11895,10 @@ snapshots: touch@3.1.1: {} + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.23 + tr46@0.0.3: {} ts-interface-checker@0.1.13: {} @@ -11549,19 +11923,19 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.27.1) - ts-jest@29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)))(typescript@5.8.3): + ts-jest@29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)))(typescript@5.8.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)) + jest: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.7.1 type-fest: 4.41.0 - typescript: 5.8.3 + typescript: 5.8.2 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.27.1 @@ -11569,19 +11943,19 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.27.1) - ts-jest@29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)))(typescript@5.8.2): + ts-jest@29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)))(typescript@5.8.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + jest: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.7.1 type-fest: 4.41.0 - typescript: 5.8.2 + typescript: 5.8.3 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.27.1 @@ -11589,12 +11963,12 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.27.1) - ts-jest@29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)))(typescript@5.8.3): + ts-jest@29.3.2(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(jest@29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)))(typescript@5.8.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.17.46)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.2)) + jest: 29.7.0(@types/node@24.3.1)(ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -11631,6 +12005,27 @@ snapshots: optionalDependencies: '@swc/core': 1.11.24 + ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.17.46 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.8.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.11.24 + optional: true + ts-node@10.9.2(@swc/core@1.11.24)(@types/node@20.17.46)(typescript@5.8.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -11671,6 +12066,26 @@ snapshots: optionalDependencies: '@swc/core': 1.11.24 + ts-node@10.9.2(@swc/core@1.11.24)(@types/node@24.3.1)(typescript@5.8.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 24.3.1 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.8.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.11.24 + tslib@1.14.1: {} tslib@2.6.3: {} @@ -11736,6 +12151,10 @@ snapshots: type-fest@4.41.0: {} + type-fest@5.4.4: + dependencies: + tagged-tag: 1.0.0 + type-is@2.0.1: dependencies: content-type: 1.0.5 @@ -11780,6 +12199,8 @@ snapshots: unpipe@1.0.0: {} + until-async@3.0.2: {} + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 @@ -11977,6 +12398,8 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.3: {} + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2