From 62992bc09971d3799d644c585070c08f10b76b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Tkaczy=C5=84ski?= <wiktor.tkaczynski@gmail.com> Date: Tue, 14 Jan 2025 17:50:38 +0100 Subject: [PATCH 1/6] feat: index both mainnet and sepolia --- apps/api/src/config.json | 94 --- apps/api/src/config.ts | 164 +++++ apps/api/src/currentConfig.ts | 16 - apps/api/src/index.ts | 27 +- apps/api/src/ipfs.ts | 77 ++- apps/api/src/overrides.json | 8 + apps/api/src/overrrides.ts | 53 -- apps/api/src/utils.ts | 66 +- apps/api/src/writer.ts | 1214 +++++++++++++++++---------------- 9 files changed, 915 insertions(+), 804 deletions(-) delete mode 100644 apps/api/src/config.json create mode 100644 apps/api/src/config.ts delete mode 100644 apps/api/src/currentConfig.ts create mode 100644 apps/api/src/overrides.json delete mode 100644 apps/api/src/overrrides.ts diff --git a/apps/api/src/config.json b/apps/api/src/config.json deleted file mode 100644 index d99527172..000000000 --- a/apps/api/src/config.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "network_node_url": "https://starknet-sepolia.infura.io/v3/46a5dd9727bf48d4a132672d3f376146", - "optimistic_indexing": true, - "decimal_types": { - "BigDecimalVP": { - "p": 60, - "d": 0 - } - }, - "sources": [ - { - "contract": "0x302d332e9aceb184e5f301cb62c85181e7fc3b30559935c5736e987de579f6e", - "start": 17960, - "abi": "SpaceFactory", - "events": [ - { - "name": "NewContractDeployed", - "fn": "handleContractDeployed" - } - ] - } - ], - "templates": { - "Space": { - "abi": "Space", - "events": [ - { - "name": "SpaceCreated", - "fn": "handleSpaceCreated" - }, - { - "name": "ProposalCreated", - "fn": "handlePropose" - }, - { - "name": "ProposalCancelled", - "fn": "handleCancel" - }, - { - "name": "ProposalUpdated", - "fn": "handleUpdate" - }, - { - "name": "ProposalExecuted", - "fn": "handleExecute" - }, - { - "name": "VoteCast", - "fn": "handleVote" - }, - { - "name": "MetadataUriUpdated", - "fn": "handleMetadataUriUpdated" - }, - { - "name": "MinVotingDurationUpdated", - "fn": "handleMinVotingDurationUpdated" - }, - { - "name": "MaxVotingDurationUpdated", - "fn": "handleMaxVotingDurationUpdated" - }, - { - "name": "VotingDelayUpdated", - "fn": "handleVotingDelayUpdated" - }, - { - "name": "OwnershipTransferred", - "fn": "handleOwnershipTransferred" - }, - { - "name": "AuthenticatorsAdded", - "fn": "handleAuthenticatorsAdded" - }, - { - "name": "AuthenticatorsRemoved", - "fn": "handleAuthenticatorsRemoved" - }, - { - "name": "VotingStrategiesAdded", - "fn": "handleVotingStrategiesAdded" - }, - { - "name": "VotingStrategiesRemoved", - "fn": "handleVotingStrategiesRemoved" - }, - { - "name": "ProposalValidationStrategyUpdated", - "fn": "handleProposalValidationStrategyUpdated" - } - ] - } - } -} diff --git a/apps/api/src/config.ts b/apps/api/src/config.ts new file mode 100644 index 000000000..cc3622cdc --- /dev/null +++ b/apps/api/src/config.ts @@ -0,0 +1,164 @@ +import { CheckpointConfig } from '@snapshot-labs/checkpoint'; +import { starknetNetworks } from '@snapshot-labs/sx'; +import { validateAndParseAddress } from 'starknet'; +import spaceAbi from './abis/space.json'; +import spaceFactoryAbi from './abis/spaceFactory.json'; + +export type FullConfig = { + indexerName: 'sn' | 'sn-sep'; + overrides: ReturnType<typeof createOverrides>; +} & CheckpointConfig; + +const CONFIG = { + sn: { + indexerName: 'sn', + networkNodeUrl: + 'https://starknet-mainnet.infura.io/v3/46a5dd9727bf48d4a132672d3f376146', + l1NetworkNodeUrl: 'https://rpc.brovider.xyz/1', + contract: starknetNetworks['sn'].Meta.spaceFactory, + start: 445498, + verifiedSpaces: [ + '0x009fedaf0d7a480d21a27683b0965c0f8ded35b3f1cac39827a25a06a8a682a4', + '0x05ea5ef0c54c84dc7382629684c6e536c0b06246b3b0981c426b42372e3ef263', + '0x07c251045154318a2376a3bb65be47d3c90df1740d8e35c9b9d943aa3f240e50', + '0x07bd3419669f9f0cc8f19e9e2457089cdd4804a4c41a5729ee9c7fd02ab8ab62' + ] + }, + 'sn-sep': { + indexerName: 'sn-sep', + networkNodeUrl: + 'https://starknet-sepolia.infura.io/v3/46a5dd9727bf48d4a132672d3f376146', + l1NetworkNodeUrl: 'https://rpc.brovider.xyz/11155111', + contract: starknetNetworks['sn-sep'].Meta.spaceFactory, + start: 17960, + verifiedSpaces: [ + '0x0141464688e48ae5b7c83045edb10ecc242ce0e1ad4ff44aca3402f7f47c1ab9' + ] + } +}; + +const manaRpcUrl = process.env.VITE_MANA_URL || 'https://mana.box'; + +function createOverrides(networkId: keyof typeof CONFIG) { + const config = starknetNetworks[networkId]; + + return { + networkNodeUrl: CONFIG[networkId].networkNodeUrl, + l1NetworkNodeUrl: CONFIG[networkId].l1NetworkNodeUrl, + manaRpcUrl: `${manaRpcUrl}/stark_rpc/${config.Meta.eip712ChainId}`, + baseChainId: config.Meta.herodotusAccumulatesChainId, + erc20VotesStrategy: config.Strategies.ERC20Votes, + propositionPowerValidationStrategyAddress: + config.ProposalValidations.VotingPower, + spaceClassHash: config.Meta.masterSpace, + verifiedSpaces: CONFIG[networkId].verifiedSpaces, + herodotusStrategies: [ + config.Strategies.OZVotesStorageProof, + config.Strategies.OZVotesTrace208StorageProof, + config.Strategies.EVMSlotValue + ] + .filter(address => !!address) + .map(strategy => validateAndParseAddress(strategy)) + }; +} + +export function createConfig(indexerName: keyof typeof CONFIG): FullConfig { + const { networkNodeUrl, contract, start } = CONFIG[indexerName]; + + const overrides = createOverrides(indexerName); + + return { + indexerName, + overrides, + network_node_url: networkNodeUrl, + optimistic_indexing: true, + sources: [ + { + contract, + start, + abi: 'SpaceFactory', + events: [ + { + name: 'NewContractDeployed', + fn: 'handleContractDeployed' + } + ] + } + ], + templates: { + Space: { + abi: 'Space', + events: [ + { + name: 'SpaceCreated', + fn: 'handleSpaceCreated' + }, + { + name: 'ProposalCreated', + fn: 'handlePropose' + }, + { + name: 'ProposalCancelled', + fn: 'handleCancel' + }, + { + name: 'ProposalUpdated', + fn: 'handleUpdate' + }, + { + name: 'ProposalExecuted', + fn: 'handleExecute' + }, + { + name: 'VoteCast', + fn: 'handleVote' + }, + { + name: 'MetadataUriUpdated', + fn: 'handleMetadataUriUpdated' + }, + { + name: 'MinVotingDurationUpdated', + fn: 'handleMinVotingDurationUpdated' + }, + { + name: 'MaxVotingDurationUpdated', + fn: 'handleMaxVotingDurationUpdated' + }, + { + name: 'VotingDelayUpdated', + fn: 'handleVotingDelayUpdated' + }, + { + name: 'OwnershipTransferred', + fn: 'handleOwnershipTransferred' + }, + { + name: 'AuthenticatorsAdded', + fn: 'handleAuthenticatorsAdded' + }, + { + name: 'AuthenticatorsRemoved', + fn: 'handleAuthenticatorsRemoved' + }, + { + name: 'VotingStrategiesAdded', + fn: 'handleVotingStrategiesAdded' + }, + { + name: 'VotingStrategiesRemoved', + fn: 'handleVotingStrategiesRemoved' + }, + { + name: 'ProposalValidationStrategyUpdated', + fn: 'handleProposalValidationStrategyUpdated' + } + ] + } + }, + abis: { + SpaceFactory: spaceFactoryAbi, + Space: spaceAbi + } + }; +} diff --git a/apps/api/src/currentConfig.ts b/apps/api/src/currentConfig.ts deleted file mode 100644 index 06a431238..000000000 --- a/apps/api/src/currentConfig.ts +++ /dev/null @@ -1,16 +0,0 @@ -import baseConfig from './config.json'; -import { networkNodeUrl, networkProperties } from './overrrides'; - -export default { - ...baseConfig, - network_node_url: networkNodeUrl, - sources: baseConfig.sources.map((source, i) => { - if (i !== 0) return source; - - return { - ...source, - contract: networkProperties.factoryAddress, - start: networkProperties.startBlock - }; - }) -}; diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index d1bbf2655..eb84f5f05 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -9,10 +9,9 @@ import Checkpoint, { LogLevel, starknet } from '@snapshot-labs/checkpoint'; -import spaceAbi from './abis/space.json'; -import spaceFactoryAbi from './abis/spaceFactory.json'; -import config from './currentConfig'; -import * as writer from './writer'; +import { createConfig } from './config'; +import overrides from './overrides.json'; +import { createWriters } from './writer'; const dir = __dirname.endsWith('dist/src') ? '../' : ''; const schemaFile = path.join(__dirname, `${dir}../src/schema.gql`); @@ -25,17 +24,22 @@ if (process.env.CA_CERT) { process.env.CA_CERT = process.env.CA_CERT.replace(/\\n/g, '\n'); } -const indexer = new starknet.StarknetIndexer(writer); -const checkpoint = new Checkpoint(config, indexer, schema, { +const snConfig = createConfig('sn'); +const snSepConfig = createConfig('sn-sep'); + +const snIndexer = new starknet.StarknetIndexer(createWriters(snConfig)); +const snSepIndexer = new starknet.StarknetIndexer(createWriters(snSepConfig)); + +const checkpoint = new Checkpoint(schema, { logLevel: LogLevel.Info, resetOnConfigChange: true, prettifyLogs: process.env.NODE_ENV !== 'production', - abis: { - SpaceFactory: spaceFactoryAbi, - Space: spaceAbi - } + overridesConfig: overrides }); +checkpoint.addIndexer(snConfig.indexerName, snConfig, snIndexer); +checkpoint.addIndexer(snSepConfig.indexerName, snSepConfig, snSepIndexer); + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); async function run() { @@ -65,7 +69,8 @@ async function run() { await sleep(PRODUCTION_INDEXER_DELAY); } - // await checkpoint.reset(); + await checkpoint.resetMetadata(); + await checkpoint.reset(); checkpoint.start(); } diff --git a/apps/api/src/ipfs.ts b/apps/api/src/ipfs.ts index 7ab97bbfb..5d3bcaf27 100644 --- a/apps/api/src/ipfs.ts +++ b/apps/api/src/ipfs.ts @@ -1,9 +1,10 @@ import { getAddress } from '@ethersproject/address'; import { Contract as EthContract } from '@ethersproject/contracts'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { validateAndParseAddress } from 'starknet'; import L1AvatarExectionStrategyAbi from './abis/l1/L1AvatarExectionStrategy.json'; -import { networkProperties } from './overrrides'; -import { dropIpfs, ethProvider, getJSON, getSpaceName } from './utils'; +import { FullConfig } from './config'; +import { dropIpfs, getJSON, getSpaceName } from './utils'; import { ExecutionStrategy, ProposalMetadataItem, @@ -12,11 +13,21 @@ import { VoteMetadataItem } from '../.checkpoint/models'; -export async function handleSpaceMetadata(space: string, metadataUri: string) { - const exists = await SpaceMetadataItem.loadEntity(dropIpfs(metadataUri)); +export async function handleSpaceMetadata( + space: string, + metadataUri: string, + config: FullConfig +) { + const exists = await SpaceMetadataItem.loadEntity( + dropIpfs(metadataUri), + config.indexerName + ); if (exists) return; - const spaceMetadataItem = new SpaceMetadataItem(dropIpfs(metadataUri)); + const spaceMetadataItem = new SpaceMetadataItem( + dropIpfs(metadataUri), + config.indexerName + ); spaceMetadataItem.name = getSpaceName(space); spaceMetadataItem.about = ''; spaceMetadataItem.avatar = ''; @@ -92,8 +103,12 @@ export async function handleSpaceMetadata(space: string, metadataUri: string) { destinations.push(destination); uniqueExecutors.push(id); - let executionStrategy = await ExecutionStrategy.loadEntity(id); - if (!executionStrategy) executionStrategy = new ExecutionStrategy(id); + let executionStrategy = await ExecutionStrategy.loadEntity( + id, + config.indexerName + ); + if (!executionStrategy) + executionStrategy = new ExecutionStrategy(id, config.indexerName); executionStrategy.type = metadata.properties.execution_strategies_types[i]; @@ -106,6 +121,11 @@ export async function handleSpaceMetadata(space: string, metadataUri: string) { if (executionStrategy.type === 'EthRelayer') { const l1Destination = getAddress(destination); + const ethProvider = new StaticJsonRpcProvider( + config.overrides.l1NetworkNodeUrl, + config.overrides.baseChainId + ); + const l1AvatarExecutionStrategyContract = new EthContract( l1Destination, L1AvatarExectionStrategyAbi, @@ -120,7 +140,7 @@ export async function handleSpaceMetadata(space: string, metadataUri: string) { executionStrategy.destination_address = l1Destination; executionStrategy.quorum = quorum; executionStrategy.treasury = treasury; - executionStrategy.treasury_chain = networkProperties.baseChainId; + executionStrategy.treasury_chain = config.overrides.baseChainId; } await executionStrategy.save(); @@ -140,11 +160,20 @@ export async function handleSpaceMetadata(space: string, metadataUri: string) { await spaceMetadataItem.save(); } -export async function handleProposalMetadata(metadataUri: string) { - const exists = await ProposalMetadataItem.loadEntity(dropIpfs(metadataUri)); +export async function handleProposalMetadata( + metadataUri: string, + config: FullConfig +) { + const exists = await ProposalMetadataItem.loadEntity( + dropIpfs(metadataUri), + config.indexerName + ); if (exists) return; - const proposalMetadataItem = new ProposalMetadataItem(dropIpfs(metadataUri)); + const proposalMetadataItem = new ProposalMetadataItem( + dropIpfs(metadataUri), + config.indexerName + ); proposalMetadataItem.choices = ['For', 'Against', 'Abstain']; proposalMetadataItem.labels = []; @@ -172,11 +201,20 @@ export async function handleProposalMetadata(metadataUri: string) { await proposalMetadataItem.save(); } -export async function handleVoteMetadata(metadataUri: string) { - const exists = await VoteMetadataItem.loadEntity(dropIpfs(metadataUri)); +export async function handleVoteMetadata( + metadataUri: string, + config: FullConfig +) { + const exists = await VoteMetadataItem.loadEntity( + dropIpfs(metadataUri), + config.indexerName + ); if (exists) return; - const voteMetadataItem = new VoteMetadataItem(dropIpfs(metadataUri)); + const voteMetadataItem = new VoteMetadataItem( + dropIpfs(metadataUri), + config.indexerName + ); const metadata: any = await getJSON(metadataUri); voteMetadataItem.reason = metadata.reason ?? ''; @@ -184,14 +222,19 @@ export async function handleVoteMetadata(metadataUri: string) { await voteMetadataItem.save(); } -export async function handleStrategiesParsedMetadata(metadataUri: string) { +export async function handleStrategiesParsedMetadata( + metadataUri: string, + config: FullConfig +) { const exists = await StrategiesParsedMetadataDataItem.loadEntity( - dropIpfs(metadataUri) + dropIpfs(metadataUri), + config.indexerName ); if (exists) return; const strategiesParsedMetadataItem = new StrategiesParsedMetadataDataItem( - dropIpfs(metadataUri) + dropIpfs(metadataUri), + config.indexerName ); const metadata: any = await getJSON(metadataUri); diff --git a/apps/api/src/overrides.json b/apps/api/src/overrides.json new file mode 100644 index 000000000..74a7b36e1 --- /dev/null +++ b/apps/api/src/overrides.json @@ -0,0 +1,8 @@ +{ + "decimal_types": { + "BigDecimalVP": { + "p": 60, + "d": 0 + } + } +} diff --git a/apps/api/src/overrrides.ts b/apps/api/src/overrrides.ts deleted file mode 100644 index 5c150684b..000000000 --- a/apps/api/src/overrrides.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { starknetNetworks } from '@snapshot-labs/sx'; -import { validateAndParseAddress } from 'starknet'; - -export const networkNodeUrl = - process.env.NETWORK_NODE_URL || - 'https://starknet-sepolia.infura.io/v3/46a5dd9727bf48d4a132672d3f376146'; - -export const manaRpcUrl = process.env.VITE_MANA_URL || 'https://mana.box'; - -const verifiedSpaces = { - sn: [ - '0x009fedaf0d7a480d21a27683b0965c0f8ded35b3f1cac39827a25a06a8a682a4', - '0x05ea5ef0c54c84dc7382629684c6e536c0b06246b3b0981c426b42372e3ef263', - '0x07c251045154318a2376a3bb65be47d3c90df1740d8e35c9b9d943aa3f240e50', - '0x07bd3419669f9f0cc8f19e9e2457089cdd4804a4c41a5729ee9c7fd02ab8ab62' - ], - 'sn-sep': [ - '0x0141464688e48ae5b7c83045edb10ecc242ce0e1ad4ff44aca3402f7f47c1ab9' - ] -}; - -const createConfig = ( - networkId: keyof typeof starknetNetworks, - { startBlock }: { startBlock: number } -) => { - const config = starknetNetworks[networkId]; - - return { - manaRpcUrl: `${manaRpcUrl}/stark_rpc/${config.Meta.eip712ChainId}`, - baseChainId: config.Meta.herodotusAccumulatesChainId, - factoryAddress: config.Meta.spaceFactory, - erc20VotesStrategy: config.Strategies.ERC20Votes, - propositionPowerValidationStrategyAddress: - config.ProposalValidations.VotingPower, - spaceClassHash: config.Meta.masterSpace, - verifiedSpaces: verifiedSpaces[networkId], - herodotusStrategies: [ - config.Strategies.OZVotesStorageProof, - config.Strategies.OZVotesTrace208StorageProof, - config.Strategies.EVMSlotValue - ] - .filter(address => !!address) - .map(strategy => validateAndParseAddress(strategy)), - startBlock - }; -}; - -let networkProperties = createConfig('sn-sep', { startBlock: 17960 }); -if (process.env.NETWORK === 'SN_MAIN') { - networkProperties = createConfig('sn', { startBlock: 445498 }); -} - -export { networkProperties }; diff --git a/apps/api/src/utils.ts b/apps/api/src/utils.ts index dd6d50ae6..5714b98e4 100644 --- a/apps/api/src/utils.ts +++ b/apps/api/src/utils.ts @@ -1,7 +1,7 @@ import { getAddress } from '@ethersproject/address'; import { BigNumber } from '@ethersproject/bignumber'; import { Contract as EthContract } from '@ethersproject/contracts'; -import { JsonRpcProvider } from '@ethersproject/providers'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { faker } from '@faker-js/faker'; import { utils } from '@snapshot-labs/sx'; import fetch from 'cross-fetch'; @@ -17,8 +17,8 @@ import { import EncodersAbi from './abis/encoders.json'; import ExecutionStrategyAbi from './abis/executionStrategy.json'; import SimpleQuorumExecutionStrategyAbi from './abis/l1/SimpleQuorumExecutionStrategy.json'; +import { FullConfig } from './config'; import { handleStrategiesParsedMetadata } from './ipfs'; -import { networkNodeUrl, networkProperties } from './overrrides'; import { Space, StrategiesParsedMetadataItem, @@ -30,14 +30,6 @@ type StrategyConfig = { params: BigNumberish[]; }; -export const ethProvider = new JsonRpcProvider( - process.env.L1_NETWORK_NODE_URL ?? - `https://rpc.brovider.xyz/${networkProperties.baseChainId}` -); -const starkProvider = new RpcProvider({ - nodeUrl: networkNodeUrl -}); - const encodersAbi = new CallData(EncodersAbi); export function getCurrentTimestamp() { @@ -145,11 +137,16 @@ export function getVoteValue(label: string) { export async function handleExecutionStrategy( address: string, - payload: string[] + payload: string[], + config: FullConfig ) { try { if (address === '0x0') return null; + const starkProvider = new RpcProvider({ + nodeUrl: config.overrides.networkNodeUrl + }); + const executionContract = new Contract( ExecutionStrategyAbi, address, @@ -170,6 +167,11 @@ export async function handleExecutionStrategy( throw new Error('Invalid payload for EthRelayer execution strategy'); destinationAddress = formatAddress('Ethereum', l1Destination); + const ethProvider = new StaticJsonRpcProvider( + config.overrides.l1NetworkNodeUrl, + config.overrides.baseChainId + ); + const SimpleQuorumExecutionStrategyContract = new EthContract( destinationAddress, SimpleQuorumExecutionStrategyAbi, @@ -197,7 +199,8 @@ export async function updateProposaValidationStrategy( space: Space, validationStrategyAddress: string, validationStrategyParams: string[], - metadataUri: string[] + metadataUri: string[], + config: FullConfig ) { space.validation_strategy = validationStrategyAddress; space.validation_strategy_params = validationStrategyParams.join(','); @@ -209,7 +212,7 @@ export async function updateProposaValidationStrategy( if ( utils.encoding.hexPadLeft(validationStrategyAddress) === utils.encoding.hexPadLeft( - networkProperties.propositionPowerValidationStrategyAddress + config.overrides.propositionPowerValidationStrategyAddress ) ) { const parsed = encodersAbi.parse( @@ -232,7 +235,8 @@ export async function updateProposaValidationStrategy( try { await handleVotingPowerValidationMetadata( space.id, - space.voting_power_validation_strategy_metadata + space.voting_power_validation_strategy_metadata, + config ); } catch (e) { console.log('failed to handle voting power strategies metadata', e); @@ -244,6 +248,7 @@ export async function handleStrategiesMetadata( spaceId: string, metadataUris: string[], startingIndex: number, + config: FullConfig, type: | typeof StrategiesParsedMetadataItem | typeof VotingPowerValidationStrategiesParsedMetadataItem = StrategiesParsedMetadataItem @@ -255,17 +260,17 @@ export async function handleStrategiesMetadata( const index = startingIndex + i; const uniqueId = `${spaceId}/${index}/${dropIpfs(metadataUri)}`; - const exists = await type.loadEntity(uniqueId); + const exists = await type.loadEntity(uniqueId, config.indexerName); if (exists) continue; - const strategiesParsedMetadataItem = new type(uniqueId); + const strategiesParsedMetadataItem = new type(uniqueId, config.indexerName); strategiesParsedMetadataItem.space = spaceId; strategiesParsedMetadataItem.index = index; if (metadataUri.startsWith('ipfs://')) { strategiesParsedMetadataItem.data = dropIpfs(metadataUri); - await handleStrategiesParsedMetadata(metadataUri); + await handleStrategiesParsedMetadata(metadataUri, config); } await strategiesParsedMetadataItem.save(); @@ -274,7 +279,8 @@ export async function handleStrategiesMetadata( export async function handleVotingPowerValidationMetadata( spaceId: string, - metadataUri: string + metadataUri: string, + config: FullConfig ) { if (!metadataUri) return; @@ -285,20 +291,24 @@ export async function handleVotingPowerValidationMetadata( spaceId, metadata.strategies_metadata, 0, + config, VotingPowerValidationStrategiesParsedMetadataItem ); } -export async function registerProposal({ - l1TokenAddress, - strategyAddress, - snapshotTimestamp -}: { - l1TokenAddress: string; - strategyAddress: string; - snapshotTimestamp: number; -}) { - const res = await fetch(networkProperties.manaRpcUrl, { +export async function registerProposal( + { + l1TokenAddress, + strategyAddress, + snapshotTimestamp + }: { + l1TokenAddress: string; + strategyAddress: string; + snapshotTimestamp: number; + }, + config: FullConfig +) { + const res = await fetch(config.overrides.manaRpcUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/apps/api/src/writer.ts b/apps/api/src/writer.ts index 6cbda47c5..dd11456b6 100644 --- a/apps/api/src/writer.ts +++ b/apps/api/src/writer.ts @@ -1,11 +1,11 @@ import { starknet } from '@snapshot-labs/checkpoint'; import { validateAndParseAddress } from 'starknet'; +import { FullConfig } from './config'; import { handleProposalMetadata, handleSpaceMetadata, handleVoteMetadata } from './ipfs'; -import { networkProperties } from './overrrides'; import { dropIpfs, findVariant, @@ -31,673 +31,717 @@ type Strategy = { params: string[]; }; -export const handleContractDeployed: starknet.Writer = async ({ - blockNumber, - event, - instance -}) => { - console.log('Handle contract deployed'); - - if (!event) return; - - const paddedClassHash = validateAndParseAddress(event.class_hash); - - if (paddedClassHash === networkProperties.spaceClassHash) { - await instance.executeTemplate('Space', { - contract: event.contract_address, - start: blockNumber - }); - } else { - console.log('Unknown class hash', paddedClassHash); - } -}; +export function createWriters(config: FullConfig) { + const handleContractDeployed: starknet.Writer = async ({ + blockNumber, + event, + helpers: { executeTemplate } + }) => { + console.log('Handle contract deployed'); + + if (!event) return; + + const paddedClassHash = validateAndParseAddress(event.class_hash); + + if (paddedClassHash === config.overrides.spaceClassHash) { + await executeTemplate('Space', { + contract: event.contract_address, + start: blockNumber + }); + } else { + console.log('Unknown class hash', paddedClassHash); + } + }; + + const handleSpaceCreated: starknet.Writer = async ({ block, tx, event }) => { + console.log('Handle space created'); + + if (!event || !tx.transaction_hash) return; -export const handleSpaceCreated: starknet.Writer = async ({ - block, - tx, - event -}) => { - console.log('Handle space created'); - - if (!event || !tx.transaction_hash) return; - - const strategies: string[] = event.voting_strategies.map( - (strategy: Strategy) => strategy.address - ); - const strategiesParams = event.voting_strategies.map((strategy: Strategy) => - strategy.params.join(',') - ); // different format than sx-evm - const strategiesMetadataUris = event.voting_strategy_metadata_uris.map( - (array: string[]) => longStringToText(array) - ); - - const id = validateAndParseAddress(event.space); - - const space = new Space(id); - space.verified = networkProperties.verifiedSpaces.includes(id); - space.turbo = false; - space.metadata = null; - space.controller = validateAndParseAddress(event.owner); - space.voting_delay = Number(BigInt(event.voting_delay).toString()); - space.min_voting_period = Number( - BigInt(event.min_voting_duration).toString() - ); - space.max_voting_period = Number( - BigInt(event.max_voting_duration).toString() - ); - space.proposal_threshold = '0'; - space.strategies_indices = strategies.map((_, i) => i); - // NOTE: deprecated - space.strategies_indicies = space.strategies_indices; - space.strategies = strategies; - space.next_strategy_index = strategies.length; - space.strategies_params = strategiesParams; - space.strategies_metadata = strategiesMetadataUris; - space.authenticators = event.authenticators; - space.proposal_count = 0; - space.vote_count = 0; - space.proposer_count = 0; - space.voter_count = 0; - space.created = block?.timestamp ?? getCurrentTimestamp(); - space.tx = tx.transaction_hash; - - await updateProposaValidationStrategy( - space, - event.proposal_validation_strategy.address, - event.proposal_validation_strategy.params, - event.proposal_validation_strategy_metadata_uri - ); - - try { - const metadataUri = longStringToText(event.metadata_uri || []).replaceAll( - '\x00', - '' + const strategies: string[] = event.voting_strategies.map( + (strategy: Strategy) => strategy.address + ); + const strategiesParams = event.voting_strategies.map((strategy: Strategy) => + strategy.params.join(',') + ); // different format than sx-evm + const strategiesMetadataUris = event.voting_strategy_metadata_uris.map( + (array: string[]) => longStringToText(array) ); - await handleSpaceMetadata(space.id, metadataUri); - space.metadata = dropIpfs(metadataUri); - } catch (e) { - console.log('failed to parse space metadata', e); - } + const id = validateAndParseAddress(event.space); - try { - await handleStrategiesMetadata(space.id, strategiesMetadataUris, 0); - } catch (e) { - console.log('failed to handle strategies metadata', e); - } + const space = new Space(id, config.indexerName); + space.verified = config.overrides.verifiedSpaces.includes(id); + space.turbo = false; + space.metadata = null; + space.controller = validateAndParseAddress(event.owner); + space.voting_delay = Number(BigInt(event.voting_delay).toString()); + space.min_voting_period = Number( + BigInt(event.min_voting_duration).toString() + ); + space.max_voting_period = Number( + BigInt(event.max_voting_duration).toString() + ); + space.proposal_threshold = '0'; + space.strategies_indices = strategies.map((_, i) => i); + // NOTE: deprecated + space.strategies_indicies = space.strategies_indices; + space.strategies = strategies; + space.next_strategy_index = strategies.length; + space.strategies_params = strategiesParams; + space.strategies_metadata = strategiesMetadataUris; + space.authenticators = event.authenticators; + space.proposal_count = 0; + space.vote_count = 0; + space.proposer_count = 0; + space.voter_count = 0; + space.created = block?.timestamp ?? getCurrentTimestamp(); + space.tx = tx.transaction_hash; + + await updateProposaValidationStrategy( + space, + event.proposal_validation_strategy.address, + event.proposal_validation_strategy.params, + event.proposal_validation_strategy_metadata_uri, + config + ); - await space.save(); -}; + try { + const metadataUri = longStringToText(event.metadata_uri || []).replaceAll( + '\x00', + '' + ); + await handleSpaceMetadata(space.id, metadataUri, config); -export const handleMetadataUriUpdated: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; + space.metadata = dropIpfs(metadataUri); + } catch (e) { + console.log('failed to parse space metadata', e); + } - console.log('Handle space metadata uri updated'); + try { + await handleStrategiesMetadata( + space.id, + strategiesMetadataUris, + 0, + config + ); + } catch (e) { + console.log('failed to handle strategies metadata', e); + } - const spaceId = validateAndParseAddress(rawEvent.from_address); + await space.save(); + }; + + const handleMetadataUriUpdated: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; + + console.log('Handle space metadata uri updated'); + + const spaceId = validateAndParseAddress(rawEvent.from_address); + + try { + const metadataUri = longStringToText(event.metadata_uri).replaceAll( + '\x00', + '' + ); + await handleSpaceMetadata(spaceId, metadataUri, config); + + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; + + space.metadata = dropIpfs(metadataUri); + + await space.save(); + } catch (e) { + console.log('failed to update space metadata', e); + } + }; + + const handleMinVotingDurationUpdated: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; + + console.log('Handle space min voting duration updated'); + + const spaceId = validateAndParseAddress(rawEvent.from_address); - try { - const metadataUri = longStringToText(event.metadata_uri).replaceAll( - '\x00', - '' + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; + + space.min_voting_period = Number( + BigInt(event.min_voting_duration).toString() ); - await handleSpaceMetadata(spaceId, metadataUri); - const space = await Space.loadEntity(spaceId); + await space.save(); + }; + + const handleMaxVotingDurationUpdated: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; + + console.log('Handle space max voting duration updated'); + + const spaceId = validateAndParseAddress(rawEvent.from_address); + + const space = await Space.loadEntity(spaceId, config.indexerName); if (!space) return; - space.metadata = dropIpfs(metadataUri); + space.max_voting_period = Number( + BigInt(event.max_voting_duration).toString() + ); await space.save(); - } catch (e) { - console.log('failed to update space metadata', e); - } -}; + }; -export const handleMinVotingDurationUpdated: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; + const handleOwnershipTransferred: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; - console.log('Handle space min voting duration updated'); + console.log('Handle space ownership transferred'); - const spaceId = validateAndParseAddress(rawEvent.from_address); + const spaceId = validateAndParseAddress(rawEvent.from_address); - const space = await Space.loadEntity(spaceId); - if (!space) return; + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; - space.min_voting_period = Number( - BigInt(event.min_voting_duration).toString() - ); + space.controller = validateAndParseAddress(event.new_owner); - await space.save(); -}; + await space.save(); + }; -export const handleMaxVotingDurationUpdated: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; + const handleVotingDelayUpdated: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; - console.log('Handle space max voting duration updated'); + console.log('Handle space voting delay updated'); - const spaceId = validateAndParseAddress(rawEvent.from_address); + const spaceId = validateAndParseAddress(rawEvent.from_address); - const space = await Space.loadEntity(spaceId); - if (!space) return; + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; - space.max_voting_period = Number( - BigInt(event.max_voting_duration).toString() - ); + space.voting_delay = Number(BigInt(event.voting_delay).toString()); - await space.save(); -}; + await space.save(); + }; -export const handleOwnershipTransferred: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; + const handleAuthenticatorsAdded: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; - console.log('Handle space ownership transferred'); + console.log('Handle space authenticators added'); - const spaceId = validateAndParseAddress(rawEvent.from_address); + const spaceId = validateAndParseAddress(rawEvent.from_address); - const space = await Space.loadEntity(spaceId); - if (!space) return; + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; - space.controller = validateAndParseAddress(event.new_owner); + space.authenticators = [ + ...new Set([...space.authenticators, ...event.authenticators]) + ]; - await space.save(); -}; + await space.save(); + }; -export const handleVotingDelayUpdated: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; + const handleAuthenticatorsRemoved: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; - console.log('Handle space voting delay updated'); + console.log('Handle space authenticators removed'); - const spaceId = validateAndParseAddress(rawEvent.from_address); + const spaceId = validateAndParseAddress(rawEvent.from_address); - const space = await Space.loadEntity(spaceId); - if (!space) return; + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; - space.voting_delay = Number(BigInt(event.voting_delay).toString()); + space.authenticators = space.authenticators.filter( + authenticator => !event.authenticators.includes(authenticator) + ); - await space.save(); -}; + await space.save(); + }; -export const handleAuthenticatorsAdded: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; + const handleVotingStrategiesAdded: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; - console.log('Handle space authenticators added'); + console.log('Handle space voting strategies added'); - const spaceId = validateAndParseAddress(rawEvent.from_address); + const spaceId = validateAndParseAddress(rawEvent.from_address); - const space = await Space.loadEntity(spaceId); - if (!space) return; + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; - space.authenticators = [ - ...new Set([...space.authenticators, ...event.authenticators]) - ]; + const initialNextStrategy = space.next_strategy_index; - await space.save(); -}; + const strategies: string[] = event.voting_strategies.map( + (strategy: Strategy) => strategy.address + ); + const strategiesParams = event.voting_strategies.map((strategy: Strategy) => + strategy.params.join(',') + ); + const strategiesMetadataUris = event.voting_strategy_metadata_uris.map( + (array: string[]) => longStringToText(array) + ); -export const handleAuthenticatorsRemoved: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; + space.strategies_indices = [ + ...space.strategies_indices, + ...strategies.map((_, i) => space.next_strategy_index + i) + ]; + // NOTE: deprecated + space.strategies_indicies = space.strategies_indices; + space.strategies = [...space.strategies, ...strategies]; + space.next_strategy_index += strategies.length; + space.strategies_params = [...space.strategies_params, ...strategiesParams]; + space.strategies_metadata = [ + ...space.strategies_metadata, + ...strategiesMetadataUris + ]; - console.log('Handle space authenticators removed'); + try { + await handleStrategiesMetadata( + space.id, + strategiesMetadataUris, + initialNextStrategy, + config + ); + } catch (e) { + console.log('failed to handle strategies metadata', e); + } - const spaceId = validateAndParseAddress(rawEvent.from_address); + await space.save(); + }; - const space = await Space.loadEntity(spaceId); - if (!space) return; + const handleVotingStrategiesRemoved: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; - space.authenticators = space.authenticators.filter( - authenticator => !event.authenticators.includes(authenticator) - ); + console.log('Handle space voting strategies removed'); - await space.save(); -}; + const spaceId = validateAndParseAddress(rawEvent.from_address); -export const handleVotingStrategiesAdded: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; - - console.log('Handle space voting strategies added'); - - const spaceId = validateAndParseAddress(rawEvent.from_address); - - const space = await Space.loadEntity(spaceId); - if (!space) return; - - const initialNextStrategy = space.next_strategy_index; - - const strategies: string[] = event.voting_strategies.map( - (strategy: Strategy) => strategy.address - ); - const strategiesParams = event.voting_strategies.map((strategy: Strategy) => - strategy.params.join(',') - ); - const strategiesMetadataUris = event.voting_strategy_metadata_uris.map( - (array: string[]) => longStringToText(array) - ); - - space.strategies_indices = [ - ...space.strategies_indices, - ...strategies.map((_, i) => space.next_strategy_index + i) - ]; - // NOTE: deprecated - space.strategies_indicies = space.strategies_indices; - space.strategies = [...space.strategies, ...strategies]; - space.next_strategy_index += strategies.length; - space.strategies_params = [...space.strategies_params, ...strategiesParams]; - space.strategies_metadata = [ - ...space.strategies_metadata, - ...strategiesMetadataUris - ]; - - try { - await handleStrategiesMetadata( - space.id, - strategiesMetadataUris, - initialNextStrategy + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; + + const indicesToRemove = event.voting_strategy_indices.map((index: string) => + space.strategies_indices.indexOf(parseInt(index)) ); - } catch (e) { - console.log('failed to handle strategies metadata', e); - } - await space.save(); -}; + space.strategies_indices = space.strategies_indices.filter( + (_, i) => !indicesToRemove.includes(i) + ); + // NOTE: deprecated + space.strategies_indicies = space.strategies_indices; + space.strategies = space.strategies.filter( + (_, i) => !indicesToRemove.includes(i) + ); + space.strategies_params = space.strategies_params.filter( + (_, i) => !indicesToRemove.includes(i) + ); + space.strategies_metadata = space.strategies_metadata.filter( + (_, i) => !indicesToRemove.includes(i) + ); -export const handleVotingStrategiesRemoved: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; - - console.log('Handle space voting strategies removed'); - - const spaceId = validateAndParseAddress(rawEvent.from_address); - - const space = await Space.loadEntity(spaceId); - if (!space) return; - - const indicesToRemove = event.voting_strategy_indices.map((index: string) => - space.strategies_indices.indexOf(parseInt(index)) - ); - - space.strategies_indices = space.strategies_indices.filter( - (_, i) => !indicesToRemove.includes(i) - ); - // NOTE: deprecated - space.strategies_indicies = space.strategies_indices; - space.strategies = space.strategies.filter( - (_, i) => !indicesToRemove.includes(i) - ); - space.strategies_params = space.strategies_params.filter( - (_, i) => !indicesToRemove.includes(i) - ); - space.strategies_metadata = space.strategies_metadata.filter( - (_, i) => !indicesToRemove.includes(i) - ); - - await space.save(); -}; + await space.save(); + }; -export const handleProposalValidationStrategyUpdated: starknet.Writer = async ({ - rawEvent, - event -}) => { - if (!event || !rawEvent) return; + const handleProposalValidationStrategyUpdated: starknet.Writer = async ({ + rawEvent, + event + }) => { + if (!event || !rawEvent) return; - console.log('Handle space proposal validation strategy updated'); + console.log('Handle space proposal validation strategy updated'); - const spaceId = validateAndParseAddress(rawEvent.from_address); + const spaceId = validateAndParseAddress(rawEvent.from_address); - const space = await Space.loadEntity(spaceId); - if (!space) return; + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; - await updateProposaValidationStrategy( - space, - event.proposal_validation_strategy.address, - event.proposal_validation_strategy.params, - event.proposal_validation_strategy_metadata_uri - ); + await updateProposaValidationStrategy( + space, + event.proposal_validation_strategy.address, + event.proposal_validation_strategy.params, + event.proposal_validation_strategy_metadata_uri, + config + ); - await space.save(); -}; + await space.save(); + }; -export const handlePropose: starknet.Writer = async ({ - tx, - rawEvent, - event -}) => { - if (!rawEvent || !event || !tx.transaction_hash) return; - - console.log('Handle propose'); - - const spaceId = validateAndParseAddress(rawEvent.from_address); - - const space = await Space.loadEntity(spaceId); - if (!space) return; - - const proposalId = parseInt(BigInt(event.proposal_id).toString()); - const author = formatAddressVariant(findVariant(event.author)); - - const created = - BigInt(event.proposal.start_timestamp) - BigInt(space.voting_delay); - - // for erc20votes strategies we have to add artificial delay to prevent voting within same block - // snapshot needs to remain the same as we need real timestamp to compute VP - let startTimestamp = BigInt(event.proposal.start_timestamp); - let minEnd = BigInt(event.proposal.min_end_timestamp); - if ( - space.strategies.some( - strategy => strategy === networkProperties.erc20VotesStrategy - ) - ) { - const minimumDelay = 10n * 60n; - startTimestamp = - startTimestamp > created + minimumDelay - ? startTimestamp - : created + minimumDelay; - minEnd = minEnd > startTimestamp ? minEnd : startTimestamp; - } - - const proposal = new Proposal(`${spaceId}/${proposalId}`); - proposal.proposal_id = proposalId; - proposal.space = spaceId; - proposal.author = author.address; - proposal.metadata = null; - proposal.execution_hash = event.proposal.execution_payload_hash; - proposal.start = parseInt(startTimestamp.toString()); - proposal.min_end = parseInt(minEnd.toString()); - proposal.max_end = parseInt( - BigInt(event.proposal.max_end_timestamp).toString() - ); - proposal.snapshot = parseInt( - BigInt(event.proposal.start_timestamp).toString() - ); - proposal.execution_time = 0; - proposal.execution_strategy = validateAndParseAddress( - event.proposal.execution_strategy - ); - proposal.execution_strategy_type = 'none'; - proposal.type = 'basic'; - proposal.scores_1 = '0'; - proposal.scores_2 = '0'; - proposal.scores_3 = '0'; - proposal.scores_total = '0'; - proposal.quorum = 0n; - proposal.strategies_indices = space.strategies_indices; - // NOTE: deprecated - proposal.strategies_indicies = proposal.strategies_indices; - proposal.strategies = space.strategies; - proposal.strategies_params = space.strategies_params; - proposal.created = parseInt(created.toString()); - proposal.tx = tx.transaction_hash; - proposal.execution_tx = null; - proposal.veto_tx = null; - proposal.vote_count = 0; - proposal.execution_ready = true; - proposal.executed = false; - proposal.vetoed = false; - proposal.completed = false; - proposal.cancelled = false; - - const executionStrategy = await handleExecutionStrategy( - event.proposal.execution_strategy, - event.payload - ); - if (executionStrategy) { - proposal.execution_strategy_type = executionStrategy.executionStrategyType; - proposal.execution_destination = executionStrategy.destinationAddress; - proposal.quorum = executionStrategy.quorum; - } - - try { - const metadataUri = longStringToText(event.metadata_uri); - await handleProposalMetadata(metadataUri); - - proposal.metadata = dropIpfs(metadataUri); - } catch (e) { - console.log(JSON.stringify(e).slice(0, 256)); - } - - const existingUser = await User.loadEntity(author.address); - if (existingUser) { - existingUser.proposal_count += 1; - await existingUser.save(); - } else { - const user = new User(author.address); - user.address_type = author.type; - user.created = parseInt(created.toString()); - await user.save(); - } - - let leaderboardItem = await Leaderboard.loadEntity( - `${spaceId}/${author.address}` - ); - if (!leaderboardItem) { - leaderboardItem = new Leaderboard(`${spaceId}/${author.address}`); - leaderboardItem.space = spaceId; - leaderboardItem.user = author.address; - leaderboardItem.vote_count = 0; - leaderboardItem.proposal_count = 0; - } - - leaderboardItem.proposal_count += 1; - await leaderboardItem.save(); - - if (leaderboardItem.proposal_count === 1) space.proposer_count += 1; - space.proposal_count += 1; - - const herodotusStrategiesIndices = space.strategies - .map((strategy, i) => [strategy, i] as const) - .filter(([strategy]) => - networkProperties.herodotusStrategies.includes( - validateAndParseAddress(strategy) + const handlePropose: starknet.Writer = async ({ tx, rawEvent, event }) => { + if (!rawEvent || !event || !tx.transaction_hash) return; + + console.log('Handle propose'); + + const spaceId = validateAndParseAddress(rawEvent.from_address); + + const space = await Space.loadEntity(spaceId, config.indexerName); + if (!space) return; + + const proposalId = parseInt(BigInt(event.proposal_id).toString()); + const author = formatAddressVariant(findVariant(event.author)); + + const created = + BigInt(event.proposal.start_timestamp) - BigInt(space.voting_delay); + + // for erc20votes strategies we have to add artificial delay to prevent voting within same block + // snapshot needs to remain the same as we need real timestamp to compute VP + let startTimestamp = BigInt(event.proposal.start_timestamp); + let minEnd = BigInt(event.proposal.min_end_timestamp); + if ( + space.strategies.some( + strategy => strategy === config.overrides.erc20VotesStrategy ) + ) { + const minimumDelay = 10n * 60n; + startTimestamp = + startTimestamp > created + minimumDelay + ? startTimestamp + : created + minimumDelay; + minEnd = minEnd > startTimestamp ? minEnd : startTimestamp; + } + + const proposal = new Proposal( + `${spaceId}/${proposalId}`, + config.indexerName + ); + proposal.proposal_id = proposalId; + proposal.space = spaceId; + proposal.author = author.address; + proposal.metadata = null; + proposal.execution_hash = event.proposal.execution_payload_hash; + proposal.start = parseInt(startTimestamp.toString()); + proposal.min_end = parseInt(minEnd.toString()); + proposal.max_end = parseInt( + BigInt(event.proposal.max_end_timestamp).toString() ); + proposal.snapshot = parseInt( + BigInt(event.proposal.start_timestamp).toString() + ); + proposal.execution_time = 0; + proposal.execution_strategy = validateAndParseAddress( + event.proposal.execution_strategy + ); + proposal.execution_strategy_type = 'none'; + proposal.type = 'basic'; + proposal.scores_1 = '0'; + proposal.scores_2 = '0'; + proposal.scores_3 = '0'; + proposal.scores_total = '0'; + proposal.quorum = 0n; + proposal.strategies_indices = space.strategies_indices; + // NOTE: deprecated + proposal.strategies_indicies = proposal.strategies_indices; + proposal.strategies = space.strategies; + proposal.strategies_params = space.strategies_params; + proposal.created = parseInt(created.toString()); + proposal.tx = tx.transaction_hash; + proposal.execution_tx = null; + proposal.veto_tx = null; + proposal.vote_count = 0; + proposal.execution_ready = true; + proposal.executed = false; + proposal.vetoed = false; + proposal.completed = false; + proposal.cancelled = false; + + const executionStrategy = await handleExecutionStrategy( + event.proposal.execution_strategy, + event.payload, + config + ); + if (executionStrategy) { + proposal.execution_strategy_type = + executionStrategy.executionStrategyType; + proposal.execution_destination = executionStrategy.destinationAddress; + proposal.quorum = executionStrategy.quorum; + } + + try { + const metadataUri = longStringToText(event.metadata_uri); + await handleProposalMetadata(metadataUri, config); + + proposal.metadata = dropIpfs(metadataUri); + } catch (e) { + console.log(JSON.stringify(e).slice(0, 256)); + } + + const existingUser = await User.loadEntity( + author.address, + config.indexerName + ); + if (existingUser) { + existingUser.proposal_count += 1; + await existingUser.save(); + } else { + const user = new User(author.address, config.indexerName); + user.address_type = author.type; + user.created = parseInt(created.toString()); + await user.save(); + } + + let leaderboardItem = await Leaderboard.loadEntity( + `${spaceId}/${author.address}`, + config.indexerName + ); + if (!leaderboardItem) { + leaderboardItem = new Leaderboard( + `${spaceId}/${author.address}`, + config.indexerName + ); + leaderboardItem.space = spaceId; + leaderboardItem.user = author.address; + leaderboardItem.vote_count = 0; + leaderboardItem.proposal_count = 0; + } + + leaderboardItem.proposal_count += 1; + await leaderboardItem.save(); + + if (leaderboardItem.proposal_count === 1) space.proposer_count += 1; + space.proposal_count += 1; + + const herodotusStrategiesIndices = space.strategies + .map((strategy, i) => [strategy, i] as const) + .filter(([strategy]) => + config.overrides.herodotusStrategies.includes( + validateAndParseAddress(strategy) + ) + ); + + for (const herodotusStrategy of herodotusStrategiesIndices) { + const [strategy, i] = herodotusStrategy; + const params = space.strategies_params[i]; + if (!params) continue; + + const [l1TokenAddress] = params.split(','); + if (!l1TokenAddress) continue; + + try { + await registerProposal( + { + l1TokenAddress, + strategyAddress: strategy, + snapshotTimestamp: proposal.snapshot + }, + config + ); + } catch (e) { + console.log('failed to register proposal'); + } + } + + await Promise.all([proposal.save(), space.save()]); + }; + + const handleCancel: starknet.Writer = async ({ rawEvent, event }) => { + if (!rawEvent || !event) return; + + console.log('Handle cancel'); + + const spaceId = validateAndParseAddress(rawEvent.from_address); + const proposalId = `${spaceId}/${parseInt(event.proposal_id)}`; + + const [proposal, space] = await Promise.all([ + Proposal.loadEntity(proposalId, config.indexerName), + Space.loadEntity(spaceId, config.indexerName) + ]); + if (!proposal || !space) return; - for (const herodotusStrategy of herodotusStrategiesIndices) { - const [strategy, i] = herodotusStrategy; - const params = space.strategies_params[i]; - if (!params) continue; + proposal.cancelled = true; + space.proposal_count -= 1; + space.vote_count -= proposal.vote_count; - const [l1TokenAddress] = params.split(','); - if (!l1TokenAddress) continue; + await Promise.all([proposal.save(), space.save()]); + }; + + const handleUpdate: starknet.Writer = async ({ block, rawEvent, event }) => { + if (!rawEvent || !event) return; + + console.log('Handle update'); + + const spaceId = validateAndParseAddress(rawEvent.from_address); + const proposalId = `${spaceId}/${parseInt(event.proposal_id)}`; + const metadataUri = longStringToText(event.metadata_uri); + + const proposal = await Proposal.loadEntity(proposalId, config.indexerName); + if (!proposal) return; try { - await registerProposal({ - l1TokenAddress, - strategyAddress: strategy, - snapshotTimestamp: proposal.snapshot - }); + await handleProposalMetadata(metadataUri, config); + + proposal.metadata = dropIpfs(metadataUri); + proposal.edited = block?.timestamp ?? getCurrentTimestamp(); } catch (e) { - console.log('failed to register proposal'); + console.log('failed to update proposal metadata', e); } - } - await Promise.all([proposal.save(), space.save()]); -}; + const executionStrategy = await handleExecutionStrategy( + event.execution_strategy, + event.payload, + config + ); + if (executionStrategy) { + proposal.execution_strategy_type = + executionStrategy.executionStrategyType; + proposal.quorum = executionStrategy.quorum; + } -export const handleCancel: starknet.Writer = async ({ rawEvent, event }) => { - if (!rawEvent || !event) return; + await proposal.save(); + }; - console.log('Handle cancel'); + const handleExecute: starknet.Writer = async ({ tx, rawEvent, event }) => { + if (!rawEvent || !event) return; - const spaceId = validateAndParseAddress(rawEvent.from_address); - const proposalId = `${spaceId}/${parseInt(event.proposal_id)}`; + console.log('Handle execute'); - const [proposal, space] = await Promise.all([ - Proposal.loadEntity(proposalId), - Space.loadEntity(spaceId) - ]); - if (!proposal || !space) return; + const spaceId = validateAndParseAddress(rawEvent.from_address); + const proposalId = `${spaceId}/${parseInt(event.proposal_id)}`; - proposal.cancelled = true; - space.proposal_count -= 1; - space.vote_count -= proposal.vote_count; + const proposal = await Proposal.loadEntity(proposalId, config.indexerName); + if (!proposal) return; - await Promise.all([proposal.save(), space.save()]); -}; + proposal.executed = true; + proposal.completed = true; + proposal.execution_tx = tx.transaction_hash ?? null; -export const handleUpdate: starknet.Writer = async ({ - block, - rawEvent, - event -}) => { - if (!rawEvent || !event) return; - - console.log('Handle update'); - - const spaceId = validateAndParseAddress(rawEvent.from_address); - const proposalId = `${spaceId}/${parseInt(event.proposal_id)}`; - const metadataUri = longStringToText(event.metadata_uri); - - const proposal = await Proposal.loadEntity(proposalId); - if (!proposal) return; - - try { - await handleProposalMetadata(metadataUri); - - proposal.metadata = dropIpfs(metadataUri); - proposal.edited = block?.timestamp ?? getCurrentTimestamp(); - } catch (e) { - console.log('failed to update proposal metadata', e); - } - - const executionStrategy = await handleExecutionStrategy( - event.execution_strategy, - event.payload - ); - if (executionStrategy) { - proposal.execution_strategy_type = executionStrategy.executionStrategyType; - proposal.quorum = executionStrategy.quorum; - } - - await proposal.save(); -}; + await proposal.save(); + }; -export const handleExecute: starknet.Writer = async ({ - tx, - rawEvent, - event -}) => { - if (!rawEvent || !event) return; + const handleVote: starknet.Writer = async ({ + block, + tx, + rawEvent, + event + }) => { + if (!rawEvent || !event) return; - console.log('Handle execute'); + console.log('Handle vote'); - const spaceId = validateAndParseAddress(rawEvent.from_address); - const proposalId = `${spaceId}/${parseInt(event.proposal_id)}`; + const spaceId = validateAndParseAddress(rawEvent.from_address); + const proposalId = parseInt(event.proposal_id); + const choice = getVoteValue(findVariant(event.choice).key); + const vp = BigInt(event.voting_power); - const proposal = await Proposal.loadEntity(proposalId); - if (!proposal) return; + const created = block?.timestamp ?? getCurrentTimestamp(); + const voter = formatAddressVariant(findVariant(event.voter)); - proposal.executed = true; - proposal.completed = true; - proposal.execution_tx = tx.transaction_hash ?? null; + const vote = new Vote( + `${spaceId}/${proposalId}/${voter.address}`, + config.indexerName + ); + vote.space = spaceId; + vote.proposal = proposalId; + vote.voter = voter.address; + vote.choice = choice; + vote.vp = vp.toString(); + vote.created = created; + vote.tx = tx.transaction_hash; - await proposal.save(); -}; + try { + const metadataUri = longStringToText(event.metadata_uri); + await handleVoteMetadata(metadataUri, config); -export const handleVote: starknet.Writer = async ({ - block, - tx, - rawEvent, - event -}) => { - if (!rawEvent || !event) return; - - console.log('Handle vote'); - - const spaceId = validateAndParseAddress(rawEvent.from_address); - const proposalId = parseInt(event.proposal_id); - const choice = getVoteValue(findVariant(event.choice).key); - const vp = BigInt(event.voting_power); - - const created = block?.timestamp ?? getCurrentTimestamp(); - const voter = formatAddressVariant(findVariant(event.voter)); - - const vote = new Vote(`${spaceId}/${proposalId}/${voter.address}`); - vote.space = spaceId; - vote.proposal = proposalId; - vote.voter = voter.address; - vote.choice = choice; - vote.vp = vp.toString(); - vote.created = created; - vote.tx = tx.transaction_hash; - - try { - const metadataUri = longStringToText(event.metadata_uri); - await handleVoteMetadata(metadataUri); - - vote.metadata = dropIpfs(metadataUri); - } catch (e) { - console.log(JSON.stringify(e).slice(0, 256)); - } - - await vote.save(); - - const existingUser = await User.loadEntity(voter.address); - if (existingUser) { - existingUser.vote_count += 1; - await existingUser.save(); - } else { - const user = new User(voter.address); - user.address_type = voter.type; - user.created = created; - await user.save(); - } - - let leaderboardItem = await Leaderboard.loadEntity( - `${spaceId}/${voter.address}` - ); - if (!leaderboardItem) { - leaderboardItem = new Leaderboard(`${spaceId}/${voter.address}`); - leaderboardItem.space = spaceId; - leaderboardItem.user = voter.address; - leaderboardItem.vote_count = 0; - leaderboardItem.proposal_count = 0; - } - - leaderboardItem.vote_count += 1; - await leaderboardItem.save(); - - const space = await Space.loadEntity(spaceId); - if (space) { - space.vote_count += 1; - if (leaderboardItem.vote_count === 1) space.voter_count += 1; + vote.metadata = dropIpfs(metadataUri); + } catch (e) { + console.log(JSON.stringify(e).slice(0, 256)); + } - await space.save(); - } - - const proposal = await Proposal.loadEntity(`${spaceId}/${proposalId}`); - if (proposal) { - proposal.vote_count += 1; - proposal.scores_total = ( - BigInt(proposal.scores_total) + BigInt(vote.vp) - ).toString(); - proposal[`scores_${choice}`] = ( - BigInt(proposal[`scores_${choice}`]) + BigInt(vote.vp) - ).toString(); - await proposal.save(); - } -}; + await vote.save(); + + const existingUser = await User.loadEntity( + voter.address, + config.indexerName + ); + if (existingUser) { + existingUser.vote_count += 1; + await existingUser.save(); + } else { + const user = new User(voter.address, config.indexerName); + user.address_type = voter.type; + user.created = created; + await user.save(); + } + + let leaderboardItem = await Leaderboard.loadEntity( + `${spaceId}/${voter.address}`, + config.indexerName + ); + if (!leaderboardItem) { + leaderboardItem = new Leaderboard( + `${spaceId}/${voter.address}`, + config.indexerName + ); + leaderboardItem.space = spaceId; + leaderboardItem.user = voter.address; + leaderboardItem.vote_count = 0; + leaderboardItem.proposal_count = 0; + } + + leaderboardItem.vote_count += 1; + await leaderboardItem.save(); + + const space = await Space.loadEntity(spaceId, config.indexerName); + if (space) { + space.vote_count += 1; + if (leaderboardItem.vote_count === 1) space.voter_count += 1; + + await space.save(); + } + + const proposal = await Proposal.loadEntity( + `${spaceId}/${proposalId}`, + config.indexerName + ); + if (proposal) { + proposal.vote_count += 1; + proposal.scores_total = ( + BigInt(proposal.scores_total) + BigInt(vote.vp) + ).toString(); + proposal[`scores_${choice}`] = ( + BigInt(proposal[`scores_${choice}`]) + BigInt(vote.vp) + ).toString(); + await proposal.save(); + } + }; + + return { + handleContractDeployed, + handleSpaceCreated, + handleMetadataUriUpdated, + handleMinVotingDurationUpdated, + handleMaxVotingDurationUpdated, + handleOwnershipTransferred, + handleVotingDelayUpdated, + handleAuthenticatorsAdded, + handleAuthenticatorsRemoved, + handleVotingStrategiesAdded, + handleVotingStrategiesRemoved, + handleProposalValidationStrategyUpdated, + handlePropose, + handleCancel, + handleUpdate, + handleExecute, + handleVote + }; +} From bd07bdf6ee58dc3cd55470a1da739127457c2497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Tkaczy=C5=84ski?= <wiktor.tkaczynski@gmail.com> Date: Thu, 23 Jan 2025 14:33:21 +0100 Subject: [PATCH 2/6] refactor: move starknet specific code to directory --- apps/api/src/index.ts | 15 +++------------ apps/api/src/{ => starknet}/abis/encoders.json | 0 .../{ => starknet}/abis/executionStrategy.json | 0 .../abis/l1/L1AvatarExectionStrategy.json | 0 .../abis/l1/SimpleQuorumExecutionStrategy.json | 0 apps/api/src/{ => starknet}/abis/space.json | 0 .../api/src/{ => starknet}/abis/spaceFactory.json | 0 apps/api/src/{ => starknet}/config.ts | 0 apps/api/src/starknet/index.ts | 14 ++++++++++++++ apps/api/src/{ => starknet}/ipfs.ts | 2 +- apps/api/src/{ => starknet}/utils.ts | 2 +- apps/api/src/{writer.ts => starknet/writers.ts} | 2 +- 12 files changed, 20 insertions(+), 15 deletions(-) rename apps/api/src/{ => starknet}/abis/encoders.json (100%) rename apps/api/src/{ => starknet}/abis/executionStrategy.json (100%) rename apps/api/src/{ => starknet}/abis/l1/L1AvatarExectionStrategy.json (100%) rename apps/api/src/{ => starknet}/abis/l1/SimpleQuorumExecutionStrategy.json (100%) rename apps/api/src/{ => starknet}/abis/space.json (100%) rename apps/api/src/{ => starknet}/abis/spaceFactory.json (100%) rename apps/api/src/{ => starknet}/config.ts (100%) create mode 100644 apps/api/src/starknet/index.ts rename apps/api/src/{ => starknet}/ipfs.ts (99%) rename apps/api/src/{ => starknet}/utils.ts (99%) rename apps/api/src/{writer.ts => starknet/writers.ts} (99%) diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index eb84f5f05..aafda0e21 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -6,12 +6,10 @@ import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin import { startStandaloneServer } from '@apollo/server/standalone'; import Checkpoint, { createGetLoader, - LogLevel, - starknet + LogLevel } from '@snapshot-labs/checkpoint'; -import { createConfig } from './config'; import overrides from './overrides.json'; -import { createWriters } from './writer'; +import { addStarknetIndexers } from './starknet'; const dir = __dirname.endsWith('dist/src') ? '../' : ''; const schemaFile = path.join(__dirname, `${dir}../src/schema.gql`); @@ -24,12 +22,6 @@ if (process.env.CA_CERT) { process.env.CA_CERT = process.env.CA_CERT.replace(/\\n/g, '\n'); } -const snConfig = createConfig('sn'); -const snSepConfig = createConfig('sn-sep'); - -const snIndexer = new starknet.StarknetIndexer(createWriters(snConfig)); -const snSepIndexer = new starknet.StarknetIndexer(createWriters(snSepConfig)); - const checkpoint = new Checkpoint(schema, { logLevel: LogLevel.Info, resetOnConfigChange: true, @@ -37,8 +29,7 @@ const checkpoint = new Checkpoint(schema, { overridesConfig: overrides }); -checkpoint.addIndexer(snConfig.indexerName, snConfig, snIndexer); -checkpoint.addIndexer(snSepConfig.indexerName, snSepConfig, snSepIndexer); +addStarknetIndexers(checkpoint); const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); diff --git a/apps/api/src/abis/encoders.json b/apps/api/src/starknet/abis/encoders.json similarity index 100% rename from apps/api/src/abis/encoders.json rename to apps/api/src/starknet/abis/encoders.json diff --git a/apps/api/src/abis/executionStrategy.json b/apps/api/src/starknet/abis/executionStrategy.json similarity index 100% rename from apps/api/src/abis/executionStrategy.json rename to apps/api/src/starknet/abis/executionStrategy.json diff --git a/apps/api/src/abis/l1/L1AvatarExectionStrategy.json b/apps/api/src/starknet/abis/l1/L1AvatarExectionStrategy.json similarity index 100% rename from apps/api/src/abis/l1/L1AvatarExectionStrategy.json rename to apps/api/src/starknet/abis/l1/L1AvatarExectionStrategy.json diff --git a/apps/api/src/abis/l1/SimpleQuorumExecutionStrategy.json b/apps/api/src/starknet/abis/l1/SimpleQuorumExecutionStrategy.json similarity index 100% rename from apps/api/src/abis/l1/SimpleQuorumExecutionStrategy.json rename to apps/api/src/starknet/abis/l1/SimpleQuorumExecutionStrategy.json diff --git a/apps/api/src/abis/space.json b/apps/api/src/starknet/abis/space.json similarity index 100% rename from apps/api/src/abis/space.json rename to apps/api/src/starknet/abis/space.json diff --git a/apps/api/src/abis/spaceFactory.json b/apps/api/src/starknet/abis/spaceFactory.json similarity index 100% rename from apps/api/src/abis/spaceFactory.json rename to apps/api/src/starknet/abis/spaceFactory.json diff --git a/apps/api/src/config.ts b/apps/api/src/starknet/config.ts similarity index 100% rename from apps/api/src/config.ts rename to apps/api/src/starknet/config.ts diff --git a/apps/api/src/starknet/index.ts b/apps/api/src/starknet/index.ts new file mode 100644 index 000000000..57c66bfc1 --- /dev/null +++ b/apps/api/src/starknet/index.ts @@ -0,0 +1,14 @@ +import Checkpoint, { starknet } from '@snapshot-labs/checkpoint'; +import { createConfig } from './config'; +import { createWriters } from './writers'; + +const snConfig = createConfig('sn'); +const snSepConfig = createConfig('sn-sep'); + +const snIndexer = new starknet.StarknetIndexer(createWriters(snConfig)); +const snSepIndexer = new starknet.StarknetIndexer(createWriters(snSepConfig)); + +export function addStarknetIndexers(checkpoint: Checkpoint) { + checkpoint.addIndexer(snConfig.indexerName, snConfig, snIndexer); + checkpoint.addIndexer(snSepConfig.indexerName, snSepConfig, snSepIndexer); +} diff --git a/apps/api/src/ipfs.ts b/apps/api/src/starknet/ipfs.ts similarity index 99% rename from apps/api/src/ipfs.ts rename to apps/api/src/starknet/ipfs.ts index 5d3bcaf27..9486c9f4f 100644 --- a/apps/api/src/ipfs.ts +++ b/apps/api/src/starknet/ipfs.ts @@ -11,7 +11,7 @@ import { SpaceMetadataItem, StrategiesParsedMetadataDataItem, VoteMetadataItem -} from '../.checkpoint/models'; +} from '../../.checkpoint/models'; export async function handleSpaceMetadata( space: string, diff --git a/apps/api/src/utils.ts b/apps/api/src/starknet/utils.ts similarity index 99% rename from apps/api/src/utils.ts rename to apps/api/src/starknet/utils.ts index 5714b98e4..91372d982 100644 --- a/apps/api/src/utils.ts +++ b/apps/api/src/starknet/utils.ts @@ -23,7 +23,7 @@ import { Space, StrategiesParsedMetadataItem, VotingPowerValidationStrategiesParsedMetadataItem -} from '../.checkpoint/models'; +} from '../../.checkpoint/models'; type StrategyConfig = { address: BigNumberish; diff --git a/apps/api/src/writer.ts b/apps/api/src/starknet/writers.ts similarity index 99% rename from apps/api/src/writer.ts rename to apps/api/src/starknet/writers.ts index dd11456b6..82bad500d 100644 --- a/apps/api/src/writer.ts +++ b/apps/api/src/starknet/writers.ts @@ -24,7 +24,7 @@ import { Space, User, Vote -} from '../.checkpoint/models'; +} from '../../.checkpoint/models'; type Strategy = { address: string; From e6d4ce78f40fa83caf4774146c946d097f3bb41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Tkaczy=C5=84ski?= <wiktor.tkaczynski@gmail.com> Date: Mon, 27 Jan 2025 16:30:33 +0100 Subject: [PATCH 3/6] chore: bump @checkpoint-labs/checkpoint --- apps/api/package.json | 2 +- yarn.lock | 57 ++++++++++++++++--------------------------- 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 62e78ef42..3979a75c1 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -23,7 +23,7 @@ "@ethersproject/providers": "^5.7.2", "@ethersproject/units": "^5.6.1", "@faker-js/faker": "^7.4.0", - "@snapshot-labs/checkpoint": "^0.1.0-beta.44", + "@snapshot-labs/checkpoint": "^0.1.0-beta.46", "@snapshot-labs/sx": "^0.1.0", "@types/bn.js": "^5.1.0", "@types/mysql": "^2.15.21", diff --git a/yarn.lock b/yarn.lock index 77536093a..bf4a149f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2420,6 +2420,14 @@ "@noble/hashes" "~1.3.2" "@scure/base" "~1.1.4" +"@scure/starknet@~0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@scure/starknet/-/starknet-0.3.0.tgz#b8273a42fc721025f8098b1f1d96368a7067e1c4" + integrity sha512-Ma66yZlwa5z00qI5alSxdWtIpky5LBhy22acVFdoC5kwwbd9uDyMWEYzWHdNyKmQg9t5Y2UOXzINMeb3yez+Gw== + dependencies: + "@noble/curves" "~1.2.0" + "@noble/hashes" "~1.3.2" + "@scure/starknet@~1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@scure/starknet/-/starknet-1.0.0.tgz#4419bc2fdf70f3dd6cb461d36c878c9ef4419f8c" @@ -2445,10 +2453,10 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== -"@snapshot-labs/checkpoint@^0.1.0-beta.44": - version "0.1.0-beta.44" - resolved "https://registry.yarnpkg.com/@snapshot-labs/checkpoint/-/checkpoint-0.1.0-beta.44.tgz#61bac9957fcaf1fcdbd819a99deba02bdc7705f8" - integrity sha512-SE4p0TLAwb28phlfKp02obBUm4wlAwpf+gxuv40t1ei3vuYGeBHeGhoOsvCIKaUDXqyUbicdwQ2BE2WDEU2HIQ== +"@snapshot-labs/checkpoint@^0.1.0-beta.46": + version "0.1.0-beta.46" + resolved "https://registry.yarnpkg.com/@snapshot-labs/checkpoint/-/checkpoint-0.1.0-beta.46.tgz#7a68ed67cb8f8952e2000b45f03d643c28303c8f" + integrity sha512-4u5G6+KZJFiUJXwtgrcgSz8++/KxapNMd/EkFi4HPynq4k5dgbQQrOvfmC4E/QcOeFpqup6cAi9egCiMcnpq1A== dependencies: "@ethersproject/abi" "^5.7.0" "@ethersproject/address" "^5.7.0" @@ -2469,7 +2477,7 @@ pino "^8.3.1" pino-pretty "^8.1.0" pluralize "^8.0.0" - starknet "^5.19.3" + starknet "~5.19.3" yargs "^17.7.2" zod "^3.21.4" @@ -4013,28 +4021,7 @@ abbrev@^2.0.0: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== -"abi-wan-kanabi-v1@npm:abi-wan-kanabi@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/abi-wan-kanabi/-/abi-wan-kanabi-1.0.3.tgz#0d8607f2a2ccb2151a69debea1c3bb68b76c5aa2" - integrity sha512-Xwva0AnhXx/IVlzo3/kwkI7Oa7ZX7codtcSn+Gmoa2PmjGPF/0jeVud9puasIPtB7V50+uBdUj4Mh3iATqtBvg== - dependencies: - abi-wan-kanabi "^1.0.1" - fs-extra "^10.0.0" - rome "^12.1.3" - typescript "^4.9.5" - yargs "^17.7.2" - -"abi-wan-kanabi-v2@npm:abi-wan-kanabi@^2.1.1": - version "2.2.3" - resolved "https://registry.yarnpkg.com/abi-wan-kanabi/-/abi-wan-kanabi-2.2.3.tgz#d1c410325aac866f31f3d589279a87b341e5641f" - integrity sha512-JlqiAl9CPvTm5kKG0QXmVCWNWoC/XyRMOeT77cQlbxXWllgjf6SqUmaNqFon72C2o5OSZids+5FvLdsw6dvWaw== - dependencies: - ansicolors "^0.3.2" - cardinal "^2.1.1" - fs-extra "^10.0.0" - yargs "^17.7.2" - -abi-wan-kanabi@^1.0.1: +abi-wan-kanabi@^1.0.1, abi-wan-kanabi@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/abi-wan-kanabi/-/abi-wan-kanabi-1.0.3.tgz#0d8607f2a2ccb2151a69debea1c3bb68b76c5aa2" integrity sha512-Xwva0AnhXx/IVlzo3/kwkI7Oa7ZX7codtcSn+Gmoa2PmjGPF/0jeVud9puasIPtB7V50+uBdUj4Mh3iATqtBvg== @@ -12150,16 +12137,14 @@ starknet@6.11.0: ts-mixer "^6.0.3" url-join "^4.0.1" -starknet@^5.19.3: - version "5.29.0" - resolved "https://registry.yarnpkg.com/starknet/-/starknet-5.29.0.tgz#26d8074340f26a2da4bc373169a1a5ed6dc9bedf" - integrity sha512-eEcd6uiYIwGvl8MtHOsXGBhREqjJk84M/qUkvPLQ3n/JAMkbKBGnygDlh+HAsvXJsGlMQfwrcVlm6KpDoPha7w== +starknet@^5.19.3, starknet@~5.19.3: + version "5.19.6" + resolved "https://registry.yarnpkg.com/starknet/-/starknet-5.19.6.tgz#61e43e083888ef64598473bc2e4bd3e1e07fe9d4" + integrity sha512-MyO48QKu+d8CBH/7ruI/RPrDwoFRNV/uxql2nBeA4ccqBXs698FkeGkMhZv++VIwf1MRajHcTM91KuyZQMyMLw== dependencies: - "@noble/curves" "~1.3.0" - "@scure/base" "~1.1.3" - "@scure/starknet" "~1.0.0" - abi-wan-kanabi-v1 "npm:abi-wan-kanabi@^1.0.3" - abi-wan-kanabi-v2 "npm:abi-wan-kanabi@^2.1.1" + "@noble/curves" "~1.2.0" + "@scure/starknet" "~0.3.0" + abi-wan-kanabi "^1.0.3" isomorphic-fetch "^3.0.0" lossless-json "^2.0.8" pako "^2.0.4" From 8348211d4d1f5c65c3e50afbabbf521f3b207bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Tkaczy=C5=84ski?= <wiktor.tkaczynski@gmail.com> Date: Mon, 27 Jan 2025 16:30:53 +0100 Subject: [PATCH 4/6] feat: use env variables for networkNodeUrls You can use either Infura with single variable (INFURA_API_KEY) or specify full URL for certain network: - NETWORK_NODE_URL_SN - NETWORK_NODE_URL_SN_SEP --- apps/api/src/starknet/config.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/api/src/starknet/config.ts b/apps/api/src/starknet/config.ts index cc3622cdc..760e456de 100644 --- a/apps/api/src/starknet/config.ts +++ b/apps/api/src/starknet/config.ts @@ -4,6 +4,17 @@ import { validateAndParseAddress } from 'starknet'; import spaceAbi from './abis/space.json'; import spaceFactoryAbi from './abis/spaceFactory.json'; +const DEFAULT_INFURA_API_KEY = + process.env.INFURA_API_KEY || '46a5dd9727bf48d4a132672d3f376146'; + +const snNetworkNodeUrl = + process.env.NETWORK_NODE_URL_SN || + `https://starknet-mainnet.infura.io/v3/${DEFAULT_INFURA_API_KEY}`; +const snSepNetworkNodeUrl = + process.env.NETWORK_NODE_URL_SN_SEP || + `https://starknet-sepolia.infura.io/v3/${DEFAULT_INFURA_API_KEY}`; +const manaRpcUrl = process.env.VITE_MANA_URL || 'https://mana.box'; + export type FullConfig = { indexerName: 'sn' | 'sn-sep'; overrides: ReturnType<typeof createOverrides>; @@ -12,8 +23,7 @@ export type FullConfig = { const CONFIG = { sn: { indexerName: 'sn', - networkNodeUrl: - 'https://starknet-mainnet.infura.io/v3/46a5dd9727bf48d4a132672d3f376146', + networkNodeUrl: snNetworkNodeUrl, l1NetworkNodeUrl: 'https://rpc.brovider.xyz/1', contract: starknetNetworks['sn'].Meta.spaceFactory, start: 445498, @@ -26,8 +36,7 @@ const CONFIG = { }, 'sn-sep': { indexerName: 'sn-sep', - networkNodeUrl: - 'https://starknet-sepolia.infura.io/v3/46a5dd9727bf48d4a132672d3f376146', + networkNodeUrl: snSepNetworkNodeUrl, l1NetworkNodeUrl: 'https://rpc.brovider.xyz/11155111', contract: starknetNetworks['sn-sep'].Meta.spaceFactory, start: 17960, @@ -37,8 +46,6 @@ const CONFIG = { } }; -const manaRpcUrl = process.env.VITE_MANA_URL || 'https://mana.box'; - function createOverrides(networkId: keyof typeof CONFIG) { const config = starknetNetworks[networkId]; From 5c33a496884c0a56d6811c3abe99a77704a5a002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Tkaczy=C5=84ski?= <wiktor.tkaczynski@gmail.com> Date: Mon, 27 Jan 2025 16:49:22 +0100 Subject: [PATCH 5/6] ci: disable api deployments --- .github/workflows/deploy.yml | 73 ++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f1457d42c..8204736ce 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,43 +6,44 @@ on: - master jobs: - deploy_api_mainnet: - name: Deploy API to mainnet - environment: api_mainnet - concurrency: - group: api_mainnet - cancel-in-progress: true - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v3 - with: - fetch-depth: 2 - - name: Install doctl - uses: digitalocean/action-doctl@v2 - with: - token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - - name: Deploy if files have changed - run: ./scripts/deploy-api-prod.sh + # Those are disabled as part of transition to unified API + # deploy_api_mainnet: + # name: Deploy API to mainnet + # environment: api_mainnet + # concurrency: + # group: api_mainnet + # cancel-in-progress: true + # runs-on: ubuntu-latest + # steps: + # - name: Checkout Repo + # uses: actions/checkout@v3 + # with: + # fetch-depth: 2 + # - name: Install doctl + # uses: digitalocean/action-doctl@v2 + # with: + # token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + # - name: Deploy if files have changed + # run: ./scripts/deploy-api-prod.sh - deploy_api_testnet: - name: Deploy API to testnet - environment: api_testnet - concurrency: - group: api_testnet - cancel-in-progress: true - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v3 - with: - fetch-depth: 2 - - name: Install doctl - uses: digitalocean/action-doctl@v2 - with: - token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - - name: Deploy if files have changed - run: ./scripts/deploy-changed.sh apps/api ${{ vars.DIGITALOCEAN_APP_ID }} + # deploy_api_testnet: + # name: Deploy API to testnet + # environment: api_testnet + # concurrency: + # group: api_testnet + # cancel-in-progress: true + # runs-on: ubuntu-latest + # steps: + # - name: Checkout Repo + # uses: actions/checkout@v3 + # with: + # fetch-depth: 2 + # - name: Install doctl + # uses: digitalocean/action-doctl@v2 + # with: + # token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + # - name: Deploy if files have changed + # run: ./scripts/deploy-changed.sh apps/api ${{ vars.DIGITALOCEAN_APP_ID }} deploy_mana_prod: name: Deploy mana to production From 1115aac8754ce72691d4ea437dc30e0555d8d5e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Tkaczy=C5=84ski?= <wiktor.tkaczynski@gmail.com> Date: Mon, 27 Jan 2025 16:59:59 +0100 Subject: [PATCH 6/6] test: move starknet utils tests --- apps/api/test/unit/{ => starknet}/utils.test.ts | 2 +- apps/api/test/unit/writer.test.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) rename apps/api/test/unit/{ => starknet}/utils.test.ts (95%) delete mode 100644 apps/api/test/unit/writer.test.ts diff --git a/apps/api/test/unit/utils.test.ts b/apps/api/test/unit/starknet/utils.test.ts similarity index 95% rename from apps/api/test/unit/utils.test.ts rename to apps/api/test/unit/starknet/utils.test.ts index 963e8bf32..90763b64e 100644 --- a/apps/api/test/unit/utils.test.ts +++ b/apps/api/test/unit/starknet/utils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { formatAddressVariant } from '../../src/utils'; +import { formatAddressVariant } from '../../../src/starknet/utils'; describe('formatAddressVariant', () => { it.each([ diff --git a/apps/api/test/unit/writer.test.ts b/apps/api/test/unit/writer.test.ts deleted file mode 100644 index 0f0924a01..000000000 --- a/apps/api/test/unit/writer.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -describe('smoke test', () => { - it('should pass', () => { - expect(true).toBe(true); - }); -});