From c5f75128c5c133388727c0469c4e1b42db7de026 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 23 Feb 2023 14:25:49 -0800 Subject: [PATCH 01/28] Add getEventsQuery definition --- src/lib/fetch.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 6c707cbd75..0c2c498258 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -609,6 +609,30 @@ function sendZkappQuery(json: string) { `; } +const getEventsQuery = (publicKey: string, tokenId: string) => `{ + events(input: { address: "${publicKey}", tokenId: "${tokenId}" }) { + blockInfo { + stateHash + timestamp + ledgerHash + height + parentHash + chainStatus + distanceFromMaxBlockHeight + } + transactionInfo { + status + hash + memo + } + eventData { + index + data + } + } +} +`; + // removes the quotes on JSON keys function removeJsonQuotes(json: string) { let cleaned = JSON.stringify(JSON.parse(json), null, 2); From c22a9856ef685a00141b14d1c7f513699ce918db Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 23 Feb 2023 16:13:19 -0800 Subject: [PATCH 02/28] feat: Naive implementation for fetch events --- src/index.ts | 1 + src/lib/fetch.ts | 37 +++++++++++++++++++++++++++++++++++++ src/lib/mina.ts | 12 ++++++++---- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2db0ba04c4..a83e821ecc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,6 +45,7 @@ export { fetchAccount, fetchLastBlock, fetchTransactionStatus, + fetchEvents, TransactionStatus, addCachedAccount, setGraphqlEndpoint, diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 0c2c498258..f0f58ad97b 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -24,6 +24,7 @@ export { sendZkappQuery, sendZkapp, removeJsonQuotes, + fetchEvents, }; export { Account }; @@ -619,6 +620,7 @@ const getEventsQuery = (publicKey: string, tokenId: string) => `{ parentHash chainStatus distanceFromMaxBlockHeight + globalSlotSinceGenesis } transactionInfo { status @@ -633,6 +635,41 @@ const getEventsQuery = (publicKey: string, tokenId: string) => `{ } `; +/** + * Fetches the events for an account. + */ +async function fetchEvents( + accountInfo: { publicKey: string; tokenId?: string }, + graphqlEndpoint = defaultGraphqlEndpoint +): Promise { + const { publicKey, tokenId } = accountInfo; + let [response, error] = await makeGraphqlRequest( + getEventsQuery(publicKey, tokenId ?? TokenId.toBase58(TokenId.default)), + graphqlEndpoint + ); + if (error) throw Error(error.statusText); + let fetchedEvents = response?.data.events; + if (fetchedEvents === undefined) { + throw Error( + `Failed to fetch events data. Account: ${publicKey} Token: ${tokenId}` + ); + } + let events = []; + for (let i = 0; i < fetchedEvents.length; i++) { + let event = fetchedEvents[i]; + let parsedEvents: any = []; + + event.eventData.forEach((event: { index: string; data: string[] }) => { + parsedEvents.push([event.index].concat(event.data)); + }); + events.push({ + events: parsedEvents, + slot: event.blockInfo.globalSlotSinceGenesis, + }); + } + return events; +} + // removes the quotes on JSON keys function removeJsonQuotes(json: string) { let cleaned = JSON.stringify(JSON.parse(json), null, 2); diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 943e6da36e..70a66493f9 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -767,9 +767,13 @@ function Network(graphqlEndpoint: string): Mina { isFinalRunOutsideCircuit: !hasProofs, }); }, - async fetchEvents() { - throw Error( - 'fetchEvents() is not implemented yet for remote blockchains.' + async fetchEvents(publicKey: PublicKey, tokenId: Field = TokenId.default) { + let pubKey = publicKey.toBase58(); + let token = TokenId.toBase58(tokenId); + + return await Fetch.fetchEvents( + { publicKey: pubKey, tokenId: token }, + graphqlEndpoint ); }, getActions() { @@ -845,7 +849,7 @@ let activeInstance: Mina = { async transaction(sender: DeprecatedFeePayerSpec, f: () => void) { return createTransaction(sender, f, 0); }, - fetchEvents() { + fetchEvents(publicKey: PublicKey, tokenId: Field = TokenId.default) { throw Error('must call Mina.setActiveInstance first'); }, getActions() { From 898bc8ecab131201f91e406722101d51dd0680ec Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 27 Feb 2023 14:49:32 -0800 Subject: [PATCH 03/28] Add functionality to remove best tip blocks --- src/lib/fetch.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index dbb242c3aa..1f718023af 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -497,6 +497,25 @@ async function fetchEvents( `Failed to fetch events data. Account: ${publicKey} Token: ${tokenId}` ); } + + // TODO: Is this what we want to do? Should we just return the events? + // If we have multiple blocks returned at the best tip (e.g. distanceFromMaxBlockHeight === 0), + // then filter out the blocks at the best tip. This is because we cannot guarantee that every block + // at the best tip will have the correct event data or guarantee that the specific block data will not + // fork in anyway. If this happens, we delay fetching event data until another block has been added to the network. + let numberOfBestTipBlocks = 0; + for (let i = 0; i < fetchedEvents.length; i++) { + if (fetchedEvents[i].blockInfo.distanceFromMaxBlockHeight === 0) { + numberOfBestTipBlocks++; + } + if (numberOfBestTipBlocks > 1) { + fetchedEvents = fetchedEvents.filter((event: any) => { + return event.blockInfo.distanceFromMaxBlockHeight !== 0; + }); + break; + } + } + let events = []; for (let i = 0; i < fetchedEvents.length; i++) { let event = fetchedEvents[i]; From 2758ae646fd495ec1a25a97d48930ed8a4abaa77 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Mon, 27 Feb 2023 15:33:13 -0800 Subject: [PATCH 04/28] Remove unused fields on fetchEvents query --- src/lib/fetch.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 1f718023af..421df01dfd 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -456,20 +456,9 @@ function sendZkappQuery(json: string) { const getEventsQuery = (publicKey: string, tokenId: string) => `{ events(input: { address: "${publicKey}", tokenId: "${tokenId}" }) { blockInfo { - stateHash - timestamp - ledgerHash - height - parentHash - chainStatus distanceFromMaxBlockHeight globalSlotSinceGenesis } - transactionInfo { - status - hash - memo - } eventData { index data From 94b3fece2fbc53f90bdf4afc342f3a03fe6ba51c Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 1 Mar 2023 11:38:45 -0800 Subject: [PATCH 05/28] Add archiveGraphql Endpoint to Mina Network --- src/lib/fetch.ts | 9 ++++++++- src/lib/mina.ts | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 421df01dfd..56f7f4277f 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -28,7 +28,9 @@ export { getCachedNetwork, addCachedAccount, defaultGraphqlEndpoint, + archiveGraphqlEndpoint, setGraphqlEndpoint, + setArchiveGraphqlEndpoint, sendZkappQuery, sendZkapp, removeJsonQuotes, @@ -36,6 +38,7 @@ export { }; let defaultGraphqlEndpoint = 'none'; +let archiveGraphqlEndpoint = 'none'; /** * Specifies the default GraphQL endpoint. */ @@ -43,6 +46,10 @@ function setGraphqlEndpoint(graphqlEndpoint: string) { defaultGraphqlEndpoint = graphqlEndpoint; } +function setArchiveGraphqlEndpoint(graphqlEndpoint: string) { + archiveGraphqlEndpoint = graphqlEndpoint; +} + /** * Gets account information on the specified publicKey by performing a GraphQL query * to the specified endpoint. This will call the 'GetAccountInfo' query which fetches @@ -472,7 +479,7 @@ const getEventsQuery = (publicKey: string, tokenId: string) => `{ */ async function fetchEvents( accountInfo: { publicKey: string; tokenId?: string }, - graphqlEndpoint = defaultGraphqlEndpoint + graphqlEndpoint = archiveGraphqlEndpoint ): Promise { const { publicKey, tokenId } = accountInfo; let [response, error] = await makeGraphqlRequest( diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 5b76c5ef3d..a03139b183 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -568,9 +568,11 @@ function LocalBlockchain({ /** * Represents the Mina blockchain running on a real network */ -function Network(graphqlEndpoint: string): Mina { +// TODO: Should this be an object instead? +function Network(graphqlEndpoint: string, archiveEndpoint?: string): Mina { let accountCreationFee = UInt64.from(defaultAccountCreationFee); Fetch.setGraphqlEndpoint(graphqlEndpoint); + if (archiveEndpoint) Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); return { accountCreationFee: () => accountCreationFee, currentSlot() { @@ -726,7 +728,7 @@ function Network(graphqlEndpoint: string): Mina { return await Fetch.fetchEvents( { publicKey: pubKey, tokenId: token }, - graphqlEndpoint + archiveEndpoint ); }, getActions() { From f839961e8d924856c743bffdece71184928a5f0e Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 1 Mar 2023 12:18:33 -0800 Subject: [PATCH 06/28] Add filter options to FetchEvents --- src/lib/fetch.ts | 36 ++++++++++++++++++++++++++++++------ src/lib/mina.ts | 26 +++++++++++++++++++------- src/lib/zkapp.ts | 15 ++++++++++----- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 56f7f4277f..1f7c680666 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -24,6 +24,7 @@ export { fetchMissingData, fetchTransactionStatus, TransactionStatus, + EventActionFilterOptions, getCachedAccount, getCachedNetwork, addCachedAccount, @@ -460,11 +461,24 @@ function sendZkappQuery(json: string) { `; } -const getEventsQuery = (publicKey: string, tokenId: string) => `{ - events(input: { address: "${publicKey}", tokenId: "${tokenId}" }) { +const getEventsQuery = ( + publicKey: string, + tokenId: string, + filterOptions?: EventActionFilterOptions +) => { + const { to, from } = filterOptions ?? {}; + let input = `address: "${publicKey}", tokenId: "${tokenId}"`; + if (to !== undefined) { + input += `, to: ${to}`; + } + if (from !== undefined) { + input += `, from: ${from}`; + } + return `{ + events(input: { ${input} }) { blockInfo { distanceFromMaxBlockHeight - globalSlotSinceGenesis + height } eventData { index @@ -473,17 +487,27 @@ const getEventsQuery = (publicKey: string, tokenId: string) => `{ } } `; +}; +type EventActionFilterOptions = { + to?: UInt32; + from?: UInt32; +}; /** * Fetches the events for an account. */ async function fetchEvents( accountInfo: { publicKey: string; tokenId?: string }, - graphqlEndpoint = archiveGraphqlEndpoint + graphqlEndpoint = archiveGraphqlEndpoint, + filterOptions: EventActionFilterOptions = {} ): Promise { const { publicKey, tokenId } = accountInfo; let [response, error] = await makeGraphqlRequest( - getEventsQuery(publicKey, tokenId ?? TokenId.toBase58(TokenId.default)), + getEventsQuery( + publicKey, + tokenId ?? TokenId.toBase58(TokenId.default), + filterOptions + ), graphqlEndpoint ); if (error) throw Error(error.statusText); @@ -522,7 +546,7 @@ async function fetchEvents( }); events.push({ events: parsedEvents, - slot: event.blockInfo.globalSlotSinceGenesis, + height: event.blockInfo.height, }); } return events; diff --git a/src/lib/mina.ts b/src/lib/mina.ts index a03139b183..a75d60b5a5 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -311,7 +311,11 @@ interface Mina { getNetworkState(): NetworkValue; accountCreationFee(): UInt64; sendTransaction(transaction: Transaction): Promise; - fetchEvents: (publicKey: PublicKey, tokenId?: Field) => any; + fetchEvents: ( + publicKey: PublicKey, + tokenId?: Field, + filterOptions?: Fetch.EventActionFilterOptions + ) => any; getActions: ( publicKey: PublicKey, tokenId?: Field @@ -443,7 +447,7 @@ function LocalBlockchain({ } events[addr][tokenId].push({ events: p.body.events, - slot: networkState.globalSlotSinceGenesis.toString(), + height: networkState.blockchainLength.toString(), }); } @@ -568,7 +572,6 @@ function LocalBlockchain({ /** * Represents the Mina blockchain running on a real network */ -// TODO: Should this be an object instead? function Network(graphqlEndpoint: string, archiveEndpoint?: string): Mina { let accountCreationFee = UInt64.from(defaultAccountCreationFee); Fetch.setGraphqlEndpoint(graphqlEndpoint); @@ -722,13 +725,18 @@ function Network(graphqlEndpoint: string, archiveEndpoint?: string): Mina { isFinalRunOutsideCircuit: !hasProofs, }); }, - async fetchEvents(publicKey: PublicKey, tokenId: Field = TokenId.default) { + async fetchEvents( + publicKey: PublicKey, + tokenId: Field = TokenId.default, + filterOptions: Fetch.EventActionFilterOptions = {} + ) { let pubKey = publicKey.toBase58(); let token = TokenId.toBase58(tokenId); return await Fetch.fetchEvents( { publicKey: pubKey, tokenId: token }, - archiveEndpoint + archiveEndpoint, + filterOptions ); }, getActions() { @@ -943,8 +951,12 @@ async function sendTransaction(txn: Transaction) { /** * @return A list of emitted events associated to the given public key. */ -async function fetchEvents(publicKey: PublicKey, tokenId: Field) { - return await activeInstance.fetchEvents(publicKey, tokenId); +async function fetchEvents( + publicKey: PublicKey, + tokenId: Field, + filterOptions: Fetch.EventActionFilterOptions = {} +) { + return await activeInstance.fetchEvents(publicKey, tokenId, filterOptions); } /** diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index f59f7d3226..1bc40c460d 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1042,13 +1042,18 @@ super.init(); ): Promise<{ type: string; event: ProvablePure }[]> { // filters all elements so that they are within the given range // only returns { type: "", event: [] } in a flat format - let events = (await Mina.fetchEvents(this.address, this.self.body.tokenId)) + let events = ( + await Mina.fetchEvents(this.address, this.self.body.tokenId, { + from: start, + to: end, + }) + ) .filter((el: any) => { - let slot = UInt32.from(el.slot); + let height = UInt32.from(el.height); return end === undefined - ? start.lessThanOrEqual(slot).toBoolean() - : start.lessThanOrEqual(slot).toBoolean() && - slot.lessThanOrEqual(end).toBoolean(); + ? start.lessThanOrEqual(height).toBoolean() + : start.lessThanOrEqual(height).toBoolean() && + height.lessThanOrEqual(end).toBoolean(); }) .map((el: any) => el.events) .flat(); From 4279ba843d0456642f35e1ba36fb032c9c078ea7 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 1 Mar 2023 13:02:25 -0800 Subject: [PATCH 07/28] Switch to use map in Fetch.fetchEvents --- src/lib/fetch.ts | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 1f7c680666..cbbf41e094 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -536,20 +536,17 @@ async function fetchEvents( } } - let events = []; - for (let i = 0; i < fetchedEvents.length; i++) { - let event = fetchedEvents[i]; - let parsedEvents: any = []; - - event.eventData.forEach((event: { index: string; data: string[] }) => { - parsedEvents.push([event.index].concat(event.data)); - }); - events.push({ - events: parsedEvents, + return fetchedEvents.map((event: any) => { + const events = event.eventData.map( + (eventData: { index: string; data: string[] }) => { + return [eventData.index].concat(eventData.data); + } + ); + return { + events, height: event.blockInfo.height, - }); - } - return events; + }; + }); } // removes the quotes on JSON keys From bddebc697fc9faaf22f8800d09f2e525cbd62365 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Mar 2023 10:25:15 -0800 Subject: [PATCH 08/28] Add method overloading for Mina.Network --- src/lib/mina.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index a75d60b5a5..0cea7a18b4 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -572,10 +572,24 @@ function LocalBlockchain({ /** * Represents the Mina blockchain running on a real network */ -function Network(graphqlEndpoint: string, archiveEndpoint?: string): Mina { +function Network(graphqlEndpoint: string): Mina; +function Network(graphqlEndpoints: { mina: string; archive: string }): Mina; +function Network(input: { mina: string; archive: string } | string): Mina; +function Network(input: { mina: string; archive: string } | string): Mina { let accountCreationFee = UInt64.from(defaultAccountCreationFee); - Fetch.setGraphqlEndpoint(graphqlEndpoint); - if (archiveEndpoint) Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); + let graphqlEndpoint: string; + let archiveEndpoint: string; + + if (typeof input === 'string') { + graphqlEndpoint = input; + Fetch.setGraphqlEndpoint(graphqlEndpoint); + } else { + graphqlEndpoint = input.mina; + archiveEndpoint = input.archive; + Fetch.setGraphqlEndpoint(graphqlEndpoint); + Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); + } + return { accountCreationFee: () => accountCreationFee, currentSlot() { From 416e8ebfef8022b14e426e1b72fdf59579181865 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Mar 2023 10:41:12 -0800 Subject: [PATCH 09/28] Add TODO for best tip issue --- src/lib/fetch.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index cbbf41e094..fe784ecc01 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -518,7 +518,8 @@ async function fetchEvents( ); } - // TODO: Is this what we want to do? Should we just return the events? + // TODO: This is a temporary fix. We should be able to fetch the event/action data from any block at the best tip. + // Once https://github.com/o1-labs/Archive-Node-API/issues/7 is resolved, we can remove this. // If we have multiple blocks returned at the best tip (e.g. distanceFromMaxBlockHeight === 0), // then filter out the blocks at the best tip. This is because we cannot guarantee that every block // at the best tip will have the correct event data or guarantee that the specific block data will not From 4e15641c18457dac621e6c38fa9b7f94541ca4c0 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Mar 2023 10:52:35 -0800 Subject: [PATCH 10/28] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46b0c875a5..5d82933db9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 > No unreleased changes yet +### Added + +- Add functionality to fetch events from a GraphQL endpoint that implements the [following schema](https://github.com/o1-labs/Archive-Node-API/blob/efebc9fd3cfc028f536ae2125e0d2676e2b86cd2/src/schema.ts#L1) : https://github.com/o1-labs/snarkyjs/pull/749 + ## [0.9.2](https://github.com/o1-labs/snarkyjs/compare/9c44b9c2...1abdfb70) ### Added From e7ce0d527cf308b2209f9331da1b78fbae5a1abc Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Mar 2023 12:19:32 -0800 Subject: [PATCH 11/28] Add comments for fetchEvents --- src/lib/fetch.ts | 22 ++++++++++++++++++++-- src/lib/zkapp.ts | 14 ++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index fe784ecc01..2ea736a178 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -47,6 +47,11 @@ function setGraphqlEndpoint(graphqlEndpoint: string) { defaultGraphqlEndpoint = graphqlEndpoint; } +/** + * Sets up a GraphQL endpoint to be used for fetching information from an Archive Node. + * + * @param {string} - A GraphQL endpoint. + */ function setArchiveGraphqlEndpoint(graphqlEndpoint: string) { archiveGraphqlEndpoint = graphqlEndpoint; } @@ -493,9 +498,22 @@ type EventActionFilterOptions = { to?: UInt32; from?: UInt32; }; + /** - * Fetches the events for an account. - */ +Asynchronously fetches event data for an account from the Mina Archive Node GraphQL API. +@async +@param {object} accountInfo - The account information object. +@param {string} accountInfo.publicKey - The account public key. +@param {string} [accountInfo.tokenId] - The optional token ID for the account. +@param {string} [graphqlEndpoint=archiveGraphqlEndpoint] - The GraphQL endpoint to query. Defaults to the Archive Node GraphQL API. +@param {object} [filterOptions={}] - The optional filter options object. +@returns {Promise} A promise that resolves to an array of objects containing event data and block height for the account. +@throws {Error} If the GraphQL request fails or the response is invalid. +@example +const accountInfo = { publicKey: 'Gt7YKtRQ2bm7mNgxkPT2SwmLYm55KJXgz7SBaE5z5WH5' }; +const events = await fetchEvents(accountInfo); +console.log(events); +*/ async function fetchEvents( accountInfo: { publicKey: string; tokenId?: string }, graphqlEndpoint = archiveGraphqlEndpoint, diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index e5333d6fde..a78f33f89f 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1036,8 +1036,18 @@ super.init(); } /** - * Fetches a list of events that have been emitted by this {@link SmartContract}. - */ + Asynchronously fetches events emitted by this {@link SmartContract} and returns an array of events with their corresponding types. + @async + @param {UInt32} [start=UInt32.from(0)] - The start height of the events to fetch. + @param {UInt32} [end] - The end height of the events to fetch. If not provided, fetches events up to the latest height. + @returns {Promise} A promise that resolves to an array of objects, each containing the event type and event data for the specified range. + @throws {Error} If there is an error fetching events from the Mina network. + @example + const startHeight = UInt32.from(1000); + const endHeight = UInt32.from(2000); + const events = await myZkapp.fetchEvents(startHeight, endHeight); + console.log(events); + */ async fetchEvents( start: UInt32 = UInt32.from(0), end?: UInt32 From 3c8210149b48ca1f6b00e5e6e6527e106234d5ea Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Mar 2023 12:23:53 -0800 Subject: [PATCH 12/28] Remove unneeded line to method overload declaration --- src/lib/mina.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 6523b166ae..c4dba294bd 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -593,7 +593,6 @@ LocalBlockchain satisfies (...args: any) => Mina; */ function Network(graphqlEndpoint: string): Mina; function Network(graphqlEndpoints: { mina: string; archive: string }): Mina; -function Network(input: { mina: string; archive: string } | string): Mina; function Network(input: { mina: string; archive: string } | string): Mina { let accountCreationFee = UInt64.from(defaultAccountCreationFee); let graphqlEndpoint: string; @@ -609,7 +608,6 @@ function Network(input: { mina: string; archive: string } | string): Mina { Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); } - // copied from mina/genesis_ledgers/berkeley.json // TODO fetch from graphql instead of hardcoding const genesisTimestampString = '2023-02-23T20:00:01Z'; From 2cde7f3da679466fce874a38ec57af48f32505a5 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Mar 2023 12:27:44 -0800 Subject: [PATCH 13/28] Reword Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d82933db9..5cd6d96bf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add functionality to fetch events from a GraphQL endpoint that implements the [following schema](https://github.com/o1-labs/Archive-Node-API/blob/efebc9fd3cfc028f536ae2125e0d2676e2b86cd2/src/schema.ts#L1) : https://github.com/o1-labs/snarkyjs/pull/749 +- Added a new feature to the library: `fetchEvents` can now be used to fetch events for a specified zkApp from a GraphQL endpoint that implements the schema specified [here](https://github.com/o1-labs/Archive-Node-API/blob/efebc9fd3cfc028f536ae2125e0d2676e2b86cd2/src/schema.ts#L1). ## [0.9.2](https://github.com/o1-labs/snarkyjs/compare/9c44b9c2...1abdfb70) From 07d238992c2664ed1dec5615ec90646be9998be0 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Mar 2023 12:52:47 -0800 Subject: [PATCH 14/28] Update example using zkapp.fetchEvents --- src/examples/zkapps/local_events_zkapp.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/examples/zkapps/local_events_zkapp.ts b/src/examples/zkapps/local_events_zkapp.ts index 3c7a903d86..0af796df49 100644 --- a/src/examples/zkapps/local_events_zkapp.ts +++ b/src/examples/zkapps/local_events_zkapp.ts @@ -86,14 +86,14 @@ await tx.prove(); await tx.sign([feePayerKey]).send(); console.log('---- emitted events: ----'); -// fetches all events from zkapp starting slot 0 +// fetches all events from zkapp starting block height 0 let events = await zkapp.fetchEvents(UInt32.from(0)); console.log(events); console.log('---- emitted events: ----'); -// fetches all events -events = await zkapp.fetchEvents(); +// fetches all events from zkapp starting block height 0 and ending at block height 10 +events = await zkapp.fetchEvents(UInt32.from(0), UInt64.from(10)); console.log(events); console.log('---- emitted events: ----'); -// fetches all events second time +// fetches all events events = await zkapp.fetchEvents(); console.log(events); From f71845786c8a750464b7dec25da374e9188cf3fb Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Mar 2023 12:54:52 -0800 Subject: [PATCH 15/28] Use real Mina Address in fetchEvents docs --- src/lib/fetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 2ea736a178..b4da8d2286 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -510,7 +510,7 @@ Asynchronously fetches event data for an account from the Mina Archive Node Grap @returns {Promise} A promise that resolves to an array of objects containing event data and block height for the account. @throws {Error} If the GraphQL request fails or the response is invalid. @example -const accountInfo = { publicKey: 'Gt7YKtRQ2bm7mNgxkPT2SwmLYm55KJXgz7SBaE5z5WH5' }; +const accountInfo = { publicKey: 'B62qiwmXrWn7Cok5VhhB3KvCwyZ7NHHstFGbiU5n7m8s2RqqNW1p1wF' }; const events = await fetchEvents(accountInfo); console.log(events); */ From b7c41df18a56933f46f5121e211cabe6f7ce406a Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 2 Mar 2023 14:09:40 -0800 Subject: [PATCH 16/28] Feedback --- CHANGELOG.md | 2 +- src/lib/fetch.ts | 20 ++++++++++++-------- src/lib/mina.ts | 8 ++++++-- src/lib/zkapp.ts | 8 ++++---- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cd6d96bf5..03ee5c6c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added a new feature to the library: `fetchEvents` can now be used to fetch events for a specified zkApp from a GraphQL endpoint that implements the schema specified [here](https://github.com/o1-labs/Archive-Node-API/blob/efebc9fd3cfc028f536ae2125e0d2676e2b86cd2/src/schema.ts#L1). +- Added a new feature to the library: `fetchEvents` can now be used to fetch events for a specified zkApp from a GraphQL endpoint that implements the schema specified [here](https://github.com/o1-labs/Archive-Node-API/blob/efebc9fd3cfc028f536ae2125e0d2676e2b86cd2/src/schema.ts#L1). `Mina.Network` now accepts an additional endpoint to configure, which points to a GraphQL server running the mentioned schema. Use the `mina` property for normal usage and use `archive` to connect to the mentioned GraphQL server. ## [0.9.2](https://github.com/o1-labs/snarkyjs/compare/9c44b9c2...1abdfb70) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index b4da8d2286..5c6e01866a 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -50,7 +50,7 @@ function setGraphqlEndpoint(graphqlEndpoint: string) { /** * Sets up a GraphQL endpoint to be used for fetching information from an Archive Node. * - * @param {string} - A GraphQL endpoint. + * @param A GraphQL endpoint. */ function setArchiveGraphqlEndpoint(graphqlEndpoint: string) { archiveGraphqlEndpoint = graphqlEndpoint; @@ -502,13 +502,13 @@ type EventActionFilterOptions = { /** Asynchronously fetches event data for an account from the Mina Archive Node GraphQL API. @async -@param {object} accountInfo - The account information object. -@param {string} accountInfo.publicKey - The account public key. -@param {string} [accountInfo.tokenId] - The optional token ID for the account. -@param {string} [graphqlEndpoint=archiveGraphqlEndpoint] - The GraphQL endpoint to query. Defaults to the Archive Node GraphQL API. -@param {object} [filterOptions={}] - The optional filter options object. -@returns {Promise} A promise that resolves to an array of objects containing event data and block height for the account. -@throws {Error} If the GraphQL request fails or the response is invalid. +@param accountInfo - The account information object. +@param accountInfo.publicKey - The account public key. +@param [accountInfo.tokenId] - The optional token ID for the account. +@param [graphqlEndpoint=archiveGraphqlEndpoint] - The GraphQL endpoint to query. Defaults to the Archive Node GraphQL API. +@param [filterOptions={}] - The optional filter options object. +@returns A promise that resolves to an array of objects containing event data and block height for the account. +@throws If the GraphQL request fails or the response is invalid. @example const accountInfo = { publicKey: 'B62qiwmXrWn7Cok5VhhB3KvCwyZ7NHHstFGbiU5n7m8s2RqqNW1p1wF' }; const events = await fetchEvents(accountInfo); @@ -519,6 +519,10 @@ async function fetchEvents( graphqlEndpoint = archiveGraphqlEndpoint, filterOptions: EventActionFilterOptions = {} ): Promise { + if (!graphqlEndpoint) + throw new Error( + 'fetchEvents: Specified GraphQL endpoint is undefined. Please specify a valid endpoint.' + ); const { publicKey, tokenId } = accountInfo; let [response, error] = await makeGraphqlRequest( getEventsQuery( diff --git a/src/lib/mina.ts b/src/lib/mina.ts index c4dba294bd..c219027e51 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -598,14 +598,18 @@ function Network(input: { mina: string; archive: string } | string): Mina { let graphqlEndpoint: string; let archiveEndpoint: string; - if (typeof input === 'string') { + if (input && typeof input === 'string') { graphqlEndpoint = input; Fetch.setGraphqlEndpoint(graphqlEndpoint); - } else { + } else if (input && typeof input === 'object') { graphqlEndpoint = input.mina; archiveEndpoint = input.archive; Fetch.setGraphqlEndpoint(graphqlEndpoint); Fetch.setArchiveGraphqlEndpoint(archiveEndpoint); + } else { + throw new Error( + "Network: malformed input. Please provide a string or an object with 'mina' and 'archive' endpoints." + ); } // copied from mina/genesis_ledgers/berkeley.json diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index a78f33f89f..b517e0debd 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1038,10 +1038,10 @@ super.init(); /** Asynchronously fetches events emitted by this {@link SmartContract} and returns an array of events with their corresponding types. @async - @param {UInt32} [start=UInt32.from(0)] - The start height of the events to fetch. - @param {UInt32} [end] - The end height of the events to fetch. If not provided, fetches events up to the latest height. - @returns {Promise} A promise that resolves to an array of objects, each containing the event type and event data for the specified range. - @throws {Error} If there is an error fetching events from the Mina network. + @param [start=UInt32.from(0)] - The start height of the events to fetch. + @param end - The end height of the events to fetch. If not provided, fetches events up to the latest height. + @returns A promise that resolves to an array of objects, each containing the event type and event data for the specified range. + @throws If there is an error fetching events from the Mina network. @example const startHeight = UInt32.from(1000); const endHeight = UInt32.from(2000); From 08342ec188ffc060ea825d39877c53ee598b92c4 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 8 Mar 2023 09:13:46 -0800 Subject: [PATCH 17/28] Update comments to use unified style --- src/lib/fetch.ts | 28 ++++++++++++++-------------- src/lib/zkapp.ts | 24 ++++++++++++------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 5c6e01866a..d343542265 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -500,20 +500,20 @@ type EventActionFilterOptions = { }; /** -Asynchronously fetches event data for an account from the Mina Archive Node GraphQL API. -@async -@param accountInfo - The account information object. -@param accountInfo.publicKey - The account public key. -@param [accountInfo.tokenId] - The optional token ID for the account. -@param [graphqlEndpoint=archiveGraphqlEndpoint] - The GraphQL endpoint to query. Defaults to the Archive Node GraphQL API. -@param [filterOptions={}] - The optional filter options object. -@returns A promise that resolves to an array of objects containing event data and block height for the account. -@throws If the GraphQL request fails or the response is invalid. -@example -const accountInfo = { publicKey: 'B62qiwmXrWn7Cok5VhhB3KvCwyZ7NHHstFGbiU5n7m8s2RqqNW1p1wF' }; -const events = await fetchEvents(accountInfo); -console.log(events); -*/ + * Asynchronously fetches event data for an account from the Mina Archive Node GraphQL API. + * @async + * @param accountInfo - The account information object. + * @param accountInfo.publicKey - The account public key. + * @param [accountInfo.tokenId] - The optional token ID for the account. + * @param [graphqlEndpoint=archiveGraphqlEndpoint] - The GraphQL endpoint to query. Defaults to the Archive Node GraphQL API. + * @param [filterOptions={}] - The optional filter options object. + * @returns A promise that resolves to an array of objects containing event data and block height for the account. + * @throws If the GraphQL request fails or the response is invalid. + * @example + * const accountInfo = { publicKey: 'B62qiwmXrWn7Cok5VhhB3KvCwyZ7NHHstFGbiU5n7m8s2RqqNW1p1wF' }; + * const events = await fetchEvents(accountInfo); + * console.log(events); + */ async function fetchEvents( accountInfo: { publicKey: string; tokenId?: string }, graphqlEndpoint = archiveGraphqlEndpoint, diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index b517e0debd..58b8c62609 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1036,18 +1036,18 @@ super.init(); } /** - Asynchronously fetches events emitted by this {@link SmartContract} and returns an array of events with their corresponding types. - @async - @param [start=UInt32.from(0)] - The start height of the events to fetch. - @param end - The end height of the events to fetch. If not provided, fetches events up to the latest height. - @returns A promise that resolves to an array of objects, each containing the event type and event data for the specified range. - @throws If there is an error fetching events from the Mina network. - @example - const startHeight = UInt32.from(1000); - const endHeight = UInt32.from(2000); - const events = await myZkapp.fetchEvents(startHeight, endHeight); - console.log(events); - */ + * Asynchronously fetches events emitted by this {@link SmartContract} and returns an array of events with their corresponding types. + * @async + * @param [start=UInt32.from(0)] - The start height of the events to fetch. + * @param end - The end height of the events to fetch. If not provided, fetches events up to the latest height. + * @returns A promise that resolves to an array of objects, each containing the event type and event data for the specified range. + * @throws If there is an error fetching events from the Mina network. + * @example + * const startHeight = UInt32.from(1000); + * const endHeight = UInt32.from(2000); + * const events = await myZkapp.fetchEvents(startHeight, endHeight); + * console.log(events); + */ async fetchEvents( start: UInt32 = UInt32.from(0), end?: UInt32 From 5e57898b2eff213998f7d938a8488334caa5d30d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 8 Mar 2023 10:13:40 -0800 Subject: [PATCH 18/28] Add better type saftey --- src/lib/fetch.ts | 40 ++++++++++++++++++++++++---------------- src/lib/mina.ts | 4 ++-- src/lib/zkapp.ts | 11 ++++++----- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index d343542265..89d800e47d 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -466,6 +466,22 @@ function sendZkappQuery(json: string) { `; } +type FetchedEvents = { + blockInfo: { + distanceFromMaxBlockHeight: number; + height: number; + }; + eventData: { + index: string; + data: string; + }[]; +}; + +type EventActionFilterOptions = { + to?: UInt32; + from?: UInt32; +}; + const getEventsQuery = ( publicKey: string, tokenId: string, @@ -490,13 +506,7 @@ const getEventsQuery = ( data } } -} -`; -}; - -type EventActionFilterOptions = { - to?: UInt32; - from?: UInt32; +}`; }; /** @@ -518,7 +528,7 @@ async function fetchEvents( accountInfo: { publicKey: string; tokenId?: string }, graphqlEndpoint = archiveGraphqlEndpoint, filterOptions: EventActionFilterOptions = {} -): Promise { +) { if (!graphqlEndpoint) throw new Error( 'fetchEvents: Specified GraphQL endpoint is undefined. Please specify a valid endpoint.' @@ -533,7 +543,7 @@ async function fetchEvents( graphqlEndpoint ); if (error) throw Error(error.statusText); - let fetchedEvents = response?.data.events; + let fetchedEvents = response?.data.events as FetchedEvents[]; if (fetchedEvents === undefined) { throw Error( `Failed to fetch events data. Account: ${publicKey} Token: ${tokenId}` @@ -552,19 +562,17 @@ async function fetchEvents( numberOfBestTipBlocks++; } if (numberOfBestTipBlocks > 1) { - fetchedEvents = fetchedEvents.filter((event: any) => { + fetchedEvents = fetchedEvents.filter((event) => { return event.blockInfo.distanceFromMaxBlockHeight !== 0; }); break; } } - return fetchedEvents.map((event: any) => { - const events = event.eventData.map( - (eventData: { index: string; data: string[] }) => { - return [eventData.index].concat(eventData.data); - } - ); + return fetchedEvents.map((event) => { + let events = event.eventData.map((eventData) => { + return [eventData.index].concat(eventData.data); + }); return { events, height: event.blockInfo.height, diff --git a/src/lib/mina.ts b/src/lib/mina.ts index c219027e51..3c8bfd3d46 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -324,7 +324,7 @@ interface Mina { publicKey: PublicKey, tokenId?: Field, filterOptions?: Fetch.EventActionFilterOptions - ) => any; + ) => Promise<{ events: string[][]; height: number }[]>; getActions: ( publicKey: PublicKey, tokenId?: Field @@ -784,7 +784,7 @@ function Network(input: { mina: string; archive: string } | string): Mina { let pubKey = publicKey.toBase58(); let token = TokenId.toBase58(tokenId); - return await Fetch.fetchEvents( + return Fetch.fetchEvents( { publicKey: pubKey, tokenId: token }, archiveEndpoint, filterOptions diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 58b8c62609..da48b9dbfc 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1060,20 +1060,20 @@ super.init(); to: end, }) ) - .filter((el: any) => { - let height = UInt32.from(el.height); + .filter((eventData) => { + let height = UInt32.from(eventData.height); return end === undefined ? start.lessThanOrEqual(height).toBoolean() : start.lessThanOrEqual(height).toBoolean() && height.lessThanOrEqual(end).toBoolean(); }) - .map((el: any) => el.events) + .map((eventData) => eventData.events) .flat(); // used to match field values back to their original type let sortedEventTypes = Object.keys(this.events).sort(); - return events.map((event: any) => { + return events.map((event) => { // if there is only one event type, the event structure has no index and can directly be matched to the event type if (sortedEventTypes.length === 1) { let type = sortedEventTypes[0]; @@ -1085,7 +1085,8 @@ super.init(); }; } else { // if there are multiple events we have to use the index event[0] to find the exact event type - let type = sortedEventTypes[event[0]]; + let eventObjectIndex = Number(event[0]); + let type = sortedEventTypes[eventObjectIndex]; // all other elements of the array are values used to construct the original object, we can drop the first value since its just an index let eventProps = event.slice(1); return { From 68eece98ea1470c7d85f8635e8b0630c5c79d13b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 8 Mar 2023 10:24:09 -0800 Subject: [PATCH 19/28] Add additional block and transaction info to Fetch.fetchEvents --- src/lib/fetch.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 89d800e47d..d818d7a1fb 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -469,7 +469,16 @@ function sendZkappQuery(json: string) { type FetchedEvents = { blockInfo: { distanceFromMaxBlockHeight: number; + globalSlotSinceGenesis: number; height: number; + stateHash: string; + parentHash: string; + chainStatus: string; + }; + transactionInfo: { + hash: string; + memo: string; + status: string; }; eventData: { index: string; @@ -500,6 +509,15 @@ const getEventsQuery = ( blockInfo { distanceFromMaxBlockHeight height + globalSlotSinceGenesis + stateHash + parentHash + chainStatus + } + transactionInfo { + hash + memo + status } eventData { index @@ -517,7 +535,7 @@ const getEventsQuery = ( * @param [accountInfo.tokenId] - The optional token ID for the account. * @param [graphqlEndpoint=archiveGraphqlEndpoint] - The GraphQL endpoint to query. Defaults to the Archive Node GraphQL API. * @param [filterOptions={}] - The optional filter options object. - * @returns A promise that resolves to an array of objects containing event data and block height for the account. + * @returns A promise that resolves to an array of objects containing event data, block information and transaction information for the account. * @throws If the GraphQL request fails or the response is invalid. * @example * const accountInfo = { publicKey: 'B62qiwmXrWn7Cok5VhhB3KvCwyZ7NHHstFGbiU5n7m8s2RqqNW1p1wF' }; @@ -576,6 +594,13 @@ async function fetchEvents( return { events, height: event.blockInfo.height, + blockHash: event.blockInfo.stateHash, + parentBlockHash: event.blockInfo.parentHash, + globalSlot: event.blockInfo.globalSlotSinceGenesis, + chainStatus: event.blockInfo.chainStatus, + transactionHash: event.transactionInfo.hash, + transactionStatus: event.transactionInfo.status, + transactionMemo: event.transactionInfo.memo, }; }); } From dc1739275e0e41af518a32ade510c33c2693161d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 8 Mar 2023 11:10:18 -0800 Subject: [PATCH 20/28] Add additional network info to zkApp.fetchEvents w/ better typing --- src/lib/fetch.ts | 16 ++++++------- src/lib/mina.ts | 7 ++---- src/lib/zkapp.ts | 59 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index d818d7a1fb..ab557329d3 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -465,8 +465,7 @@ function sendZkappQuery(json: string) { } `; } - -type FetchedEvents = { +type FetchedEventActionBase = { blockInfo: { distanceFromMaxBlockHeight: number; globalSlotSinceGenesis: number; @@ -480,11 +479,13 @@ type FetchedEvents = { memo: string; status: string; }; +}; +type FetchedEvents = { eventData: { index: string; - data: string; + data: string[]; }[]; -}; +} & FetchedEventActionBase; type EventActionFilterOptions = { to?: UInt32; @@ -588,12 +589,9 @@ async function fetchEvents( } return fetchedEvents.map((event) => { - let events = event.eventData.map((eventData) => { - return [eventData.index].concat(eventData.data); - }); return { - events, - height: event.blockInfo.height, + events: event.eventData, + blockHeight: event.blockInfo.height, blockHash: event.blockInfo.stateHash, parentBlockHash: event.blockInfo.parentHash, globalSlot: event.blockInfo.globalSlotSinceGenesis, diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 3c8bfd3d46..0356f243d5 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -324,7 +324,7 @@ interface Mina { publicKey: PublicKey, tokenId?: Field, filterOptions?: Fetch.EventActionFilterOptions - ) => Promise<{ events: string[][]; height: number }[]>; + ) => ReturnType; getActions: ( publicKey: PublicKey, tokenId?: Field @@ -546,10 +546,7 @@ function LocalBlockchain({ JSON.stringify(networkState) ); }, - async fetchEvents( - publicKey: PublicKey, - tokenId: Field = TokenId.default - ): Promise { + async fetchEvents(publicKey: PublicKey, tokenId: Field = TokenId.default) { return events?.[publicKey.toBase58()]?.[TokenId.toBase58(tokenId)] ?? []; }, getActions( diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index da48b9dbfc..7af949002d 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1051,7 +1051,20 @@ super.init(); async fetchEvents( start: UInt32 = UInt32.from(0), end?: UInt32 - ): Promise<{ type: string; event: ProvablePure }[]> { + ): Promise< + { + type: string; + event: ProvablePure; + blockHeight: number; + blockHash: string; + parentBlockHash: string; + globalSlot: number; + chainStatus: string; + transactionHash: string; + transactionStatus: string; + transactionMemo: string; + }[] + > { // filters all elements so that they are within the given range // only returns { type: "", event: [] } in a flat format let events = ( @@ -1061,14 +1074,30 @@ super.init(); }) ) .filter((eventData) => { - let height = UInt32.from(eventData.height); + let height = UInt32.from(eventData.blockHeight); return end === undefined ? start.lessThanOrEqual(height).toBoolean() : start.lessThanOrEqual(height).toBoolean() && height.lessThanOrEqual(end).toBoolean(); }) - .map((eventData) => eventData.events) - .flat(); + .map((event) => { + let eventData = event.events + .map((event) => { + return [event.index, ...event.data]; + }) + .flat(); + return { + eventData, + blockHeight: event.blockHeight, + blockHash: event.blockHash, + parentBlockHash: event.parentBlockHash, + globalSlot: event.globalSlot, + chainStatus: event.chainStatus, + transactionHash: event.transactionHash, + transactionStatus: event.transactionStatus, + transactionMemo: event.transactionMemo, + }; + }); // used to match field values back to their original type let sortedEventTypes = Object.keys(this.events).sort(); @@ -1080,20 +1109,36 @@ super.init(); return { type, event: this.events[type].fromFields( - event.map((f: string) => Field(f)) + event.eventData.map((f: string) => Field(f)) ), + blockHeight: event.blockHeight, + blockHash: event.blockHash, + parentBlockHash: event.parentBlockHash, + globalSlot: event.globalSlot, + chainStatus: event.chainStatus, + transactionHash: event.transactionHash, + transactionStatus: event.transactionStatus, + transactionMemo: event.transactionMemo, }; } else { // if there are multiple events we have to use the index event[0] to find the exact event type - let eventObjectIndex = Number(event[0]); + let eventObjectIndex = Number(event.eventData[0]); let type = sortedEventTypes[eventObjectIndex]; // all other elements of the array are values used to construct the original object, we can drop the first value since its just an index - let eventProps = event.slice(1); + let eventProps = event.eventData.slice(1); return { type, event: this.events[type].fromFields( eventProps.map((f: string) => Field(f)) ), + blockHeight: event.blockHeight, + blockHash: event.blockHash, + parentBlockHash: event.parentBlockHash, + globalSlot: event.globalSlot, + chainStatus: event.chainStatus, + transactionHash: event.transactionHash, + transactionStatus: event.transactionStatus, + transactionMemo: event.transactionMemo, }; } }); From dc7fa1dcb1529ffeb682182b7454ec49f7f08c2d Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 8 Mar 2023 14:42:00 -0800 Subject: [PATCH 21/28] Match local and network versions of fetchEvents --- src/lib/fetch.ts | 5 +++- src/lib/mina.ts | 11 +++++++- src/lib/zkapp.ts | 72 +++++++++++++++++++++++------------------------- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index ab557329d3..24053560b3 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -589,8 +589,11 @@ async function fetchEvents( } return fetchedEvents.map((event) => { + let events = event.eventData.map((eventData) => { + return [eventData.index, ...eventData.data]; + }); return { - events: event.eventData, + events, blockHeight: event.blockInfo.height, blockHash: event.blockInfo.stateHash, parentBlockHash: event.blockInfo.parentHash, diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 0356f243d5..687d1a3095 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -464,7 +464,16 @@ function LocalBlockchain({ } events[addr][tokenId].push({ events: p.body.events, - height: networkState.blockchainLength.toString(), + blockHeight: networkState.blockchainLength.toString(), + globalSlot: networkState.globalSlotSinceGenesis.toString(), + // The following fields are fetched from the Mina network. For now, we mock these values out + // since networkState does not contain these fields. + blockHash: '', + parentBlockHash: '', + chainStatus: '', + transactionHash: '', + transactionStatus: '', + transactionMemo: '', }); } diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 7af949002d..ac5deb1d99 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1081,64 +1081,62 @@ super.init(); height.lessThanOrEqual(end).toBoolean(); }) .map((event) => { - let eventData = event.events - .map((event) => { - return [event.index, ...event.data]; - }) - .flat(); - return { - eventData, - blockHeight: event.blockHeight, - blockHash: event.blockHash, - parentBlockHash: event.parentBlockHash, - globalSlot: event.globalSlot, - chainStatus: event.chainStatus, - transactionHash: event.transactionHash, - transactionStatus: event.transactionStatus, - transactionMemo: event.transactionMemo, - }; - }); + return event.events.map((eventData) => { + return { + event: eventData, + blockHeight: event.blockHeight, + blockHash: event.blockHash, + parentBlockHash: event.parentBlockHash, + globalSlot: event.globalSlot, + chainStatus: event.chainStatus, + transactionHash: event.transactionHash, + transactionStatus: event.transactionStatus, + transactionMemo: event.transactionMemo, + }; + }); + }) + .flat(); // used to match field values back to their original type let sortedEventTypes = Object.keys(this.events).sort(); - return events.map((event) => { + return events.map((eventData) => { // if there is only one event type, the event structure has no index and can directly be matched to the event type if (sortedEventTypes.length === 1) { let type = sortedEventTypes[0]; return { type, event: this.events[type].fromFields( - event.eventData.map((f: string) => Field(f)) + eventData.event.map((f: string) => Field(f)) ), - blockHeight: event.blockHeight, - blockHash: event.blockHash, - parentBlockHash: event.parentBlockHash, - globalSlot: event.globalSlot, - chainStatus: event.chainStatus, - transactionHash: event.transactionHash, - transactionStatus: event.transactionStatus, - transactionMemo: event.transactionMemo, + blockHeight: eventData.blockHeight, + blockHash: eventData.blockHash, + parentBlockHash: eventData.parentBlockHash, + globalSlot: eventData.globalSlot, + chainStatus: eventData.chainStatus, + transactionHash: eventData.transactionHash, + transactionStatus: eventData.transactionStatus, + transactionMemo: eventData.transactionMemo, }; } else { // if there are multiple events we have to use the index event[0] to find the exact event type - let eventObjectIndex = Number(event.eventData[0]); + let eventObjectIndex = Number(eventData.event[0]); let type = sortedEventTypes[eventObjectIndex]; // all other elements of the array are values used to construct the original object, we can drop the first value since its just an index - let eventProps = event.eventData.slice(1); + let eventProps = eventData.event.slice(1); return { type, event: this.events[type].fromFields( eventProps.map((f: string) => Field(f)) ), - blockHeight: event.blockHeight, - blockHash: event.blockHash, - parentBlockHash: event.parentBlockHash, - globalSlot: event.globalSlot, - chainStatus: event.chainStatus, - transactionHash: event.transactionHash, - transactionStatus: event.transactionStatus, - transactionMemo: event.transactionMemo, + blockHeight: eventData.blockHeight, + blockHash: eventData.blockHash, + parentBlockHash: eventData.parentBlockHash, + globalSlot: eventData.globalSlot, + chainStatus: eventData.chainStatus, + transactionHash: eventData.transactionHash, + transactionStatus: eventData.transactionStatus, + transactionMemo: eventData.transactionMemo, }; } }); From fc9c6806f374ffda3d014aaa921c4cd871fb6d7b Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 8 Mar 2023 14:54:59 -0800 Subject: [PATCH 22/28] Remove toString from localblockchain events --- src/lib/mina.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 687d1a3095..29e50b0b1d 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -464,8 +464,8 @@ function LocalBlockchain({ } events[addr][tokenId].push({ events: p.body.events, - blockHeight: networkState.blockchainLength.toString(), - globalSlot: networkState.globalSlotSinceGenesis.toString(), + blockHeight: networkState.blockchainLength, + globalSlot: networkState.globalSlotSinceGenesis, // The following fields are fetched from the Mina network. For now, we mock these values out // since networkState does not contain these fields. blockHash: '', From b81ad38a0f02b85047e8d9ec4db21f5cba47899f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 8 Mar 2023 14:57:02 -0800 Subject: [PATCH 23/28] Undo previous removal of toString and call toString on network fevents --- src/lib/mina.ts | 4 ++-- src/lib/zkapp.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 29e50b0b1d..687d1a3095 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -464,8 +464,8 @@ function LocalBlockchain({ } events[addr][tokenId].push({ events: p.body.events, - blockHeight: networkState.blockchainLength, - globalSlot: networkState.globalSlotSinceGenesis, + blockHeight: networkState.blockchainLength.toString(), + globalSlot: networkState.globalSlotSinceGenesis.toString(), // The following fields are fetched from the Mina network. For now, we mock these values out // since networkState does not contain these fields. blockHash: '', diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index ac5deb1d99..3dcdffd76b 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1055,10 +1055,10 @@ super.init(); { type: string; event: ProvablePure; - blockHeight: number; + blockHeight: string; blockHash: string; parentBlockHash: string; - globalSlot: number; + globalSlot: string; chainStatus: string; transactionHash: string; transactionStatus: string; @@ -1084,10 +1084,10 @@ super.init(); return event.events.map((eventData) => { return { event: eventData, - blockHeight: event.blockHeight, + blockHeight: event.blockHeight.toString(), blockHash: event.blockHash, parentBlockHash: event.parentBlockHash, - globalSlot: event.globalSlot, + globalSlot: event.globalSlot.toString(), chainStatus: event.chainStatus, transactionHash: event.transactionHash, transactionStatus: event.transactionStatus, From bdcff436442cc2f5fa315aba9cffc8d2c0de5ae1 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Wed, 8 Mar 2023 14:58:43 -0800 Subject: [PATCH 24/28] Add toString() to Fetch.fetchEvents to match other APIs --- src/lib/fetch.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 24053560b3..42655718ed 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -594,10 +594,10 @@ async function fetchEvents( }); return { events, - blockHeight: event.blockInfo.height, + blockHeight: event.blockInfo.height.toString(), blockHash: event.blockInfo.stateHash, parentBlockHash: event.blockInfo.parentHash, - globalSlot: event.blockInfo.globalSlotSinceGenesis, + globalSlot: event.blockInfo.globalSlotSinceGenesis.toString(), chainStatus: event.blockInfo.chainStatus, transactionHash: event.transactionInfo.hash, transactionStatus: event.transactionInfo.status, From 0cb36fe0f945b1fac8e3b1b9f42fad670c2c9088 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 9 Mar 2023 12:56:58 +0100 Subject: [PATCH 25/28] minor --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03ee5c6c59..0731ba1302 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/1abdfb70...HEAD) -> No unreleased changes yet - ### Added - Added a new feature to the library: `fetchEvents` can now be used to fetch events for a specified zkApp from a GraphQL endpoint that implements the schema specified [here](https://github.com/o1-labs/Archive-Node-API/blob/efebc9fd3cfc028f536ae2125e0d2676e2b86cd2/src/schema.ts#L1). `Mina.Network` now accepts an additional endpoint to configure, which points to a GraphQL server running the mentioned schema. Use the `mina` property for normal usage and use `archive` to connect to the mentioned GraphQL server. From 8aec80192e81ca49a488a8f7f404ee233a196eeb Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 9 Mar 2023 09:09:05 -0800 Subject: [PATCH 26/28] Use SnarkyJS number types --- src/lib/fetch.ts | 4 ++-- src/lib/mina.ts | 4 ++-- src/lib/zkapp.ts | 45 +++++++++++++-------------------------------- 3 files changed, 17 insertions(+), 36 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 42655718ed..e871f366a6 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -594,10 +594,10 @@ async function fetchEvents( }); return { events, - blockHeight: event.blockInfo.height.toString(), + blockHeight: UInt32.from(event.blockInfo.height), blockHash: event.blockInfo.stateHash, parentBlockHash: event.blockInfo.parentHash, - globalSlot: event.blockInfo.globalSlotSinceGenesis.toString(), + globalSlot: UInt32.from(event.blockInfo.globalSlotSinceGenesis), chainStatus: event.blockInfo.chainStatus, transactionHash: event.transactionInfo.hash, transactionStatus: event.transactionInfo.status, diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 687d1a3095..29e50b0b1d 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -464,8 +464,8 @@ function LocalBlockchain({ } events[addr][tokenId].push({ events: p.body.events, - blockHeight: networkState.blockchainLength.toString(), - globalSlot: networkState.globalSlotSinceGenesis.toString(), + blockHeight: networkState.blockchainLength, + globalSlot: networkState.globalSlotSinceGenesis, // The following fields are fetched from the Mina network. For now, we mock these values out // since networkState does not contain these fields. blockHash: '', diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 3dcdffd76b..4d2334a1d7 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1055,10 +1055,10 @@ super.init(); { type: string; event: ProvablePure; - blockHeight: string; + blockHeight: UInt32; blockHash: string; parentBlockHash: string; - globalSlot: string; + globalSlot: UInt32; chainStatus: string; transactionHash: string; transactionStatus: string; @@ -1083,15 +1083,8 @@ super.init(); .map((event) => { return event.events.map((eventData) => { return { + ...event, event: eventData, - blockHeight: event.blockHeight.toString(), - blockHash: event.blockHash, - parentBlockHash: event.parentBlockHash, - globalSlot: event.globalSlot.toString(), - chainStatus: event.chainStatus, - transactionHash: event.transactionHash, - transactionStatus: event.transactionStatus, - transactionMemo: event.transactionMemo, }; }); }) @@ -1104,19 +1097,13 @@ super.init(); // if there is only one event type, the event structure has no index and can directly be matched to the event type if (sortedEventTypes.length === 1) { let type = sortedEventTypes[0]; + let event = this.events[type].fromFields( + eventData.event.map((f: string) => Field(f)) + ); return { + ...eventData, type, - event: this.events[type].fromFields( - eventData.event.map((f: string) => Field(f)) - ), - blockHeight: eventData.blockHeight, - blockHash: eventData.blockHash, - parentBlockHash: eventData.parentBlockHash, - globalSlot: eventData.globalSlot, - chainStatus: eventData.chainStatus, - transactionHash: eventData.transactionHash, - transactionStatus: eventData.transactionStatus, - transactionMemo: eventData.transactionMemo, + event, }; } else { // if there are multiple events we have to use the index event[0] to find the exact event type @@ -1124,19 +1111,13 @@ super.init(); let type = sortedEventTypes[eventObjectIndex]; // all other elements of the array are values used to construct the original object, we can drop the first value since its just an index let eventProps = eventData.event.slice(1); + let event = this.events[type].fromFields( + eventProps.map((f: string) => Field(f)) + ); return { + ...eventData, type, - event: this.events[type].fromFields( - eventProps.map((f: string) => Field(f)) - ), - blockHeight: eventData.blockHeight, - blockHash: eventData.blockHash, - parentBlockHash: eventData.parentBlockHash, - globalSlot: eventData.globalSlot, - chainStatus: eventData.chainStatus, - transactionHash: eventData.transactionHash, - transactionStatus: eventData.transactionStatus, - transactionMemo: eventData.transactionMemo, + event, }; } }); From 591576f469472bb3a9f07bd82f5580a4f27b6d6f Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Thu, 9 Mar 2023 09:18:58 -0800 Subject: [PATCH 27/28] Filter out index type in Fetch.fetchEvents if only 0 is found --- src/lib/fetch.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index e871f366a6..6caac49985 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -592,6 +592,11 @@ async function fetchEvents( let events = event.eventData.map((eventData) => { return [eventData.index, ...eventData.data]; }); + // If there is only one event type, SnarkyJS expects that the events array has + // no index and can directly be matched to the event type. If this is the case, we remove the index. + if (events.every((event) => event[0] === '0')) { + events = events.map((event) => event.slice(1)); + } return { events, blockHeight: UInt32.from(event.blockInfo.height), From fae7f5713a206d915d8e922c0382eaa361571114 Mon Sep 17 00:00:00 2001 From: Martin Minkov Date: Fri, 10 Mar 2023 08:01:13 -0800 Subject: [PATCH 28/28] Remove index from events --- src/lib/fetch.ts | 11 ++--------- src/lib/zkapp.ts | 3 ++- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 6caac49985..cd9c9c8d8b 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -521,7 +521,6 @@ const getEventsQuery = ( status } eventData { - index data } } @@ -589,14 +588,8 @@ async function fetchEvents( } return fetchedEvents.map((event) => { - let events = event.eventData.map((eventData) => { - return [eventData.index, ...eventData.data]; - }); - // If there is only one event type, SnarkyJS expects that the events array has - // no index and can directly be matched to the event type. If this is the case, we remove the index. - if (events.every((event) => event[0] === '0')) { - events = events.map((event) => event.slice(1)); - } + let events = event.eventData.map((eventData) => eventData.data); + return { events, blockHeight: UInt32.from(event.blockInfo.height), diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 4d2334a1d7..f1f8fdb2b9 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -1082,8 +1082,9 @@ super.init(); }) .map((event) => { return event.events.map((eventData) => { + let { events, ...rest } = event; return { - ...event, + ...rest, event: eventData, }; });