From 5f55f64d747ed6ab48b1729a879d7b30551c795b Mon Sep 17 00:00:00 2001 From: 0xRory <0x1rory@gmail.com> Date: Sun, 4 Jun 2023 21:45:59 +0800 Subject: [PATCH 1/5] feat: Creating Nouns Endpoints --- Implementations/API/README.md | 8 +- .../API/backend/functions/config.ts | 4 + .../API/backend/functions/nouns/getMembers.ts | 105 +++++++++++++++++ Implementations/API/stacks/MyStack.ts | 1 + Implementations/Nouns/README.md | 108 ++++++++++++++++++ 5 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 Implementations/API/backend/functions/nouns/getMembers.ts create mode 100644 Implementations/Nouns/README.md diff --git a/Implementations/API/README.md b/Implementations/API/README.md index 6959a54e..2aca02ea 100644 --- a/Implementations/API/README.md +++ b/Implementations/API/README.md @@ -11,11 +11,17 @@ Currently supported DAOs and frameworks: | DAOStack | Yes | Yes | No | Mainnet | | Aave | No | Yes | No | Mainnet | | Safe | Yes | Yes | Yes | Mainnet | - +| Nonus | Yes | No | No | Mainnet | It can be forked to support other DAO frameworks. +## Adding new DAO endpoints +In order to add a new DAO's endpoints, you'll need to follow a few steps. +1. adding the endpoint -> function handler pointer in `stacks/MyStack.ts` +2. creating the folder `functions//` +3. creating the handlers for each endpoint within the folder created in step 2. +4. creating the subgraph that the handlers will fetch information from. ## Moloch diff --git a/Implementations/API/backend/functions/config.ts b/Implementations/API/backend/functions/config.ts index 7716fb91..89daa242 100644 --- a/Implementations/API/backend/functions/config.ts +++ b/Implementations/API/backend/functions/config.ts @@ -24,4 +24,8 @@ export const gnosisApiConfig: { [key: string]: any } = { export const snapshotApiConfig: { [key: string]: any } = { '1': 'https://hub.snapshot.org/graphql' +} + +export const nonusApiConfig: { [key: string]: any } = { + '1': 'https://api.thegraph.com/subgraphs/name/nounsdao/nouns-subgraph' } \ No newline at end of file diff --git a/Implementations/API/backend/functions/nouns/getMembers.ts b/Implementations/API/backend/functions/nouns/getMembers.ts new file mode 100644 index 00000000..73adce68 --- /dev/null +++ b/Implementations/API/backend/functions/nouns/getMembers.ts @@ -0,0 +1,105 @@ +import { APIGatewayProxyHandlerV2 } from "aws-lambda"; +import { nonusApiConfig } from "functions/config"; +import fetch, { RequestInit } from 'node-fetch' + +function apiRequest(path: string, method: string, data: any) { + return fetch(path, { + headers: { + 'Content-Type': 'application/json', + }, + method, + redirect: 'follow', + body: JSON.stringify(data), + }).then((res) => res.json()) +} + +export const handler: APIGatewayProxyHandlerV2 = async (event) => { + const network = event?.pathParameters?.network + if (!network) return { statusCode: 400, message: 'Missing network' } + + console.log({ graphConfig: nonusApiConfig }) + const path = nonusApiConfig[network] + if (!path) return { statusCode: 400, message: 'Missing config for network' } + + const eventId = event?.pathParameters?.id + if (!eventId) return { statusCode: 400, message: 'Missing id' } + + const template = { + '@context': { + '@vocab': 'http://daostar.org/', + }, + type: 'DAO', + name: eventId, + } + + const query = `query { + accounts( where: {tokenBalance_not: "0"}) { + tokenBalance + id + nouns { + seed { + id + } + owner { + delegate { + id + delegatedVotes + } + } + } + } + }` + + const data = { + query, + variables: { dao: eventId }, + } + console.log({ data }) + + const res = (await apiRequest(path, 'POST', data)) as any + console.log({ res }) + + let members: any = []; + res.data.accounts.forEach((n: any) => { + let member: any = {} + //let tokenBalance = n.tokenBalance; + member['memberId'] = { + "@value": n.id, + "@type": "EthereumAddress", + } + let votingShares = Number(n.tokenBalance); + let nonvotingShares = 0; + let delegatee_ethereum_address: any = []; + let delegatedVotes = 0; + for (let index = 0; index < n.nouns.length; index++) { + const m = n.nouns[index]; + if (m.owner.delegate.id != n.id) { + votingShares = votingShares - 1; + nonvotingShares = nonvotingShares + 1; + + delegatee_ethereum_address.push({ + "@value": m.owner.delegate.id, + "@type": "EthereumAddress", + }) + } + delegatedVotes = m.owner.delegate.delegatedVotes; + } + + member['votingShares'] = votingShares; + member['nonVotingShares'] = nonvotingShares; + member['delegatedShares'] = delegatedVotes; + member['delegatee-ethereum-address'] = delegatee_ethereum_address; + members.push(member); + }) + + return members + ? { + statusCode: 200, + body: JSON.stringify(members) + } + : { + statusCode: 404, + body: JSON.stringify({ error: true }) + } + +} diff --git a/Implementations/API/stacks/MyStack.ts b/Implementations/API/stacks/MyStack.ts index 38926fdd..f93ed3f0 100644 --- a/Implementations/API/stacks/MyStack.ts +++ b/Implementations/API/stacks/MyStack.ts @@ -20,6 +20,7 @@ export function MyStack({ stack }: StackContext) { 'GET /daodao/proposals/{network}/{id}': 'functions/daodao/getProposals.handler', 'GET /snapshot/members/{id}': 'functions/snapshot/getMembers.handler', 'GET /snapshot/proposals/{id}': 'functions/snapshot/getProposals.handler', + 'GET /nouns/members/{network}/': 'functions/nouns/getMembers.handler', }, customDomain: { domainName: 'services.daostar.org', diff --git a/Implementations/Nouns/README.md b/Implementations/Nouns/README.md new file mode 100644 index 00000000..15162b5c --- /dev/null +++ b/Implementations/Nouns/README.md @@ -0,0 +1,108 @@ +# Nouns + +Nouns DAO uses a fork of Compound Governance. + +*This reference implementation has not been finalized. It is pending changes to reflect the most recent version of the DAO standard.* + +### Members URI + +In this implementation of membersURI, we define a member of the DAO as any address with a voting share OR non-voting shares greater than or equal to zero. votingShares represent the amount of token each address has and is not delegated into another address. nonVotingShares represent the number of tokens one delegated to another address. Also, delegatedShares indicates the total number of tokens delegated to the member’s address. + +We add a checkpoint property because the Nouns governance contract keeps track of the voting weights based on the block time by using checkpoints. Nouns governance token contract has a structure named Checkpoint which has two elements; fromBlock and votes. Each time a member transfers tokens to another party delegates the tokens to another party or cancels the delegation this structure for the member will be updated. fromBlock will be changed into the block number in which the change happen and votes will be changed in a proper way based on the transfer of voting right. + +Also, it should be mentioned that Nouns governance calculates the voting weight of each member, by calculating the voting power each member had at the starting time of each proposal. So, when a member decides to vote on a specific proposal, the smart contract is using checkpoints of the member to find the amount of voting power the member had in the start time of the proposal (the block time in which that specific proposal is submitted). + +So, to have the members and their voting power on each proposal we need to keep track of the checkpoints and votingShares, nonVotingShares and delegatedShares related to each of the checkpoints of the members. + +(For optimization we can just calculate the start time of the oldest ongoing proposal and remove the objects with checkpoints lesser than that from the membersURI JSON) + +We add "delegatee-ethereum-address" field to specify to which address the member delegated their voting right. + +```json +{ + + "@context": { + + "members": "", + + "member": "", + + "checkpoint" : "", + + "memberId": "", + + "votingShares": "", + + "nonVotingShares": "", + + "ethereum-address": "", + + "delegatee-ethereum-address": "", + + }, + + "members": [{ + + "memberId": { + + "@value": "0xabc123", + + "@type": "EthereumAddress" + + "checkpoint" : "12000000" + + }, + + "votingShares": "0", + + "nonVotingShares": "100", + + "delegatedShares" : "0", + + "delegatee-ethereum-address" : { + + "@value": "0xabc444", + + "@type": "EthereumAddress" + + } + + }, + + "memberId": { + + "@value": "0xdef987", + + "@type": "EthereumAddress" + + "checkpoint" : "12500000" + + }, + + "votingShares": "150", + + "nonvotingShares": "0", + + "delegatedShares" : "200", + + "delegatee-ethereum-address" : { + + "@value": "0xasfh85", + + "@type": "EthereumAddress" + + } + + } + + ] + +} +``` + +### Proposals URI + + + +## Compound + From 2fcb0a4825203e2c5298299620e5bbe252107c97 Mon Sep 17 00:00:00 2001 From: 0xRory <0x1rory@gmail.com> Date: Mon, 5 Jun 2023 06:07:52 +0800 Subject: [PATCH 2/5] Revert "feat: Creating Nouns Endpoints" This reverts commit 5f55f64d747ed6ab48b1729a879d7b30551c795b. --- Implementations/API/README.md | 8 +- .../API/backend/functions/config.ts | 4 - .../API/backend/functions/nouns/getMembers.ts | 105 ----------------- Implementations/API/stacks/MyStack.ts | 1 - Implementations/Nouns/README.md | 108 ------------------ 5 files changed, 1 insertion(+), 225 deletions(-) delete mode 100644 Implementations/API/backend/functions/nouns/getMembers.ts delete mode 100644 Implementations/Nouns/README.md diff --git a/Implementations/API/README.md b/Implementations/API/README.md index 2aca02ea..6959a54e 100644 --- a/Implementations/API/README.md +++ b/Implementations/API/README.md @@ -11,17 +11,11 @@ Currently supported DAOs and frameworks: | DAOStack | Yes | Yes | No | Mainnet | | Aave | No | Yes | No | Mainnet | | Safe | Yes | Yes | Yes | Mainnet | -| Nonus | Yes | No | No | Mainnet | + It can be forked to support other DAO frameworks. -## Adding new DAO endpoints -In order to add a new DAO's endpoints, you'll need to follow a few steps. -1. adding the endpoint -> function handler pointer in `stacks/MyStack.ts` -2. creating the folder `functions//` -3. creating the handlers for each endpoint within the folder created in step 2. -4. creating the subgraph that the handlers will fetch information from. ## Moloch diff --git a/Implementations/API/backend/functions/config.ts b/Implementations/API/backend/functions/config.ts index 89daa242..7716fb91 100644 --- a/Implementations/API/backend/functions/config.ts +++ b/Implementations/API/backend/functions/config.ts @@ -24,8 +24,4 @@ export const gnosisApiConfig: { [key: string]: any } = { export const snapshotApiConfig: { [key: string]: any } = { '1': 'https://hub.snapshot.org/graphql' -} - -export const nonusApiConfig: { [key: string]: any } = { - '1': 'https://api.thegraph.com/subgraphs/name/nounsdao/nouns-subgraph' } \ No newline at end of file diff --git a/Implementations/API/backend/functions/nouns/getMembers.ts b/Implementations/API/backend/functions/nouns/getMembers.ts deleted file mode 100644 index 73adce68..00000000 --- a/Implementations/API/backend/functions/nouns/getMembers.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { APIGatewayProxyHandlerV2 } from "aws-lambda"; -import { nonusApiConfig } from "functions/config"; -import fetch, { RequestInit } from 'node-fetch' - -function apiRequest(path: string, method: string, data: any) { - return fetch(path, { - headers: { - 'Content-Type': 'application/json', - }, - method, - redirect: 'follow', - body: JSON.stringify(data), - }).then((res) => res.json()) -} - -export const handler: APIGatewayProxyHandlerV2 = async (event) => { - const network = event?.pathParameters?.network - if (!network) return { statusCode: 400, message: 'Missing network' } - - console.log({ graphConfig: nonusApiConfig }) - const path = nonusApiConfig[network] - if (!path) return { statusCode: 400, message: 'Missing config for network' } - - const eventId = event?.pathParameters?.id - if (!eventId) return { statusCode: 400, message: 'Missing id' } - - const template = { - '@context': { - '@vocab': 'http://daostar.org/', - }, - type: 'DAO', - name: eventId, - } - - const query = `query { - accounts( where: {tokenBalance_not: "0"}) { - tokenBalance - id - nouns { - seed { - id - } - owner { - delegate { - id - delegatedVotes - } - } - } - } - }` - - const data = { - query, - variables: { dao: eventId }, - } - console.log({ data }) - - const res = (await apiRequest(path, 'POST', data)) as any - console.log({ res }) - - let members: any = []; - res.data.accounts.forEach((n: any) => { - let member: any = {} - //let tokenBalance = n.tokenBalance; - member['memberId'] = { - "@value": n.id, - "@type": "EthereumAddress", - } - let votingShares = Number(n.tokenBalance); - let nonvotingShares = 0; - let delegatee_ethereum_address: any = []; - let delegatedVotes = 0; - for (let index = 0; index < n.nouns.length; index++) { - const m = n.nouns[index]; - if (m.owner.delegate.id != n.id) { - votingShares = votingShares - 1; - nonvotingShares = nonvotingShares + 1; - - delegatee_ethereum_address.push({ - "@value": m.owner.delegate.id, - "@type": "EthereumAddress", - }) - } - delegatedVotes = m.owner.delegate.delegatedVotes; - } - - member['votingShares'] = votingShares; - member['nonVotingShares'] = nonvotingShares; - member['delegatedShares'] = delegatedVotes; - member['delegatee-ethereum-address'] = delegatee_ethereum_address; - members.push(member); - }) - - return members - ? { - statusCode: 200, - body: JSON.stringify(members) - } - : { - statusCode: 404, - body: JSON.stringify({ error: true }) - } - -} diff --git a/Implementations/API/stacks/MyStack.ts b/Implementations/API/stacks/MyStack.ts index f93ed3f0..38926fdd 100644 --- a/Implementations/API/stacks/MyStack.ts +++ b/Implementations/API/stacks/MyStack.ts @@ -20,7 +20,6 @@ export function MyStack({ stack }: StackContext) { 'GET /daodao/proposals/{network}/{id}': 'functions/daodao/getProposals.handler', 'GET /snapshot/members/{id}': 'functions/snapshot/getMembers.handler', 'GET /snapshot/proposals/{id}': 'functions/snapshot/getProposals.handler', - 'GET /nouns/members/{network}/': 'functions/nouns/getMembers.handler', }, customDomain: { domainName: 'services.daostar.org', diff --git a/Implementations/Nouns/README.md b/Implementations/Nouns/README.md deleted file mode 100644 index 15162b5c..00000000 --- a/Implementations/Nouns/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# Nouns - -Nouns DAO uses a fork of Compound Governance. - -*This reference implementation has not been finalized. It is pending changes to reflect the most recent version of the DAO standard.* - -### Members URI - -In this implementation of membersURI, we define a member of the DAO as any address with a voting share OR non-voting shares greater than or equal to zero. votingShares represent the amount of token each address has and is not delegated into another address. nonVotingShares represent the number of tokens one delegated to another address. Also, delegatedShares indicates the total number of tokens delegated to the member’s address. - -We add a checkpoint property because the Nouns governance contract keeps track of the voting weights based on the block time by using checkpoints. Nouns governance token contract has a structure named Checkpoint which has two elements; fromBlock and votes. Each time a member transfers tokens to another party delegates the tokens to another party or cancels the delegation this structure for the member will be updated. fromBlock will be changed into the block number in which the change happen and votes will be changed in a proper way based on the transfer of voting right. - -Also, it should be mentioned that Nouns governance calculates the voting weight of each member, by calculating the voting power each member had at the starting time of each proposal. So, when a member decides to vote on a specific proposal, the smart contract is using checkpoints of the member to find the amount of voting power the member had in the start time of the proposal (the block time in which that specific proposal is submitted). - -So, to have the members and their voting power on each proposal we need to keep track of the checkpoints and votingShares, nonVotingShares and delegatedShares related to each of the checkpoints of the members. - -(For optimization we can just calculate the start time of the oldest ongoing proposal and remove the objects with checkpoints lesser than that from the membersURI JSON) - -We add "delegatee-ethereum-address" field to specify to which address the member delegated their voting right. - -```json -{ - - "@context": { - - "members": "", - - "member": "", - - "checkpoint" : "", - - "memberId": "", - - "votingShares": "", - - "nonVotingShares": "", - - "ethereum-address": "", - - "delegatee-ethereum-address": "", - - }, - - "members": [{ - - "memberId": { - - "@value": "0xabc123", - - "@type": "EthereumAddress" - - "checkpoint" : "12000000" - - }, - - "votingShares": "0", - - "nonVotingShares": "100", - - "delegatedShares" : "0", - - "delegatee-ethereum-address" : { - - "@value": "0xabc444", - - "@type": "EthereumAddress" - - } - - }, - - "memberId": { - - "@value": "0xdef987", - - "@type": "EthereumAddress" - - "checkpoint" : "12500000" - - }, - - "votingShares": "150", - - "nonvotingShares": "0", - - "delegatedShares" : "200", - - "delegatee-ethereum-address" : { - - "@value": "0xasfh85", - - "@type": "EthereumAddress" - - } - - } - - ] - -} -``` - -### Proposals URI - - - -## Compound - From b7fc286e57da28db11ecd079462f81e63b517f14 Mon Sep 17 00:00:00 2001 From: 0xRory <0x1rory@gmail.com> Date: Mon, 5 Jun 2023 06:08:14 +0800 Subject: [PATCH 3/5] Revert "Revert "feat: Creating Nouns Endpoints"" This reverts commit 2fcb0a4825203e2c5298299620e5bbe252107c97. --- Implementations/API/README.md | 8 +- .../API/backend/functions/config.ts | 4 + .../API/backend/functions/nouns/getMembers.ts | 105 +++++++++++++++++ Implementations/API/stacks/MyStack.ts | 1 + Implementations/Nouns/README.md | 108 ++++++++++++++++++ 5 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 Implementations/API/backend/functions/nouns/getMembers.ts create mode 100644 Implementations/Nouns/README.md diff --git a/Implementations/API/README.md b/Implementations/API/README.md index 6959a54e..2aca02ea 100644 --- a/Implementations/API/README.md +++ b/Implementations/API/README.md @@ -11,11 +11,17 @@ Currently supported DAOs and frameworks: | DAOStack | Yes | Yes | No | Mainnet | | Aave | No | Yes | No | Mainnet | | Safe | Yes | Yes | Yes | Mainnet | - +| Nonus | Yes | No | No | Mainnet | It can be forked to support other DAO frameworks. +## Adding new DAO endpoints +In order to add a new DAO's endpoints, you'll need to follow a few steps. +1. adding the endpoint -> function handler pointer in `stacks/MyStack.ts` +2. creating the folder `functions//` +3. creating the handlers for each endpoint within the folder created in step 2. +4. creating the subgraph that the handlers will fetch information from. ## Moloch diff --git a/Implementations/API/backend/functions/config.ts b/Implementations/API/backend/functions/config.ts index 7716fb91..89daa242 100644 --- a/Implementations/API/backend/functions/config.ts +++ b/Implementations/API/backend/functions/config.ts @@ -24,4 +24,8 @@ export const gnosisApiConfig: { [key: string]: any } = { export const snapshotApiConfig: { [key: string]: any } = { '1': 'https://hub.snapshot.org/graphql' +} + +export const nonusApiConfig: { [key: string]: any } = { + '1': 'https://api.thegraph.com/subgraphs/name/nounsdao/nouns-subgraph' } \ No newline at end of file diff --git a/Implementations/API/backend/functions/nouns/getMembers.ts b/Implementations/API/backend/functions/nouns/getMembers.ts new file mode 100644 index 00000000..73adce68 --- /dev/null +++ b/Implementations/API/backend/functions/nouns/getMembers.ts @@ -0,0 +1,105 @@ +import { APIGatewayProxyHandlerV2 } from "aws-lambda"; +import { nonusApiConfig } from "functions/config"; +import fetch, { RequestInit } from 'node-fetch' + +function apiRequest(path: string, method: string, data: any) { + return fetch(path, { + headers: { + 'Content-Type': 'application/json', + }, + method, + redirect: 'follow', + body: JSON.stringify(data), + }).then((res) => res.json()) +} + +export const handler: APIGatewayProxyHandlerV2 = async (event) => { + const network = event?.pathParameters?.network + if (!network) return { statusCode: 400, message: 'Missing network' } + + console.log({ graphConfig: nonusApiConfig }) + const path = nonusApiConfig[network] + if (!path) return { statusCode: 400, message: 'Missing config for network' } + + const eventId = event?.pathParameters?.id + if (!eventId) return { statusCode: 400, message: 'Missing id' } + + const template = { + '@context': { + '@vocab': 'http://daostar.org/', + }, + type: 'DAO', + name: eventId, + } + + const query = `query { + accounts( where: {tokenBalance_not: "0"}) { + tokenBalance + id + nouns { + seed { + id + } + owner { + delegate { + id + delegatedVotes + } + } + } + } + }` + + const data = { + query, + variables: { dao: eventId }, + } + console.log({ data }) + + const res = (await apiRequest(path, 'POST', data)) as any + console.log({ res }) + + let members: any = []; + res.data.accounts.forEach((n: any) => { + let member: any = {} + //let tokenBalance = n.tokenBalance; + member['memberId'] = { + "@value": n.id, + "@type": "EthereumAddress", + } + let votingShares = Number(n.tokenBalance); + let nonvotingShares = 0; + let delegatee_ethereum_address: any = []; + let delegatedVotes = 0; + for (let index = 0; index < n.nouns.length; index++) { + const m = n.nouns[index]; + if (m.owner.delegate.id != n.id) { + votingShares = votingShares - 1; + nonvotingShares = nonvotingShares + 1; + + delegatee_ethereum_address.push({ + "@value": m.owner.delegate.id, + "@type": "EthereumAddress", + }) + } + delegatedVotes = m.owner.delegate.delegatedVotes; + } + + member['votingShares'] = votingShares; + member['nonVotingShares'] = nonvotingShares; + member['delegatedShares'] = delegatedVotes; + member['delegatee-ethereum-address'] = delegatee_ethereum_address; + members.push(member); + }) + + return members + ? { + statusCode: 200, + body: JSON.stringify(members) + } + : { + statusCode: 404, + body: JSON.stringify({ error: true }) + } + +} diff --git a/Implementations/API/stacks/MyStack.ts b/Implementations/API/stacks/MyStack.ts index 38926fdd..f93ed3f0 100644 --- a/Implementations/API/stacks/MyStack.ts +++ b/Implementations/API/stacks/MyStack.ts @@ -20,6 +20,7 @@ export function MyStack({ stack }: StackContext) { 'GET /daodao/proposals/{network}/{id}': 'functions/daodao/getProposals.handler', 'GET /snapshot/members/{id}': 'functions/snapshot/getMembers.handler', 'GET /snapshot/proposals/{id}': 'functions/snapshot/getProposals.handler', + 'GET /nouns/members/{network}/': 'functions/nouns/getMembers.handler', }, customDomain: { domainName: 'services.daostar.org', diff --git a/Implementations/Nouns/README.md b/Implementations/Nouns/README.md new file mode 100644 index 00000000..15162b5c --- /dev/null +++ b/Implementations/Nouns/README.md @@ -0,0 +1,108 @@ +# Nouns + +Nouns DAO uses a fork of Compound Governance. + +*This reference implementation has not been finalized. It is pending changes to reflect the most recent version of the DAO standard.* + +### Members URI + +In this implementation of membersURI, we define a member of the DAO as any address with a voting share OR non-voting shares greater than or equal to zero. votingShares represent the amount of token each address has and is not delegated into another address. nonVotingShares represent the number of tokens one delegated to another address. Also, delegatedShares indicates the total number of tokens delegated to the member’s address. + +We add a checkpoint property because the Nouns governance contract keeps track of the voting weights based on the block time by using checkpoints. Nouns governance token contract has a structure named Checkpoint which has two elements; fromBlock and votes. Each time a member transfers tokens to another party delegates the tokens to another party or cancels the delegation this structure for the member will be updated. fromBlock will be changed into the block number in which the change happen and votes will be changed in a proper way based on the transfer of voting right. + +Also, it should be mentioned that Nouns governance calculates the voting weight of each member, by calculating the voting power each member had at the starting time of each proposal. So, when a member decides to vote on a specific proposal, the smart contract is using checkpoints of the member to find the amount of voting power the member had in the start time of the proposal (the block time in which that specific proposal is submitted). + +So, to have the members and their voting power on each proposal we need to keep track of the checkpoints and votingShares, nonVotingShares and delegatedShares related to each of the checkpoints of the members. + +(For optimization we can just calculate the start time of the oldest ongoing proposal and remove the objects with checkpoints lesser than that from the membersURI JSON) + +We add "delegatee-ethereum-address" field to specify to which address the member delegated their voting right. + +```json +{ + + "@context": { + + "members": "", + + "member": "", + + "checkpoint" : "", + + "memberId": "", + + "votingShares": "", + + "nonVotingShares": "", + + "ethereum-address": "", + + "delegatee-ethereum-address": "", + + }, + + "members": [{ + + "memberId": { + + "@value": "0xabc123", + + "@type": "EthereumAddress" + + "checkpoint" : "12000000" + + }, + + "votingShares": "0", + + "nonVotingShares": "100", + + "delegatedShares" : "0", + + "delegatee-ethereum-address" : { + + "@value": "0xabc444", + + "@type": "EthereumAddress" + + } + + }, + + "memberId": { + + "@value": "0xdef987", + + "@type": "EthereumAddress" + + "checkpoint" : "12500000" + + }, + + "votingShares": "150", + + "nonvotingShares": "0", + + "delegatedShares" : "200", + + "delegatee-ethereum-address" : { + + "@value": "0xasfh85", + + "@type": "EthereumAddress" + + } + + } + + ] + +} +``` + +### Proposals URI + + + +## Compound + From 2849ae953d27b3203e7ce584582d68ce654f98df Mon Sep 17 00:00:00 2001 From: 0xRory <0x1rory@gmail.com> Date: Mon, 5 Jun 2023 06:08:28 +0800 Subject: [PATCH 4/5] Revert "Revert "Revert "feat: Creating Nouns Endpoints""" This reverts commit b7fc286e57da28db11ecd079462f81e63b517f14. --- Implementations/API/README.md | 8 +- .../API/backend/functions/config.ts | 4 - .../API/backend/functions/nouns/getMembers.ts | 105 ----------------- Implementations/API/stacks/MyStack.ts | 1 - Implementations/Nouns/README.md | 108 ------------------ 5 files changed, 1 insertion(+), 225 deletions(-) delete mode 100644 Implementations/API/backend/functions/nouns/getMembers.ts delete mode 100644 Implementations/Nouns/README.md diff --git a/Implementations/API/README.md b/Implementations/API/README.md index 2aca02ea..6959a54e 100644 --- a/Implementations/API/README.md +++ b/Implementations/API/README.md @@ -11,17 +11,11 @@ Currently supported DAOs and frameworks: | DAOStack | Yes | Yes | No | Mainnet | | Aave | No | Yes | No | Mainnet | | Safe | Yes | Yes | Yes | Mainnet | -| Nonus | Yes | No | No | Mainnet | + It can be forked to support other DAO frameworks. -## Adding new DAO endpoints -In order to add a new DAO's endpoints, you'll need to follow a few steps. -1. adding the endpoint -> function handler pointer in `stacks/MyStack.ts` -2. creating the folder `functions//` -3. creating the handlers for each endpoint within the folder created in step 2. -4. creating the subgraph that the handlers will fetch information from. ## Moloch diff --git a/Implementations/API/backend/functions/config.ts b/Implementations/API/backend/functions/config.ts index 89daa242..7716fb91 100644 --- a/Implementations/API/backend/functions/config.ts +++ b/Implementations/API/backend/functions/config.ts @@ -24,8 +24,4 @@ export const gnosisApiConfig: { [key: string]: any } = { export const snapshotApiConfig: { [key: string]: any } = { '1': 'https://hub.snapshot.org/graphql' -} - -export const nonusApiConfig: { [key: string]: any } = { - '1': 'https://api.thegraph.com/subgraphs/name/nounsdao/nouns-subgraph' } \ No newline at end of file diff --git a/Implementations/API/backend/functions/nouns/getMembers.ts b/Implementations/API/backend/functions/nouns/getMembers.ts deleted file mode 100644 index 73adce68..00000000 --- a/Implementations/API/backend/functions/nouns/getMembers.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { APIGatewayProxyHandlerV2 } from "aws-lambda"; -import { nonusApiConfig } from "functions/config"; -import fetch, { RequestInit } from 'node-fetch' - -function apiRequest(path: string, method: string, data: any) { - return fetch(path, { - headers: { - 'Content-Type': 'application/json', - }, - method, - redirect: 'follow', - body: JSON.stringify(data), - }).then((res) => res.json()) -} - -export const handler: APIGatewayProxyHandlerV2 = async (event) => { - const network = event?.pathParameters?.network - if (!network) return { statusCode: 400, message: 'Missing network' } - - console.log({ graphConfig: nonusApiConfig }) - const path = nonusApiConfig[network] - if (!path) return { statusCode: 400, message: 'Missing config for network' } - - const eventId = event?.pathParameters?.id - if (!eventId) return { statusCode: 400, message: 'Missing id' } - - const template = { - '@context': { - '@vocab': 'http://daostar.org/', - }, - type: 'DAO', - name: eventId, - } - - const query = `query { - accounts( where: {tokenBalance_not: "0"}) { - tokenBalance - id - nouns { - seed { - id - } - owner { - delegate { - id - delegatedVotes - } - } - } - } - }` - - const data = { - query, - variables: { dao: eventId }, - } - console.log({ data }) - - const res = (await apiRequest(path, 'POST', data)) as any - console.log({ res }) - - let members: any = []; - res.data.accounts.forEach((n: any) => { - let member: any = {} - //let tokenBalance = n.tokenBalance; - member['memberId'] = { - "@value": n.id, - "@type": "EthereumAddress", - } - let votingShares = Number(n.tokenBalance); - let nonvotingShares = 0; - let delegatee_ethereum_address: any = []; - let delegatedVotes = 0; - for (let index = 0; index < n.nouns.length; index++) { - const m = n.nouns[index]; - if (m.owner.delegate.id != n.id) { - votingShares = votingShares - 1; - nonvotingShares = nonvotingShares + 1; - - delegatee_ethereum_address.push({ - "@value": m.owner.delegate.id, - "@type": "EthereumAddress", - }) - } - delegatedVotes = m.owner.delegate.delegatedVotes; - } - - member['votingShares'] = votingShares; - member['nonVotingShares'] = nonvotingShares; - member['delegatedShares'] = delegatedVotes; - member['delegatee-ethereum-address'] = delegatee_ethereum_address; - members.push(member); - }) - - return members - ? { - statusCode: 200, - body: JSON.stringify(members) - } - : { - statusCode: 404, - body: JSON.stringify({ error: true }) - } - -} diff --git a/Implementations/API/stacks/MyStack.ts b/Implementations/API/stacks/MyStack.ts index f93ed3f0..38926fdd 100644 --- a/Implementations/API/stacks/MyStack.ts +++ b/Implementations/API/stacks/MyStack.ts @@ -20,7 +20,6 @@ export function MyStack({ stack }: StackContext) { 'GET /daodao/proposals/{network}/{id}': 'functions/daodao/getProposals.handler', 'GET /snapshot/members/{id}': 'functions/snapshot/getMembers.handler', 'GET /snapshot/proposals/{id}': 'functions/snapshot/getProposals.handler', - 'GET /nouns/members/{network}/': 'functions/nouns/getMembers.handler', }, customDomain: { domainName: 'services.daostar.org', diff --git a/Implementations/Nouns/README.md b/Implementations/Nouns/README.md deleted file mode 100644 index 15162b5c..00000000 --- a/Implementations/Nouns/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# Nouns - -Nouns DAO uses a fork of Compound Governance. - -*This reference implementation has not been finalized. It is pending changes to reflect the most recent version of the DAO standard.* - -### Members URI - -In this implementation of membersURI, we define a member of the DAO as any address with a voting share OR non-voting shares greater than or equal to zero. votingShares represent the amount of token each address has and is not delegated into another address. nonVotingShares represent the number of tokens one delegated to another address. Also, delegatedShares indicates the total number of tokens delegated to the member’s address. - -We add a checkpoint property because the Nouns governance contract keeps track of the voting weights based on the block time by using checkpoints. Nouns governance token contract has a structure named Checkpoint which has two elements; fromBlock and votes. Each time a member transfers tokens to another party delegates the tokens to another party or cancels the delegation this structure for the member will be updated. fromBlock will be changed into the block number in which the change happen and votes will be changed in a proper way based on the transfer of voting right. - -Also, it should be mentioned that Nouns governance calculates the voting weight of each member, by calculating the voting power each member had at the starting time of each proposal. So, when a member decides to vote on a specific proposal, the smart contract is using checkpoints of the member to find the amount of voting power the member had in the start time of the proposal (the block time in which that specific proposal is submitted). - -So, to have the members and their voting power on each proposal we need to keep track of the checkpoints and votingShares, nonVotingShares and delegatedShares related to each of the checkpoints of the members. - -(For optimization we can just calculate the start time of the oldest ongoing proposal and remove the objects with checkpoints lesser than that from the membersURI JSON) - -We add "delegatee-ethereum-address" field to specify to which address the member delegated their voting right. - -```json -{ - - "@context": { - - "members": "", - - "member": "", - - "checkpoint" : "", - - "memberId": "", - - "votingShares": "", - - "nonVotingShares": "", - - "ethereum-address": "", - - "delegatee-ethereum-address": "", - - }, - - "members": [{ - - "memberId": { - - "@value": "0xabc123", - - "@type": "EthereumAddress" - - "checkpoint" : "12000000" - - }, - - "votingShares": "0", - - "nonVotingShares": "100", - - "delegatedShares" : "0", - - "delegatee-ethereum-address" : { - - "@value": "0xabc444", - - "@type": "EthereumAddress" - - } - - }, - - "memberId": { - - "@value": "0xdef987", - - "@type": "EthereumAddress" - - "checkpoint" : "12500000" - - }, - - "votingShares": "150", - - "nonvotingShares": "0", - - "delegatedShares" : "200", - - "delegatee-ethereum-address" : { - - "@value": "0xasfh85", - - "@type": "EthereumAddress" - - } - - } - - ] - -} -``` - -### Proposals URI - - - -## Compound - From 1467c77d198560c214c059320cb1456d5bad7124 Mon Sep 17 00:00:00 2001 From: 0xRory <0x1rory@gmail.com> Date: Wed, 7 Jun 2023 23:21:32 +0800 Subject: [PATCH 5/5] feat: add Nouns get Members URI --- Implementations/API/README.md | 3 +- .../API/backend/functions/config.ts | 4 + .../API/backend/functions/nouns/getMembers.ts | 171 ++++++++++++++++++ Implementations/API/stacks/MyStack.ts | 1 + Implementations/Nouns/README.md | 108 +++++++++++ 5 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 Implementations/API/backend/functions/nouns/getMembers.ts create mode 100644 Implementations/Nouns/README.md diff --git a/Implementations/API/README.md b/Implementations/API/README.md index 6959a54e..98ebe349 100644 --- a/Implementations/API/README.md +++ b/Implementations/API/README.md @@ -11,12 +11,11 @@ Currently supported DAOs and frameworks: | DAOStack | Yes | Yes | No | Mainnet | | Aave | No | Yes | No | Mainnet | | Safe | Yes | Yes | Yes | Mainnet | - +| Nouns | Yes | | | Mainnet | It can be forked to support other DAO frameworks. - ## Moloch *This reference implementation has not been finalized. It is pending changes to reflect the most recent version of the DAO standard.* diff --git a/Implementations/API/backend/functions/config.ts b/Implementations/API/backend/functions/config.ts index 7716fb91..89daa242 100644 --- a/Implementations/API/backend/functions/config.ts +++ b/Implementations/API/backend/functions/config.ts @@ -24,4 +24,8 @@ export const gnosisApiConfig: { [key: string]: any } = { export const snapshotApiConfig: { [key: string]: any } = { '1': 'https://hub.snapshot.org/graphql' +} + +export const nonusApiConfig: { [key: string]: any } = { + '1': 'https://api.thegraph.com/subgraphs/name/nounsdao/nouns-subgraph' } \ No newline at end of file diff --git a/Implementations/API/backend/functions/nouns/getMembers.ts b/Implementations/API/backend/functions/nouns/getMembers.ts new file mode 100644 index 00000000..b181073f --- /dev/null +++ b/Implementations/API/backend/functions/nouns/getMembers.ts @@ -0,0 +1,171 @@ +import { APIGatewayProxyHandlerV2 } from "aws-lambda"; +import { ConstructorFragment } from "ethers/lib/utils"; +import { nonusApiConfig } from "functions/config"; +import fetch, { RequestInit } from 'node-fetch' + +function apiRequest(path: string, method: string, data: any) { + return fetch(path, { + headers: { + 'Content-Type': 'application/json', + }, + method, + redirect: 'follow', + body: JSON.stringify(data), + }).then((res) => res.json()) +} + +async function getRes(query: string, path: string) { + const data = { + query + } + + const res = (await apiRequest(path, 'POST', data)) as any + return res; +} + +export const handler: APIGatewayProxyHandlerV2 = async (event) => { + const network = event?.pathParameters?.network + if (!network) return { statusCode: 400, message: 'Missing network' } + + console.log({ graphConfig: nonusApiConfig }) + const path = nonusApiConfig[network] + if (!path) return { statusCode: 400, message: 'Missing config for network' } + + const eventId = event?.pathParameters?.id + if (!eventId) return { statusCode: 400, message: 'Missing id' } + + const template = { + '@context': { + '@vocab': 'http://daostar.org/', + }, + type: 'DAO', + name: eventId, + } + + let res = await getRes(`query { + accounts( where: {tokenBalance_not: "0"},first:1000) { + tokenBalance + id + nouns { + seed { + id + } + owner { + delegate { + id + } + } + } + } + delegates(first: 1000) { + id + delegatedVotes + nounsRepresented { + id + owner { + id + } + } + } + }`, path); + + let res_1 = await getRes(`query { + delegates(skip: 1000, first: 1000) { + id + delegatedVotes + nounsRepresented { + id + owner { + id + } + } + } + }`, path); + + + let members: any = []; + let delegates = res.data.delegates.concat(res_1.data.delegates); + // accounts + for (let index = 0; index < res.data.accounts.length; index++) { + const n = res.data.accounts[index]; + let member: any = {} + //let tokenBalance = n.tokenBalance; + member['memberId'] = { + "@value": n.id, + "@type": "EthereumAddress", + } + let votingShares = Number(n.tokenBalance); + let nonvotingShares = 0; + let delegatee_ethereum_address: any = []; + let delegate = null; + //let delegatedVotes = 0; + for (let index = 0; index < n.nouns.length; index++) { + const m = n.nouns[index]; + if (m.owner.delegate.id != n.id) { + votingShares = votingShares - 1; + nonvotingShares = nonvotingShares + 1; + } + } + delegate = delegates.find((d: any) => { + return d.id == n.id + }); + delegates = delegates.filter((d: any) => { + return d.id != n.id + }); + + member['votingShares'] = votingShares; + member['nonVotingShares'] = nonvotingShares; + member['delegatedShares'] = delegate.delegatedVotes; + if (delegate.delegatedVotes > 0) { + delegate.nounsRepresented.forEach((m: any) => { + delegatee_ethereum_address.push({ + "@value": m.owner.id, + "@type": "EthereumAddress", + }) + }); + } + + member['delegatee-ethereum-address'] = delegatee_ethereum_address; + members.push(member); + + } + + for (let index = 0; index < delegates.length; index++) { + const n = delegates[index]; + if (n.delegatedVotes > 0) { + let member: any = {} + //let tokenBalance = n.tokenBalance; + member['memberId'] = { + "@value": n.id, + "@type": "EthereumAddress", + } + + let delegatee_ethereum_address: any = []; + + //let delegatedVotes = 0; + + member['votingShares'] = 0; + member['nonVotingShares'] = 0; + member['delegatedShares'] = n.delegatedVotes; + n.nounsRepresented.forEach((m: any) => { + delegatee_ethereum_address.push({ + "@value": m.owner.id, + "@type": "EthereumAddress", + }) + }); + member['delegatee-ethereum-address'] = delegatee_ethereum_address; + members.push(member); + } + } + const transformed = { members: members, ...template }; + return transformed + ? { + statusCode: 200, + body: JSON.stringify(transformed) + } + : { + statusCode: 404, + body: JSON.stringify({ error: true }) + } + +} diff --git a/Implementations/API/stacks/MyStack.ts b/Implementations/API/stacks/MyStack.ts index 38926fdd..bbbcdb4a 100644 --- a/Implementations/API/stacks/MyStack.ts +++ b/Implementations/API/stacks/MyStack.ts @@ -20,6 +20,7 @@ export function MyStack({ stack }: StackContext) { 'GET /daodao/proposals/{network}/{id}': 'functions/daodao/getProposals.handler', 'GET /snapshot/members/{id}': 'functions/snapshot/getMembers.handler', 'GET /snapshot/proposals/{id}': 'functions/snapshot/getProposals.handler', + 'GET /nouns/members/{network}/{id}': 'functions/nouns/getMembers.handler', }, customDomain: { domainName: 'services.daostar.org', diff --git a/Implementations/Nouns/README.md b/Implementations/Nouns/README.md new file mode 100644 index 00000000..15162b5c --- /dev/null +++ b/Implementations/Nouns/README.md @@ -0,0 +1,108 @@ +# Nouns + +Nouns DAO uses a fork of Compound Governance. + +*This reference implementation has not been finalized. It is pending changes to reflect the most recent version of the DAO standard.* + +### Members URI + +In this implementation of membersURI, we define a member of the DAO as any address with a voting share OR non-voting shares greater than or equal to zero. votingShares represent the amount of token each address has and is not delegated into another address. nonVotingShares represent the number of tokens one delegated to another address. Also, delegatedShares indicates the total number of tokens delegated to the member’s address. + +We add a checkpoint property because the Nouns governance contract keeps track of the voting weights based on the block time by using checkpoints. Nouns governance token contract has a structure named Checkpoint which has two elements; fromBlock and votes. Each time a member transfers tokens to another party delegates the tokens to another party or cancels the delegation this structure for the member will be updated. fromBlock will be changed into the block number in which the change happen and votes will be changed in a proper way based on the transfer of voting right. + +Also, it should be mentioned that Nouns governance calculates the voting weight of each member, by calculating the voting power each member had at the starting time of each proposal. So, when a member decides to vote on a specific proposal, the smart contract is using checkpoints of the member to find the amount of voting power the member had in the start time of the proposal (the block time in which that specific proposal is submitted). + +So, to have the members and their voting power on each proposal we need to keep track of the checkpoints and votingShares, nonVotingShares and delegatedShares related to each of the checkpoints of the members. + +(For optimization we can just calculate the start time of the oldest ongoing proposal and remove the objects with checkpoints lesser than that from the membersURI JSON) + +We add "delegatee-ethereum-address" field to specify to which address the member delegated their voting right. + +```json +{ + + "@context": { + + "members": "", + + "member": "", + + "checkpoint" : "", + + "memberId": "", + + "votingShares": "", + + "nonVotingShares": "", + + "ethereum-address": "", + + "delegatee-ethereum-address": "", + + }, + + "members": [{ + + "memberId": { + + "@value": "0xabc123", + + "@type": "EthereumAddress" + + "checkpoint" : "12000000" + + }, + + "votingShares": "0", + + "nonVotingShares": "100", + + "delegatedShares" : "0", + + "delegatee-ethereum-address" : { + + "@value": "0xabc444", + + "@type": "EthereumAddress" + + } + + }, + + "memberId": { + + "@value": "0xdef987", + + "@type": "EthereumAddress" + + "checkpoint" : "12500000" + + }, + + "votingShares": "150", + + "nonvotingShares": "0", + + "delegatedShares" : "200", + + "delegatee-ethereum-address" : { + + "@value": "0xasfh85", + + "@type": "EthereumAddress" + + } + + } + + ] + +} +``` + +### Proposals URI + + + +## Compound +