Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Nouns Members URI #71

Merged
merged 5 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Implementations/API/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down
4 changes: 4 additions & 0 deletions Implementations/API/backend/functions/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
171 changes: 171 additions & 0 deletions Implementations/API/backend/functions/nouns/getMembers.ts
Original file line number Diff line number Diff line change
@@ -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 })
}

}
1 change: 1 addition & 0 deletions Implementations/API/stacks/MyStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
108 changes: 108 additions & 0 deletions Implementations/Nouns/README.md
Original file line number Diff line number Diff line change
@@ -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": "<https://daostar.org/members>",

"member": "<https://daostar.org/member>",

"checkpoint" : "<https://daostar.org/member>",

"memberId": "<https://daostar.org/memberId>",

"votingShares": "<https://daostar.org/moloch/votingShares>",

"nonVotingShares": "<https://daostar.org/moloch/nonvotingShares>",

"ethereum-address": "<https://daostar.org/EthereumAddress>",

"delegatee-ethereum-address": "<https://daostar.org/DelegateeEthereumAddress>",

},

"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