From 3f0b54aed8f6ae34fee5e5a13743aa51e8465949 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Fri, 6 Sep 2024 22:02:43 +0200 Subject: [PATCH 01/29] add new routing for getEvents --- backend/src/routes.ts | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 8e61a2a9..1e11f2c0 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -1,8 +1,25 @@ -import { Router } from "express"; -import { submitProposal } from "./ipfs"; +import { Router } from 'express'; +import { submitProposal } from './ipfs'; +import { getVestingEvents } from './starknet'; const router = Router(); -router.post("/submit", submitProposal); +// Existing route for submitting proposals +router.post('/submit', submitProposal); -export default router; + +// New route for fetching vesting events +router.get('/vesting-events/:address', async (req, res) => { + const { address } = req.params; + + console.log(`Received request for vesting events with address ID: ${address}`); + + try { + const events = await getVestingEvents(address); + res.json(events); + } catch (error) { + console.error('Error fetching vesting events:', error); + res.status(500).json({ error: `Error fetching events: ${error}` }); + } +}); +export default router; \ No newline at end of file From 58dff7540288a31be96dd472eef6754071813393 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Fri, 6 Sep 2024 22:03:04 +0200 Subject: [PATCH 02/29] add new logic for getEvents to return vesting --- backend/src/starknet.ts | 84 +++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 13792331..2d8f4987 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -1,24 +1,86 @@ import { StarknetIdNavigator } from "starknetid.js"; -import { RpcProvider, constants } from "starknet"; +import {RpcProvider, constants, starknetId} from "starknet"; import * as dotenv from "dotenv"; dotenv.config(); const NODE_URL: string = process.env.NODE_URL || ""; +const CHAIN_ID: string = process.env.NETWORK === 'mainnet' + ? constants.StarknetChainId.SN_MAIN + : constants.StarknetChainId.SN_SEPOLIA; -export const getStarknetId = async ( - address: string -): Promise => { - const provider = new RpcProvider({ - nodeUrl: NODE_URL, - chainId: constants.StarknetChainId.SN_MAIN - }); +const provider = new RpcProvider({ + nodeUrl: NODE_URL, + chainId: CHAIN_ID, +}); - const starknetIdNavigator = new StarknetIdNavigator( +const starknetIdNavigator = new StarknetIdNavigator( provider, - constants.StarknetChainId.SN_MAIN - ); + CHAIN_ID, +); + +export const getVestingEvents = async (address: string) => { + try { + const eventFilter = { + from_block: { block_number: 0 }, + chunk_size: 1000, + address: address, + key: ['Vesting'], + }; + + const events = await provider.getEvents(eventFilter); + + // Check if events are empty and return an empty array + if (!events.events || events.events.length === 0) { + return []; + } + + const now = Math.floor(Date.now() / 1000); // Get current timestamp in seconds + const results = events.events.reduce((acc: any[], event: any) => { + try { + // VEST: {grantee, timestamp, amount} + const grantee = event.data[0]; // Grantee (index 0) + const timestamp = parseInt(event.data[1]); // Timestamp (index 1) + const amount = parseInt(event.data[2]); // Amount (index 2) + + // Processing based on the timestamp + if (timestamp < now) { + acc.push({ + grantee: grantee, + amount: amount, + is_claimable: false + }); + } else if (timestamp > now && amount) { + acc.push({ + grantee: grantee, + amount: 0, + is_claimable: false + }); + } else { + acc.push({ + grantee: grantee, + amount: amount, + is_claimable: true + }); + } + } catch (error) { + // Log the error and continue with the next event + console.error('Error processing event, skipping this event:', error); + } + return acc; + }, []); + + return results; + } catch (error) { + console.error('Error in getVestingEvents:', error); + throw new Error(`Error fetching events: ${error}`); + } +}; + +export const getStarknetId = async ( + address: string +): Promise => { try { const domain = await starknetIdNavigator.getStarkName(address); const id = await starknetIdNavigator.getStarknetId(domain); From 9970767bb4fc1316e129e4fc545f8f3edd957294 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Fri, 6 Sep 2024 22:03:40 +0200 Subject: [PATCH 03/29] add env.example file --- backend/.env.example | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 backend/.env.example diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 00000000..51b2fc92 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,5 @@ +# Env variables +NODE_URL=# +# If network == `mainnet` it will use StarknetChainId.SN_MAIN +# otherwise it will use by default StarknetChainId.SN_SEPOLIA +NETWORK=# From c45474e90f7c23f26d45757dd81a9dfe439d82d9 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Fri, 6 Sep 2024 22:07:42 +0200 Subject: [PATCH 04/29] change operant --- backend/src/starknet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 2d8f4987..d0354024 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -45,13 +45,13 @@ export const getVestingEvents = async (address: string) => { const amount = parseInt(event.data[2]); // Amount (index 2) // Processing based on the timestamp - if (timestamp < now) { + if (timestamp > now) { acc.push({ grantee: grantee, amount: amount, is_claimable: false }); - } else if (timestamp > now && amount) { + } else if (timestamp < now && amount) { acc.push({ grantee: grantee, amount: 0, From 158854a7c610efa6be4db2fc3d24c7f150aa73a8 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 9 Sep 2024 10:26:40 +0300 Subject: [PATCH 05/29] refactoring --- backend/src/starknet.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index d0354024..99d0d801 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -45,23 +45,15 @@ export const getVestingEvents = async (address: string) => { const amount = parseInt(event.data[2]); // Amount (index 2) // Processing based on the timestamp - if (timestamp > now) { + if (timestamp < now && amount) { acc.push({ - grantee: grantee, amount: amount, - is_claimable: false - }); - } else if (timestamp < now && amount) { - acc.push({ - grantee: grantee, - amount: 0, - is_claimable: false + is_claimable: true }); } else { acc.push({ - grantee: grantee, amount: amount, - is_claimable: true + is_claimable: false }); } } catch (error) { From 470bf793af5cdf072b2310ea64d386402209016e Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 9 Sep 2024 10:54:49 +0300 Subject: [PATCH 06/29] add missing param --- backend/src/starknet.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 99d0d801..ae8686a7 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -48,10 +48,12 @@ export const getVestingEvents = async (address: string) => { if (timestamp < now && amount) { acc.push({ amount: amount, + timestamp: timestamp, is_claimable: true }); } else { acc.push({ + timestamp: timestamp, amount: amount, is_claimable: false }); From c8acc1614abca1bbb0b12e4603edf4d3aaf85969 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Thu, 12 Sep 2024 18:00:23 +0200 Subject: [PATCH 07/29] add caching and start_block --- backend/src/starknet.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index ae8686a7..085d195a 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -1,11 +1,12 @@ import { StarknetIdNavigator } from "starknetid.js"; -import {RpcProvider, constants, starknetId} from "starknet"; +import {RpcProvider, constants, hash} from "starknet"; import * as dotenv from "dotenv"; dotenv.config(); const NODE_URL: string = process.env.NODE_URL || ""; -const CHAIN_ID: string = process.env.NETWORK === 'mainnet' +const START_BLOCK: number = process.env.NETWORK === 'mainnet' ? 720000 : 177000; // Started block number before vesting added +const CHAIN_ID: constants.StarknetChainId = process.env.NETWORK === 'mainnet' ? constants.StarknetChainId.SN_MAIN : constants.StarknetChainId.SN_SEPOLIA; @@ -19,18 +20,25 @@ const starknetIdNavigator = new StarknetIdNavigator( CHAIN_ID, ); +const cache = new Map(); + export const getVestingEvents = async (address: string) => { + + // Check if the result is already cached + if (cache.has(address)) { + return cache.get(address); + } + try { const eventFilter = { - from_block: { block_number: 0 }, - chunk_size: 1000, + from_block: { block_number: START_BLOCK }, + chunk_size: 100, address: address, - key: ['Vesting'], + keys: [[hash.getSelectorFromName('Vesting')]], }; const events = await provider.getEvents(eventFilter); - // Check if events are empty and return an empty array if (!events.events || events.events.length === 0) { return []; } @@ -39,32 +47,31 @@ export const getVestingEvents = async (address: string) => { const results = events.events.reduce((acc: any[], event: any) => { try { - // VEST: {grantee, timestamp, amount} - const grantee = event.data[0]; // Grantee (index 0) const timestamp = parseInt(event.data[1]); // Timestamp (index 1) const amount = parseInt(event.data[2]); // Amount (index 2) - // Processing based on the timestamp if (timestamp < now && amount) { acc.push({ amount: amount, timestamp: timestamp, - is_claimable: true + is_claimable: true, }); } else { acc.push({ timestamp: timestamp, amount: amount, - is_claimable: false + is_claimable: false, }); } } catch (error) { - // Log the error and continue with the next event console.error('Error processing event, skipping this event:', error); } return acc; }, []); + // Store the result in the cache + cache.set(address, results); + return results; } catch (error) { console.error('Error in getVestingEvents:', error); From 01eabb63f76a2b5aedc381b1632c5dba39d5e16a Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Thu, 12 Sep 2024 18:00:36 +0200 Subject: [PATCH 08/29] update package-lock.json --- backend/package-lock.json | 167 ++++++++++++++++++++++++++++++-------- 1 file changed, 134 insertions(+), 33 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 3ec69bb4..7d2e34fc 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "backend", "dependencies": { "dotenv": "^16.4.5", "express": "^4.19.2", @@ -2042,9 +2043,10 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -2054,7 +2056,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -2068,6 +2070,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -2075,7 +2078,23 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -2161,6 +2180,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2365,6 +2385,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2496,6 +2517,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2504,6 +2526,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -2687,6 +2710,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2740,36 +2764,37 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", + "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.2.0", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -2788,6 +2813,15 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2909,6 +2943,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3125,6 +3160,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -3149,6 +3185,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -4027,14 +4064,19 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -4051,10 +4093,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -4067,6 +4110,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -4343,9 +4387,10 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "license": "MIT" }, "node_modules/picocolors": { "version": "1.0.1", @@ -4470,6 +4515,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4478,6 +4524,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -4608,7 +4655,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/semver": { "version": "7.6.2", @@ -4623,9 +4671,10 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -4649,6 +4698,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -4656,17 +4706,20 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", + "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "license": "MIT", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -4677,6 +4730,51 @@ "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -4696,7 +4794,8 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -5040,6 +5139,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -5140,6 +5240,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" From 9c0840deceb57716495da611835daab498ca6ef0 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Thu, 12 Sep 2024 18:36:57 +0200 Subject: [PATCH 09/29] add new logic --- backend/src/starknet.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 085d195a..8cf92f3f 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -23,6 +23,8 @@ const starknetIdNavigator = new StarknetIdNavigator( const cache = new Map(); export const getVestingEvents = async (address: string) => { + const vesting_milestone_add_selector = hash.getSelectorFromName('VestingMilestoneAdded') + const vested_selector = hash.getSelectorFromName('Vested') // Check if the result is already cached if (cache.has(address)) { @@ -34,7 +36,7 @@ export const getVestingEvents = async (address: string) => { from_block: { block_number: START_BLOCK }, chunk_size: 100, address: address, - keys: [[hash.getSelectorFromName('Vesting')]], + keys: [[hash.getSelectorFromName('VestingEvent')]], }; const events = await provider.getEvents(eventFilter); @@ -50,17 +52,23 @@ export const getVestingEvents = async (address: string) => { const timestamp = parseInt(event.data[1]); // Timestamp (index 1) const amount = parseInt(event.data[2]); // Amount (index 2) - if (timestamp < now && amount) { + if (timestamp < now && event.keys.includes(vesting_milestone_add_selector)) { acc.push({ amount: amount, - timestamp: timestamp, is_claimable: true, + is_claimed: false, + }); + } else if (event.keys.includes(vested_selector)) { + acc.push({ + amount: amount, + is_claimable: false, + is_claimed: true, }); } else { acc.push({ - timestamp: timestamp, amount: amount, is_claimable: false, + is_claimed: false, }); } } catch (error) { From d443cc20e33e4d6c27d79acf2c505983fd1c8139 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Thu, 12 Sep 2024 18:39:51 +0200 Subject: [PATCH 10/29] add annotation for types --- backend/src/starknet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 8cf92f3f..dbd3d7e0 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -20,7 +20,7 @@ const starknetIdNavigator = new StarknetIdNavigator( CHAIN_ID, ); -const cache = new Map(); +const cache = new Map(); export const getVestingEvents = async (address: string) => { const vesting_milestone_add_selector = hash.getSelectorFromName('VestingMilestoneAdded') From d58de0088778332e7404df615a196c8739283768 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Sat, 21 Sep 2024 20:50:41 +0200 Subject: [PATCH 11/29] update routes --- backend/src/routes.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 1e11f2c0..f6b14129 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -1,25 +1,32 @@ import { Router } from 'express'; import { submitProposal } from './ipfs'; import { getVestingEvents } from './starknet'; +import { Request, Response } from 'express'; const router = Router(); // Existing route for submitting proposals router.post('/submit', submitProposal); +// Vesting events for user +router.get('/vesting-events', async (req: Request, res: Response) => { + const { contract, address } = req.query; -// New route for fetching vesting events -router.get('/vesting-events/:address', async (req, res) => { - const { address } = req.params; + // Ensure that contract and address are strings, not arrays or other types + if (typeof contract !== 'string' || typeof address !== 'string') { + return res.status(400).json({ error: 'Both contract and address must be valid strings.' }); + } - console.log(`Received request for vesting events with address ID: ${address}`); + console.log(`Received request for vesting events with contract: ${contract} and address: ${address}`); try { - const events = await getVestingEvents(address); + const events = await getVestingEvents(contract, address); res.json(events); } catch (error) { console.error('Error fetching vesting events:', error); - res.status(500).json({ error: `Error fetching events: ${error}` }); + res.status(500).json({ error: 'Error fetching vesting events.' }); } }); -export default router; \ No newline at end of file + + +export default router; From 9ef5b1ed90c9993935e6101d17dea5d0de43bd7d Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Sat, 21 Sep 2024 20:51:08 +0200 Subject: [PATCH 12/29] rework get vesting event --- backend/src/starknet.ts | 79 ++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index dbd3d7e0..45306d31 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -5,7 +5,7 @@ import * as dotenv from "dotenv"; dotenv.config(); const NODE_URL: string = process.env.NODE_URL || ""; -const START_BLOCK: number = process.env.NETWORK === 'mainnet' ? 720000 : 177000; // Started block number before vesting added +const START_BLOCK: number = process.env.NETWORK === 'mainnet' ? 720000 : 196000; // Started block number before vesting added const CHAIN_ID: constants.StarknetChainId = process.env.NETWORK === 'mainnet' ? constants.StarknetChainId.SN_MAIN : constants.StarknetChainId.SN_SEPOLIA; @@ -22,54 +22,64 @@ const starknetIdNavigator = new StarknetIdNavigator( const cache = new Map(); -export const getVestingEvents = async (address: string) => { - const vesting_milestone_add_selector = hash.getSelectorFromName('VestingMilestoneAdded') - const vested_selector = hash.getSelectorFromName('Vested') +export const getVestingEvents = async (contract: string, address: string): Promise => { + const vesting_milestone_add_selector = hash.getSelectorFromName('VestingMilestoneAdded'); + const vested_selector = hash.getSelectorFromName('Vested'); + + // Cache key using both contract and address + const cacheKey = `${contract}-${address}`; // Check if the result is already cached - if (cache.has(address)) { - return cache.get(address); + if (cache.has(cacheKey)) { + return cache.get(cacheKey); } try { const eventFilter = { from_block: { block_number: START_BLOCK }, chunk_size: 100, - address: address, + address: contract, keys: [[hash.getSelectorFromName('VestingEvent')]], }; const events = await provider.getEvents(eventFilter); if (!events.events || events.events.length === 0) { + console.log(`No events found for contract: ${contract} and address: ${address}`); return []; } - const now = Math.floor(Date.now() / 1000); // Get current timestamp in seconds + const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds const results = events.events.reduce((acc: any[], event: any) => { try { - const timestamp = parseInt(event.data[1]); // Timestamp (index 1) - const amount = parseInt(event.data[2]); // Amount (index 2) - - if (timestamp < now && event.keys.includes(vesting_milestone_add_selector)) { - acc.push({ - amount: amount, - is_claimable: true, - is_claimed: false, - }); - } else if (event.keys.includes(vested_selector)) { - acc.push({ - amount: amount, - is_claimable: false, - is_claimed: true, - }); - } else { - acc.push({ - amount: amount, - is_claimable: false, - is_claimed: false, - }); + const grantee = event.data[0]; // Grantee (index 0) + const timestamp = Number(BigInt(event.data[1])); // Timestamp (index 1) + let amount = Number(BigInt(event.data[2])); // Amount as BigInt (index 2) + + // Apply scaling if amount > 0 + if (amount > 0) { + amount = amount / (10 ** 18); + } + + // Process only if the grantee matches the address + if (grantee === address) { + const isVestingMilestone = event.keys.includes(vesting_milestone_add_selector); + const isVested = event.keys.includes(vested_selector); + + if (isVestingMilestone) { + acc.push({ + amount: amount, + is_claimable: now >= timestamp, + is_claimed: false, + }); + } else if (isVested) { + acc.push({ + amount: amount, + is_claimable: false, + is_claimed: true, + }); + } } } catch (error) { console.error('Error processing event, skipping this event:', error); @@ -77,13 +87,18 @@ export const getVestingEvents = async (address: string) => { return acc; }, []); - // Store the result in the cache - cache.set(address, results); + // Cache the result for future requests + cache.set(cacheKey, results); return results; } catch (error) { console.error('Error in getVestingEvents:', error); - throw new Error(`Error fetching events: ${error}`); + + if (error instanceof Error) { + throw new Error(`Error fetching events: ${error.message}`); + } else { + throw new Error('Unknown error occurred while fetching events'); + } } }; From 00323336930dbf38bc8bd55d43aa0f8e1be987f2 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Sat, 21 Sep 2024 20:51:20 +0200 Subject: [PATCH 13/29] change governance contract --- frontend/src/lib/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts index 469a5ce1..5bd1dfa0 100644 --- a/frontend/src/lib/config.ts +++ b/frontend/src/lib/config.ts @@ -1,6 +1,6 @@ export const CONTRACT_ADDR = // mainnet: "0x001405ab78ab6ec90fba09e6116f373cda53b0ba557789a4578d8c1ec374ba0f"; - "0x02ba6e05d06a9e7398ae71c330b018415f93710c58e99fb04fa761f97712ec76"; // sepolia with treasury + "0x0618f5457aca4d3c9e46ad07bae6d051234e308c39eb60d73bc461f4f8c1d3da"; // sepolia with treasury export const VOTING_TOKEN_CONTRACT = "0x4ff1af47bb9659aa83bbd33e13c25e8fb1b5ecf8359320251f03e1440e8890a"; export const FLOATING_TOKEN_CONTRACT = "0x31868056874ad7629055ddd00eb0931cb92167851702abf6b441cb8ea02d02b"; From bbe9c7dd2b0437a5fb265560c3bfe822796f56c5 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Sat, 21 Sep 2024 21:49:28 +0200 Subject: [PATCH 14/29] update logic to fetch all events --- backend/src/starknet.ts | 112 +++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 45306d31..91909e16 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -34,63 +34,79 @@ export const getVestingEvents = async (contract: string, address: string): Promi return cache.get(cacheKey); } - try { - const eventFilter = { - from_block: { block_number: START_BLOCK }, - chunk_size: 100, - address: contract, - keys: [[hash.getSelectorFromName('VestingEvent')]], - }; - - const events = await provider.getEvents(eventFilter); - - if (!events.events || events.events.length === 0) { - console.log(`No events found for contract: ${contract} and address: ${address}`); - return []; - } + const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds + let fromBlock = START_BLOCK; + const chunkSize = 100; + let allEvents: any[] = []; - const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds + try { + while (true) { + const eventFilter = { + from_block: { block_number: fromBlock }, + chunk_size: chunkSize, + address: contract, + keys: [[hash.getSelectorFromName('VestingEvent')]], + }; + + const events = await provider.getEvents(eventFilter); + + if (!events.events || events.events.length === 0) { + // Exit the loop if no more events are found + break; + } - const results = events.events.reduce((acc: any[], event: any) => { - try { - const grantee = event.data[0]; // Grantee (index 0) - const timestamp = Number(BigInt(event.data[1])); // Timestamp (index 1) - let amount = Number(BigInt(event.data[2])); // Amount as BigInt (index 2) + const newEvents = events.events.reduce((acc: any[], event: any) => { + try { + const grantee = event.data[0]; // Grantee (index 0) + const timestamp = Number(BigInt(event.data[1])); // Timestamp (index 1) + let amount = Number(BigInt(event.data[2])); // Amount as BigInt (index 2) - // Apply scaling if amount > 0 - if (amount > 0) { - amount = amount / (10 ** 18); - } + // Apply scaling if amount > 0 + if (amount > 0) { + amount = amount / (10 ** 18); + } - // Process only if the grantee matches the address - if (grantee === address) { - const isVestingMilestone = event.keys.includes(vesting_milestone_add_selector); - const isVested = event.keys.includes(vested_selector); - - if (isVestingMilestone) { - acc.push({ - amount: amount, - is_claimable: now >= timestamp, - is_claimed: false, - }); - } else if (isVested) { - acc.push({ - amount: amount, - is_claimable: false, - is_claimed: true, - }); + // Process only if the grantee matches the address + if (grantee === address) { + const isVestingMilestone = event.keys.includes(vesting_milestone_add_selector); + const isVested = event.keys.includes(vested_selector); + + if (isVestingMilestone) { + acc.push({ + amount: amount, + timestamp: timestamp, + is_claimable: now >= timestamp, + is_claimed: false, + }); + } else if (isVested) { + acc.push({ + amount: amount, + is_claimable: false, + timestamp: timestamp, + is_claimed: true, + }); + } } + } catch (error) { + console.error('Error processing event, skipping this event:', error); } - } catch (error) { - console.error('Error processing event, skipping this event:', error); - } - return acc; - }, []); + return acc; + }, []); + + // Add new events to the accumulated list + allEvents = [...allEvents, ...newEvents]; + + // Update `fromBlock` to the next block after the last fetched block + const lastEventBlock = events.events[events.events.length - 1].block_number; + fromBlock = lastEventBlock + 1; // Move to the next block + + await new Promise(resolve => setTimeout(resolve, 100)); + } // Cache the result for future requests - cache.set(cacheKey, results); + cache.set(cacheKey, allEvents); - return results; + return allEvents; } catch (error) { console.error('Error in getVestingEvents:', error); From 71c09ec1734af6e8ae825e29757632fb811a36fe Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Sat, 21 Sep 2024 21:51:04 +0200 Subject: [PATCH 15/29] refactoring --- backend/src/starknet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 91909e16..124207f6 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -74,7 +74,7 @@ export const getVestingEvents = async (contract: string, address: string): Promi if (isVestingMilestone) { acc.push({ amount: amount, - timestamp: timestamp, + claimable_at: timestamp, is_claimable: now >= timestamp, is_claimed: false, }); @@ -82,7 +82,7 @@ export const getVestingEvents = async (contract: string, address: string): Promi acc.push({ amount: amount, is_claimable: false, - timestamp: timestamp, + claimable_at: null, is_claimed: true, }); } From 86acad2f28a1f4dfc534e8ebec3525b8008dc92c Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 12:58:56 +0200 Subject: [PATCH 16/29] add new package --- frontend/package-lock.json | 1 + frontend/package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 827a07eb..2e1e0d71 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@starknet-react/core": "^2.9.0", + "@tanstack/react-query": "^5.0.1", "@typescript-eslint/eslint-plugin": "^6.10.0", "axios": "^1.7.2", "next": "^14.0.1", diff --git a/frontend/package.json b/frontend/package.json index 14ff7f7b..3d5e4116 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,7 +17,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", - "tailwindcss": "^3.3.5" + "tailwindcss": "^3.3.5", + "@tanstack/react-query": "^5.0.1" }, "devDependencies": { "@types/react": "^18.0.27", From 98bba223705d00e904c5af22b6c92a6ef29122fc Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 12:59:34 +0200 Subject: [PATCH 17/29] add new VestingTable --- frontend/src/App.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7bfb1ca9..c0fb0175 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,16 +1,14 @@ import React from "react"; -// import { useBlock } from "@starknet-react/core"; import Header from "./components/Header"; import { useContractRead, useAccount } from "@starknet-react/core"; -//import Tokens from "./helpers/tokens"; import { abi } from "./lib/abi"; import Proposal from "./components/Proposal"; import { CONTRACT_ADDR } from "./lib/config"; -// import { useAccount } from "@starknet-react/core"; import SubmitProposalModal from "./components/SubmitProposalModal"; import TreasuryStatus from "./components/TreasuryStatus"; import VotingPower from "./components/staking/VotingPower"; -import StatusTransfer from './components/StatusTransfer' +import StatusTransfer from './components/StatusTransfer'; +import VestingTable from './components/VestingTable'; function App() { const [isModalOpen, setIsModalOpen] = React.useState(false); @@ -31,7 +29,6 @@ function App() { return
{error?.message}
; } - // Display the proposals return (
@@ -72,6 +69,8 @@ function App() { + + {address && } {address && }
); From cb2245de527965dd702e1f701600740e19b7c576 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 13:00:10 +0200 Subject: [PATCH 18/29] add VestingTable --- frontend/src/components/VestingTable.tsx | 112 +++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 frontend/src/components/VestingTable.tsx diff --git a/frontend/src/components/VestingTable.tsx b/frontend/src/components/VestingTable.tsx new file mode 100644 index 00000000..8874a4f4 --- /dev/null +++ b/frontend/src/components/VestingTable.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react'; +import { CONTRACT_ADDR } from '../lib/config'; +import axios from 'axios'; +import { useAccount } from '@starknet-react/core'; +import { useQuery } from '@tanstack/react-query'; + +interface VestingEvent { + amount: number; + claimable_at: number | null; + is_claimable: boolean; + is_claimed: boolean; +} + +const ITEMS_PER_PAGE = 10; // Items per page + +const VestingTable: React.FC = () => { + const { address } = useAccount(); + const [currentPage, setCurrentPage] = useState(1); // Track the current page + + const { data: events = [], isLoading, error } = useQuery({ + queryKey: ['vesting-events', address], + queryFn: async () => { + if (!address) { + return []; + } + const response = await axios.get('/api/vesting-events', { + params: { + contract: CONTRACT_ADDR, + address: address, + } + }); + return response.data; + }, + enabled: !!address, // Only fetch if the address is available + retry: false, // Disable retries for this query + }); + + // Pagination logic + const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; + const paginatedEvents = events.slice(startIndex, startIndex + ITEMS_PER_PAGE); + const totalPages = Math.ceil(events.length / ITEMS_PER_PAGE); + + const handleNextPage = () => { + if (currentPage < totalPages) { + setCurrentPage(currentPage + 1); + } + }; + + const handlePreviousPage = () => { + if (currentPage > 1) { + setCurrentPage(currentPage - 1); + } + }; + + if (isLoading) { + return
Loading...
; + } + + if (error) { + return

{(error as Error).message || 'Failed to load vesting events.'}

; + } + + return ( +
+ {/* Title */} +
Vesting Event
+ + {/* Table */} +
+
+
Amount
+
Claimable At
+
Is Claimable
+
Is Claimed
+
+ {paginatedEvents.map((event: VestingEvent, index: number) => ( +
+
{event.amount}
+
{event.claimable_at ? new Date(event.claimable_at * 1000).toLocaleString() : 'N/A'}
+
{event.is_claimable ? 'Yes' : 'No'}
+
{event.is_claimed ? 'Yes' : 'No'}
+
+ ))} +
+ + {/* Pagination Controls */} +
+ + + + Page {currentPage} of {totalPages} + + + +
+
+ ); +}; + +export default VestingTable; \ No newline at end of file From cd81d40c797ebf607914e92a1ccfc0352d805ece Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 13:27:02 +0200 Subject: [PATCH 19/29] add basic url --- frontend/src/components/VestingTable.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/VestingTable.tsx b/frontend/src/components/VestingTable.tsx index 8874a4f4..bd88a631 100644 --- a/frontend/src/components/VestingTable.tsx +++ b/frontend/src/components/VestingTable.tsx @@ -3,6 +3,7 @@ import { CONTRACT_ADDR } from '../lib/config'; import axios from 'axios'; import { useAccount } from '@starknet-react/core'; import { useQuery } from '@tanstack/react-query'; +import { BASE_API_URL } from "../lib/config"; interface VestingEvent { amount: number; @@ -23,7 +24,7 @@ const VestingTable: React.FC = () => { if (!address) { return []; } - const response = await axios.get('/api/vesting-events', { + const response = await axios.get(`${BASE_API_URL}/api/vesting-events`, { params: { contract: CONTRACT_ADDR, address: address, From d14954a5a4f1abab37eff8bf82062f3c3d5469f2 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 13:29:04 +0200 Subject: [PATCH 20/29] fix api url --- frontend/src/components/VestingTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/VestingTable.tsx b/frontend/src/components/VestingTable.tsx index bd88a631..e190b0a2 100644 --- a/frontend/src/components/VestingTable.tsx +++ b/frontend/src/components/VestingTable.tsx @@ -24,7 +24,7 @@ const VestingTable: React.FC = () => { if (!address) { return []; } - const response = await axios.get(`${BASE_API_URL}/api/vesting-events`, { + const response = await axios.get(`${BASE_API_URL}vesting-events`, { params: { contract: CONTRACT_ADDR, address: address, From 842bba54692f295a83a715db2e90efa2d2107653 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 15:46:07 +0200 Subject: [PATCH 21/29] refactoring caching --- backend/.env.example | 2 +- backend/src/starknet.ts | 42 +++++++++++++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 51b2fc92..c8a53a5e 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,5 +1,5 @@ # Env variables -NODE_URL=# +STARKNET_RPC=# # If network == `mainnet` it will use StarknetChainId.SN_MAIN # otherwise it will use by default StarknetChainId.SN_SEPOLIA NETWORK=# diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 124207f6..73f13be0 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -4,14 +4,14 @@ import * as dotenv from "dotenv"; dotenv.config(); -const NODE_URL: string = process.env.NODE_URL || ""; +const STARKNET_RPC: string = process.env.STARKNET_RPC || ""; const START_BLOCK: number = process.env.NETWORK === 'mainnet' ? 720000 : 196000; // Started block number before vesting added const CHAIN_ID: constants.StarknetChainId = process.env.NETWORK === 'mainnet' ? constants.StarknetChainId.SN_MAIN : constants.StarknetChainId.SN_SEPOLIA; const provider = new RpcProvider({ - nodeUrl: NODE_URL, + nodeUrl: STARKNET_RPC, chainId: CHAIN_ID, }); @@ -20,7 +20,24 @@ const starknetIdNavigator = new StarknetIdNavigator( CHAIN_ID, ); -const cache = new Map(); +const cache: Map = new Map(); + +const CACHE_TTL_SECONDS = 3600; // TTL for cache (60 minutes) + + +interface VestingEvent { + amount: number; // The amount vested + claimable_at: number; // The timestamp when the amount becomes claimable + is_claimable: boolean; // Whether the amount is claimable at the current time + is_claimed: boolean; // Whether the amount has already been claimed +} + +interface CachedData { + data: VestingEvent[]; // Array of events + expiry: number; // Expiry timestamp +} + + export const getVestingEvents = async (contract: string, address: string): Promise => { const vesting_milestone_add_selector = hash.getSelectorFromName('VestingMilestoneAdded'); @@ -29,12 +46,14 @@ export const getVestingEvents = async (contract: string, address: string): Promi // Cache key using both contract and address const cacheKey = `${contract}-${address}`; - // Check if the result is already cached - if (cache.has(cacheKey)) { - return cache.get(cacheKey); + // Check if the result is already cached and not expired + const cached: CachedData | undefined = cache.get(cacheKey); + const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds + + if (cached && cached.expiry > now) { + return cached.data; // Return cached data if not expired } - const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds let fromBlock = START_BLOCK; const chunkSize = 100; let allEvents: any[] = []; @@ -100,11 +119,14 @@ export const getVestingEvents = async (contract: string, address: string): Promi const lastEventBlock = events.events[events.events.length - 1].block_number; fromBlock = lastEventBlock + 1; // Move to the next block - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise(resolve => setTimeout(resolve, 100)); // Pause briefly before the next request } - // Cache the result for future requests - cache.set(cacheKey, allEvents); + // Cache the result for future requests with an expiry time + cache.set(cacheKey, { + data: allEvents, + expiry: now + CACHE_TTL_SECONDS, + } as CachedData); return allEvents; } catch (error) { From d1f2bc7916d43e3f6d33d84333e672e15eb7ef58 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 15:52:14 +0200 Subject: [PATCH 22/29] add new table style --- frontend/src/components/VestingTable.tsx | 87 +++++++++++++----------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/frontend/src/components/VestingTable.tsx b/frontend/src/components/VestingTable.tsx index e190b0a2..cc634a58 100644 --- a/frontend/src/components/VestingTable.tsx +++ b/frontend/src/components/VestingTable.tsx @@ -24,7 +24,7 @@ const VestingTable: React.FC = () => { if (!address) { return []; } - const response = await axios.get(`${BASE_API_URL}vesting-events`, { + const response = await axios.get(`${BASE_API_URL}/vesting-events`, { params: { contract: CONTRACT_ADDR, address: address, @@ -62,52 +62,61 @@ const VestingTable: React.FC = () => { } return ( -
+
{/* Title */} -
Vesting Event
+

Vesting Milestones

{/* Table */} -
-
-
Amount
-
Claimable At
-
Is Claimable
-
Is Claimed
-
+ + + + + + + + + + {paginatedEvents.map((event: VestingEvent, index: number) => ( -
-
{event.amount}
-
{event.claimable_at ? new Date(event.claimable_at * 1000).toLocaleString() : 'N/A'}
-
{event.is_claimable ? 'Yes' : 'No'}
-
{event.is_claimed ? 'Yes' : 'No'}
-
+ + + + + + ))} - + +
AmountClaimable AtIs ClaimableIs Claimed
{event.amount} + {event.claimable_at ? new Date(event.claimable_at * 1000).toLocaleString() : 'N/A'} + {event.is_claimable ? 'Yes' : 'No'}{event.is_claimed ? 'Yes' : 'No'}
{/* Pagination Controls */} -
- - - - Page {currentPage} of {totalPages} - - - -
+ {totalPages > 1 && ( +
+ + + + Page {currentPage} of {totalPages} + + + +
+ )}
); + }; -export default VestingTable; \ No newline at end of file +export default VestingTable; From 07187e0382c95a643a25281493430cd9de68023d Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 15:52:27 +0200 Subject: [PATCH 23/29] unify BASE_API_URL --- frontend/src/lib/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts index 5bd1dfa0..efe9a989 100644 --- a/frontend/src/lib/config.ts +++ b/frontend/src/lib/config.ts @@ -16,7 +16,7 @@ export const formatIpfsHash = (hash: string) => { return hash.replace(/,/g, ""); }; -export const BASE_API_URL = "https://konoha.vote/discussion-api/api/" +export const BASE_API_URL = "https://konoha.vote/discussion-api/api" export const NETWORK: string = "sepolia"; export const ETH_ADDRESS = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" From b9915ccf2b5140f64f54e2fb5747efde4523f86b Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 16:57:41 +0200 Subject: [PATCH 24/29] add do .. while --- backend/src/starknet.ts | 92 +++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 73f13be0..52c7ec69 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -59,7 +59,9 @@ export const getVestingEvents = async (contract: string, address: string): Promi let allEvents: any[] = []; try { - while (true) { + let hasMoreEvents = true; + + do { const eventFilter = { from_block: { block_number: fromBlock }, chunk_size: chunkSize, @@ -71,56 +73,56 @@ export const getVestingEvents = async (contract: string, address: string): Promi if (!events.events || events.events.length === 0) { // Exit the loop if no more events are found - break; - } - - const newEvents = events.events.reduce((acc: any[], event: any) => { - try { - const grantee = event.data[0]; // Grantee (index 0) - const timestamp = Number(BigInt(event.data[1])); // Timestamp (index 1) - let amount = Number(BigInt(event.data[2])); // Amount as BigInt (index 2) - - // Apply scaling if amount > 0 - if (amount > 0) { - amount = amount / (10 ** 18); - } + hasMoreEvents = false; + } else { + const newEvents = events.events.reduce((acc: any[], event: any) => { + try { + const grantee = event.data[0]; // Grantee (index 0) + const timestamp = Number(BigInt(event.data[1])); // Timestamp (index 1) + let amount = Number(BigInt(event.data[2])); // Amount as BigInt (index 2) + + // Apply scaling if amount > 0 + if (amount > 0) { + amount = amount / (10 ** 18); + } - // Process only if the grantee matches the address - if (grantee === address) { - const isVestingMilestone = event.keys.includes(vesting_milestone_add_selector); - const isVested = event.keys.includes(vested_selector); - - if (isVestingMilestone) { - acc.push({ - amount: amount, - claimable_at: timestamp, - is_claimable: now >= timestamp, - is_claimed: false, - }); - } else if (isVested) { - acc.push({ - amount: amount, - is_claimable: false, - claimable_at: null, - is_claimed: true, - }); + // Process only if the grantee matches the address + if (grantee === address) { + const isVestingMilestone = event.keys.includes(vesting_milestone_add_selector); + const isVested = event.keys.includes(vested_selector); + + if (isVestingMilestone) { + acc.push({ + amount: amount, + claimable_at: timestamp, + is_claimable: now >= timestamp, + is_claimed: false, + }); + } else if (isVested) { + acc.push({ + amount: amount, + is_claimable: false, + claimable_at: null, + is_claimed: true, + }); + } } + } catch (error) { + console.error('Error processing event, skipping this event:', error); } - } catch (error) { - console.error('Error processing event, skipping this event:', error); - } - return acc; - }, []); + return acc; + }, []); - // Add new events to the accumulated list - allEvents = [...allEvents, ...newEvents]; + // Add new events to the accumulated list + allEvents = [...allEvents, ...newEvents]; - // Update `fromBlock` to the next block after the last fetched block - const lastEventBlock = events.events[events.events.length - 1].block_number; - fromBlock = lastEventBlock + 1; // Move to the next block + // Update `fromBlock` to the next block after the last fetched block + const lastEventBlock = events.events[events.events.length - 1].block_number; + fromBlock = lastEventBlock + 1; // Move to the next block - await new Promise(resolve => setTimeout(resolve, 100)); // Pause briefly before the next request - } + await new Promise(resolve => setTimeout(resolve, 100)); // Pause briefly before the next request + } + } while (hasMoreEvents); // Cache the result for future requests with an expiry time cache.set(cacheKey, { From d40b022be38befa8ad1995e0066d5be4f9fdb45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sojka?= Date: Mon, 23 Sep 2024 19:15:43 +0200 Subject: [PATCH 25/29] Refactor backend for readability --- backend/src/events.ts | 54 +++++++++++++++ backend/src/starknet.ts | 146 +++++++++++++--------------------------- 2 files changed, 99 insertions(+), 101 deletions(-) create mode 100644 backend/src/events.ts diff --git a/backend/src/events.ts b/backend/src/events.ts new file mode 100644 index 00000000..5546b527 --- /dev/null +++ b/backend/src/events.ts @@ -0,0 +1,54 @@ +import { RpcProvider, hash } from "starknet"; + + +interface CacheEntry { + events: any[], + lastBlock: number +} + +const cache: Map = new Map(); + +export const getRawVestingEvents = async (contract: string, provider: RpcProvider, fromBlock: number): Promise => { + const latestBlockPromise = provider.getBlockLatestAccepted(); + let cached = getCachedVestingEvents(contract, fromBlock); + + const newlyFetched = await fetchVestingEvents(contract, provider, cached.lastBlock); + console.log("fetched ", newlyFetched.length, " events") + const events = [...cached.events, ...newlyFetched]; + const cacheKey = { contract, fromBlock }; + console.log('saving ', newlyFetched.length, ' newly fetched to cache'); + cache.set(`${contract}-${fromBlock}`, { events, lastBlock: (await latestBlockPromise).block_number }); + return events; +}; + +const getCachedVestingEvents = (contract: string, fromBlock: number): CacheEntry => { + const cachedEntry = cache.get(`${contract}-${fromBlock}`) + if (cachedEntry === undefined) { + return { lastBlock: fromBlock, events: [] } + }; + let nextEntry = getCachedVestingEvents(contract, cachedEntry.lastBlock); + return { lastBlock: nextEntry.lastBlock, events: [...cachedEntry.events, ...nextEntry.events] } +} + +const fetchVestingEvents = async (contract: string, provider: RpcProvider, fromBlock: number, continuation_token?: string) => { + const eventFilter = { + from_block: { block_number: fromBlock }, + chunk_size: 100, + address: contract, + keys: [[hash.getSelectorFromName('VestingEvent')]], + ...(continuation_token && { continuation_token }) + }; + + const events = await provider.getEvents(eventFilter); + + let res = events.events + + if (events.continuation_token) { // means we can continue, more to fetch + console.debug('continuing', events.continuation_token); + console.debug(res.length) + const nextEvents = await fetchVestingEvents(contract, provider, fromBlock, events.continuation_token); + res = [...res, ...nextEvents]; + } + + return res; +} \ No newline at end of file diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 52c7ec69..171f6a89 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -1,14 +1,15 @@ import { StarknetIdNavigator } from "starknetid.js"; -import {RpcProvider, constants, hash} from "starknet"; +import { RpcProvider, constants, hash } from "starknet"; import * as dotenv from "dotenv"; +import { getRawVestingEvents } from "./events"; dotenv.config(); const STARKNET_RPC: string = process.env.STARKNET_RPC || ""; const START_BLOCK: number = process.env.NETWORK === 'mainnet' ? 720000 : 196000; // Started block number before vesting added const CHAIN_ID: constants.StarknetChainId = process.env.NETWORK === 'mainnet' - ? constants.StarknetChainId.SN_MAIN - : constants.StarknetChainId.SN_SEPOLIA; + ? constants.StarknetChainId.SN_MAIN + : constants.StarknetChainId.SN_SEPOLIA; const provider = new RpcProvider({ nodeUrl: STARKNET_RPC, @@ -16,121 +17,64 @@ const provider = new RpcProvider({ }); const starknetIdNavigator = new StarknetIdNavigator( - provider, - CHAIN_ID, + provider, + CHAIN_ID, ); -const cache: Map = new Map(); - -const CACHE_TTL_SECONDS = 3600; // TTL for cache (60 minutes) - interface VestingEvent { amount: number; // The amount vested - claimable_at: number; // The timestamp when the amount becomes claimable + claimable_at: number | null; // The timestamp when the amount becomes claimable is_claimable: boolean; // Whether the amount is claimable at the current time is_claimed: boolean; // Whether the amount has already been claimed } -interface CachedData { - data: VestingEvent[]; // Array of events - expiry: number; // Expiry timestamp -} - - - export const getVestingEvents = async (contract: string, address: string): Promise => { const vesting_milestone_add_selector = hash.getSelectorFromName('VestingMilestoneAdded'); const vested_selector = hash.getSelectorFromName('Vested'); - // Cache key using both contract and address - const cacheKey = `${contract}-${address}`; - - // Check if the result is already cached and not expired - const cached: CachedData | undefined = cache.get(cacheKey); - const now = Math.floor(Date.now() / 1000); // Current timestamp in seconds - - if (cached && cached.expiry > now) { - return cached.data; // Return cached data if not expired - } - - let fromBlock = START_BLOCK; - const chunkSize = 100; - let allEvents: any[] = []; + const rawEvents = await getRawVestingEvents(contract, provider, START_BLOCK) try { - let hasMoreEvents = true; - - do { - const eventFilter = { - from_block: { block_number: fromBlock }, - chunk_size: chunkSize, - address: contract, - keys: [[hash.getSelectorFromName('VestingEvent')]], - }; - - const events = await provider.getEvents(eventFilter); - - if (!events.events || events.events.length === 0) { - // Exit the loop if no more events are found - hasMoreEvents = false; - } else { - const newEvents = events.events.reduce((acc: any[], event: any) => { - try { - const grantee = event.data[0]; // Grantee (index 0) - const timestamp = Number(BigInt(event.data[1])); // Timestamp (index 1) - let amount = Number(BigInt(event.data[2])); // Amount as BigInt (index 2) - - // Apply scaling if amount > 0 - if (amount > 0) { - amount = amount / (10 ** 18); - } - - // Process only if the grantee matches the address - if (grantee === address) { - const isVestingMilestone = event.keys.includes(vesting_milestone_add_selector); - const isVested = event.keys.includes(vested_selector); - - if (isVestingMilestone) { - acc.push({ - amount: amount, - claimable_at: timestamp, - is_claimable: now >= timestamp, - is_claimed: false, - }); - } else if (isVested) { - acc.push({ - amount: amount, - is_claimable: false, - claimable_at: null, - is_claimed: true, - }); - } - } - } catch (error) { - console.error('Error processing event, skipping this event:', error); + const events = rawEvents.reduce((acc: VestingEvent[], event: any) => { + try { + const grantee = event.data[0]; // Grantee (index 0) + const timestamp = Number(BigInt(event.data[1])); // Timestamp (index 1) + let amount = Number(BigInt(event.data[2])); // Amount as BigInt (index 2) + + // Apply scaling if amount > 0 + if (amount > 0) { + amount = amount / (10 ** 18); + } + + // Process only if the grantee matches the address + if (grantee === address) { + const isVestingMilestone = event.keys.includes(vesting_milestone_add_selector); + const isVested = event.keys.includes(vested_selector); + + if (isVestingMilestone) { + acc.push({ + amount: amount, + claimable_at: timestamp, + is_claimable: Date.now() >= timestamp, + is_claimed: false, + }); + } else if (isVested) { + acc.push({ + amount: amount, + is_claimable: false, + claimable_at: null, + is_claimed: true, + }); } - return acc; - }, []); - - // Add new events to the accumulated list - allEvents = [...allEvents, ...newEvents]; - - // Update `fromBlock` to the next block after the last fetched block - const lastEventBlock = events.events[events.events.length - 1].block_number; - fromBlock = lastEventBlock + 1; // Move to the next block - - await new Promise(resolve => setTimeout(resolve, 100)); // Pause briefly before the next request + } + } catch (error) { + console.error('Error processing event, skipping this event:', error); } - } while (hasMoreEvents); - - // Cache the result for future requests with an expiry time - cache.set(cacheKey, { - data: allEvents, - expiry: now + CACHE_TTL_SECONDS, - } as CachedData); + return acc; + }, []); - return allEvents; + return events; } catch (error) { console.error('Error in getVestingEvents:', error); @@ -143,7 +87,7 @@ export const getVestingEvents = async (contract: string, address: string): Promi }; export const getStarknetId = async ( - address: string + address: string ): Promise => { try { const domain = await starknetIdNavigator.getStarkName(address); From 2a46b2e514c65b4cf206ee5c581405acbf0ade5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sojka?= Date: Mon, 23 Sep 2024 19:21:58 +0200 Subject: [PATCH 26/29] Polish refactor --- backend/src/events.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/events.ts b/backend/src/events.ts index 5546b527..ff2ba30d 100644 --- a/backend/src/events.ts +++ b/backend/src/events.ts @@ -13,9 +13,7 @@ export const getRawVestingEvents = async (contract: string, provider: RpcProvide let cached = getCachedVestingEvents(contract, fromBlock); const newlyFetched = await fetchVestingEvents(contract, provider, cached.lastBlock); - console.log("fetched ", newlyFetched.length, " events") const events = [...cached.events, ...newlyFetched]; - const cacheKey = { contract, fromBlock }; console.log('saving ', newlyFetched.length, ' newly fetched to cache'); cache.set(`${contract}-${fromBlock}`, { events, lastBlock: (await latestBlockPromise).block_number }); return events; @@ -44,8 +42,6 @@ const fetchVestingEvents = async (contract: string, provider: RpcProvider, fromB let res = events.events if (events.continuation_token) { // means we can continue, more to fetch - console.debug('continuing', events.continuation_token); - console.debug(res.length) const nextEvents = await fetchVestingEvents(contract, provider, fromBlock, events.continuation_token); res = [...res, ...nextEvents]; } From d57651f815c92282434f419da935b19f67510b97 Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 20:20:12 +0200 Subject: [PATCH 27/29] remove IsVestingMilestones if it is vested --- backend/src/starknet.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index 171f6a89..eec173f0 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -33,7 +33,7 @@ export const getVestingEvents = async (contract: string, address: string): Promi const vesting_milestone_add_selector = hash.getSelectorFromName('VestingMilestoneAdded'); const vested_selector = hash.getSelectorFromName('Vested'); - const rawEvents = await getRawVestingEvents(contract, provider, START_BLOCK) + const rawEvents = await getRawVestingEvents(contract, provider, START_BLOCK); try { const events = rawEvents.reduce((acc: VestingEvent[], event: any) => { @@ -63,18 +63,30 @@ export const getVestingEvents = async (contract: string, address: string): Promi acc.push({ amount: amount, is_claimable: false, - claimable_at: null, + claimable_at: timestamp, is_claimed: true, }); } } + } catch (error) { console.error('Error processing event, skipping this event:', error); } + return acc; }, []); - return events; + // Filter out isVestingMilestone if it has a corresponding isVested event with the same claimable_at timestamp + const filteredEvents = events.filter((event, index, self) => { + if (event.is_claimable) { + const matchingVested = self.find(e => e.is_claimed && e.claimable_at === event.claimable_at); + return !matchingVested; + } + return true; + }); + + return filteredEvents; + } catch (error) { console.error('Error in getVestingEvents:', error); @@ -86,6 +98,7 @@ export const getVestingEvents = async (contract: string, address: string): Promi } }; + export const getStarknetId = async ( address: string ): Promise => { From f4099020ef69a82c451b72e605b09e63e7d4d21b Mon Sep 17 00:00:00 2001 From: djeck1432 Date: Mon, 23 Sep 2024 22:00:46 +0200 Subject: [PATCH 28/29] refactoring getVestingEvents --- backend/src/starknet.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index eec173f0..cfad614e 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -62,8 +62,8 @@ export const getVestingEvents = async (contract: string, address: string): Promi } else if (isVested) { acc.push({ amount: amount, - is_claimable: false, claimable_at: timestamp, + is_claimable: false, is_claimed: true, }); } @@ -76,16 +76,20 @@ export const getVestingEvents = async (contract: string, address: string): Promi return acc; }, []); - // Filter out isVestingMilestone if it has a corresponding isVested event with the same claimable_at timestamp - const filteredEvents = events.filter((event, index, self) => { - if (event.is_claimable) { - const matchingVested = self.find(e => e.is_claimed && e.claimable_at === event.claimable_at); - return !matchingVested; - } - return true; - }); + // Create a set of claimable_at values from vested events + const vestedClaimableAts = new Set( + events.filter(e => e.is_claimed).map(e => e.claimable_at) + ); + + // Filter events, removing is_claimable if there's a matching vested event with the same claimable_at + const filteredEvents = events.filter(event => + !event.is_claimable || !vestedClaimableAts.has(event.claimable_at) + ); + + // Sort the filtered events by claimable_at in ascending order + const sortedEvents = filteredEvents.sort((a, b) => (a.claimable_at || 0) - (b.claimable_at || 0)); - return filteredEvents; + return sortedEvents; } catch (error) { console.error('Error in getVestingEvents:', error); From b102cd62c38949700a7ed447dff982104aa6ff60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sojka?= Date: Mon, 23 Sep 2024 22:11:05 +0200 Subject: [PATCH 29/29] Enforce consistency and code quality --- backend/src/starknet.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/starknet.ts b/backend/src/starknet.ts index cfad614e..c5abcd9b 100644 --- a/backend/src/starknet.ts +++ b/backend/src/starknet.ts @@ -24,7 +24,7 @@ const starknetIdNavigator = new StarknetIdNavigator( interface VestingEvent { amount: number; // The amount vested - claimable_at: number | null; // The timestamp when the amount becomes claimable + claimable_at: number; // The timestamp when the amount becomes claimable is_claimable: boolean; // Whether the amount is claimable at the current time is_claimed: boolean; // Whether the amount has already been claimed } @@ -78,16 +78,16 @@ export const getVestingEvents = async (contract: string, address: string): Promi // Create a set of claimable_at values from vested events const vestedClaimableAts = new Set( - events.filter(e => e.is_claimed).map(e => e.claimable_at) + events.filter(e => e.is_claimed).map(e => e.claimable_at) ); // Filter events, removing is_claimable if there's a matching vested event with the same claimable_at const filteredEvents = events.filter(event => - !event.is_claimable || !vestedClaimableAts.has(event.claimable_at) + !event.is_claimable || !vestedClaimableAts.has(event.claimable_at) ); // Sort the filtered events by claimable_at in ascending order - const sortedEvents = filteredEvents.sort((a, b) => (a.claimable_at || 0) - (b.claimable_at || 0)); + const sortedEvents = filteredEvents.sort((a, b) => a.claimable_at - b.claimable_at); return sortedEvents;