├── img
└── design.jpg
├── python
├── .DS_Store
├── readme.md
└── pop_api.py
├── js
├── readme.md
└── pop_api.js
├── readme.md
└── integration_guide.md
/img/design.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitcoinco/pop_api/HEAD/img/design.jpg
--------------------------------------------------------------------------------
/python/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitcoinco/pop_api/HEAD/python/.DS_Store
--------------------------------------------------------------------------------
/js/readme.md:
--------------------------------------------------------------------------------
1 | proofofpersonhood.com SDKs > JS
2 |
3 | to run, load the pop_api.js:
4 |
5 |
6 | then run this code
7 |
8 | ```
9 |
10 | let addr = '0x00De4B13153673BCAE2616b67bf822500d325Fc3';
11 | var ps = get_personhoodscore('rinkeby', '0x00De4B13153673BCAE2616b67bf822500d325Fc3');
12 | console.log('address', addr, 'personhood score:' , ps)
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # proofofpersonhood.com
2 |
3 | ## API Clients
4 |
5 | *version 0.0.1*
6 |
7 | Available in
8 | - [js/](js/)
9 | - [python/](python/)
10 |
11 | Click into each subdirectory to get sample code, or checkout the [integration guide](integration_guide.md).
12 |
13 | # Other Resources For Proof of Personhood
14 | - [Marketing Site](https://proofofpersonhood.com/)
15 | - [Marketing Site Code](https://github.com/gitcoinco/proofofpersonhood_com/)
16 | - [Smart Contract (NFT) Code](https://github.com/gitcoinco/PersonhoodPassport)
17 | - [API Clients](https://github.com/gitcoinco/pop_api)
18 | - [Gitcoin Trust Bonus tab](https://gitcoin.co/profile/trust)
19 |
20 |
21 |
--------------------------------------------------------------------------------
/python/readme.md:
--------------------------------------------------------------------------------
1 | proofofpersonhood.com SDKs > python
2 |
3 | to run, from this folder:
4 |
5 | ```
6 | pip3 install web3==4.5.0
7 | python3
8 |
9 | ```
10 |
11 | then within python
12 |
13 | ```
14 | # imports
15 | from pop_api import get_personhoodscore
16 | import web3
17 |
18 | #set up
19 | network = 'rinkeby'
20 | infura_api_key = 'TODO'
21 | w3 = web3.Web3(web3.Web3.HTTPProvider(f'https://{network}.infura.io/v3/{infura_api_key}'))
22 |
23 | # make the call
24 | addresses = [
25 | '0x00De4B13153673BCAE2616b67bf822500d325Fc3',
26 | '0xDDF369C3bf18b1B12EA295d597B943b955eF4671',
27 | ]
28 | for address in addresses:
29 | score = get_personhoodscore(w3, network, address)
30 | print(f"{address} personhood score is {score}")
31 |
32 |
33 | ```
34 |
--------------------------------------------------------------------------------
/integration_guide.md:
--------------------------------------------------------------------------------
1 | # Proof of Personhood passport (PoPP) Integration Guide
2 |
3 | ## Table of Contents
4 |
5 | - [Proof of Personhood passport (PoPP) Integration Guide](#proof-of-personhood-passport--popp--integration-guide)
6 | * [Preface](#preface)
7 | * [Why](#why)
8 | * [What](#what)
9 | + [PersonhoodScore](#personhoodscore)
10 | + [System Design](#system-design)
11 | - [PersonhoodScore](#personhoodscore-1)
12 | - [Privacy](#privacy)
13 | - [System Design](#system-design-1)
14 | - [Issuance Patterns](#issuance-patterns)
15 | * [NFTs](#nfts)
16 | * [DIDs](#dids)
17 | - [Consumption](#consumption)
18 |
19 |
20 | ## Preface
21 |
22 | ALPHA WARNING - This project is in alpha, version 0.0.1.
23 |
24 | ## Why
25 |
26 | Let's start with why proof of personhood matters.
27 |
28 | Right now the crypto ecosystem is pretty centric around capital-intensive use cases. dApps built around one-token-one-vote and one-cpu-one-vote favour those who already have capital. This is a self-reinforcing cycle in which most dApps are speculative in nature, and that means labour (which may not have capital), cannot participate.
29 |
30 | We want to start building a foundation for the ecosystem to prove personhood. We think this is going to unlock a whole ecosystem of one-human-one-vote use cases. Stuff like
31 |
32 | - quadratic funding
33 | - quadratic voting
34 | - UBI
35 | - one-person-one-vote DAOs
36 | - data collectives
37 | - sybil resistent airdrops
38 | - + other use cases we havent discovered yet!
39 |
40 | ## What
41 |
42 | The team behind Gitcoin is building [PoPP](http://proofofpersonhood.com/) - a tool thats going to allow users to take their Gitcoin Grants sybil resistent identity across the dweb with them. We think this could help unlock a whole ecosystem of one-human-one-vote use cases.
43 |
44 | We have put together an alpha implementation of this system which you can see here https://github.com/gitcoinco/pop_api and https://github.com/gitcoinco/PersonhoodPassport .
45 |
46 | ### PersonhoodScore
47 |
48 | Basically the TLDR is that the Gitcoin system issues the user an SDK that allows them to issue a NFT (and soon an DID) to take their PersonhoodScore (PS) with them across the dweb. The higher the personhoodscore, the higher the price of forgery for that identity. A user with a $1 PS has an identity that costs $1 to forge. A user with a $100 PS has an identity that costs $100 to forge, and so on.
49 |
50 | Note: Proof of Personhood NFT is already deployed to rinkeby, and will be integrated into Gitcoin's site for alpha users soon, but it is recommended for the build out of this site that you deploy your own version, which can easily be done with the setup instructions in https://github.com/gitcoinco/PersonhoodPassport .
51 |
52 | ### System Design
53 |
54 | At a high level, we've discovered that a proof of personhood solution needs a few things.
55 |
56 | 1. it needs an economic incentive for people to participate.
57 | 2. it needs a solid architecture for seperating the bot-farms from the humans.
58 |
59 | To solve for (1), we are leveraging Gitcoin Grants, an $1.5mm/quarter economic system that is dependant upon sybil resistence.
60 |
61 | To solve for (2), Proof of Personhood passport's approach is to recognize that while each of us out there is either a binary - human-or-not, that with limited capital, we will never ever ever be 100% sure of proof of personhood. For this reason, personhood in the PoPP system is represented as a floating point number, not a binary.
62 |
63 | #### PersonhoodScore
64 |
65 | We call this number the personhoodscore. The higher the personhoodscore, the higher the price of forgery for that identity. A user with a $1 PS has an identity that costs $1 to forge. A user with a $100 PS has an identity that costs $100 to forge, and so on.
66 |
67 | In order to increase Personhood Scores on PoPP, PoPP aggregates sybil resistence mechanisms across the space. For example, a user who wishes to increase their PersonhoodScore, can verify their identity on:
68 |
69 | - BrightID
70 | - Idena Network
71 | - POAP
72 | - SMS
73 | - Google
74 | - Twitter
75 | - Upala
76 | - ENS
77 | - Facebook
78 |
79 | All sybil-resistent identity providers are not created equal, and it would be foolish to presume tha the cost of forgery for a twitter account == an Idena address. For this reason, each identity provider has a PersonhoodScore Weight associated with it. This weight is initially set as follws
80 |
81 | - BrightID (25)
82 | - Idena Network (25)
83 | - POAP (10)
84 | - SMS (2)
85 | - Google (2)
86 | - Twitter (5)
87 | - Upala (0.1)
88 | - ENS (0.1)
89 | - Facebook (0.1)
90 |
91 | We plan to eventually launch a governance process which can review attacks on the system + respond by adjusting the weights of each identity provider. It is possible that we may have algorithmic weights too (e.g a twitter accounts weigh is `min(months_old, max(20, num_followers * 0.001))` instead of just `5` )
92 |
93 | #### Privacy
94 |
95 | Before we get further in the integration guide, let's talk privacy.
96 |
97 | The goal of this project to empower internet citizens to better control their personal data. Data dignity means taking our social network data from site to site but also minimizing the risk of yet-another-privacy-destroying-data-leak.
98 |
99 | For this reason, we have deliberately narrowed the scope of PoPP to ONLY contain information about PersonhoodScore, and have *no personally identifiable information available* to consumers of PoPP.
100 |
101 | #### System Design
102 |
103 | At it's highest level, the system
104 |
105 | 1. integrates the identity providers listed above
106 | 2. computes a personhood score
107 | 3. issues credentials which users can take across the dWeb
108 |
109 |
110 |
111 | Below are the details of some of the ways PoPP is issued:
112 |
113 | #### Issuance Patterns
114 |
115 | ##### NFTs
116 |
117 | NFTs are issued one-per-address and one-per-account. The token URI o of the NFT resolves to a JSON blob that looks like this:
118 |
119 | ```
120 | {
121 | "name": "Personhood Passport",
122 | "description": "Proof of Personhood Passport (PoPP) is a transportable proof of personhood identity for the web3 space.",
123 | "image": image_url,
124 | 'external_url': 'https://proofofpersonhood.com',
125 | 'background_color': 'fbfbfb',
126 | "attributes": [
127 | {
128 | "trait_type": "personhood_score",
129 | "value": personhood_score,
130 | },
131 | {
132 | "trait_type": "cost_of_forgery",
133 | "value": cost_of_forgery,
134 | },
135 | ],
136 | }
137 | ```
138 |
139 | The `personhood_score` attribute is the attribute that consumers of the NFT will should care about.
140 |
141 |
142 | ##### DIDs
143 |
144 | Coming soon
145 |
146 |
147 | #### Consumption
148 |
149 |
150 | Consumption of this API is available in various API clients:
151 |
152 | - [js/](js/)
153 | - [python/](python/)
154 |
155 |
156 | ## Warnings, Roadmap, and todos
157 |
158 | Right now this project is a proof of concept, and the following warnings should ube noted:
159 |
160 | - Alpha Warning - This project is in alpha, version 0.0.1.
161 | - Execution Centralization Warning - Due to an Oracle problem with how the integrations are managed, the PersonhoodScore is issued by Gitcoin right now. We do have plans to federate issuance of credentials down the line.
162 | - Governance Centralization Warning - The PersonhoodScore weights right now are set by the Gitcoin team, but we do have plans to one day move them to a governance process.
163 | - DIDs - We believe that the W3C compliant DIDs are the likely long-term best place for this information to live, but are providing NFTs as a recognition of where the ecosystem is today. Depending upon consumer demand + governance, we may support either/both long term.
164 |
165 |
166 |
--------------------------------------------------------------------------------
/python/pop_api.py:
--------------------------------------------------------------------------------
1 | import web3
2 | import json
3 | import requests
4 |
5 |
6 | def get_personhoodscore(w3, network, address):
7 |
8 | # prevent char limit issues with rinkeby
9 | # + validate network
10 | if network == 'rinkeby':
11 | try:
12 | from web3.middleware import geth_poa_middleware
13 | w3.middleware_stack.inject(geth_poa_middleware, layer=0)
14 | except:
15 | pass
16 | if network not in ['rinkeby', 'mainnet']:
17 | raise Exception('unsupported network')
18 |
19 | # setup
20 | address = w3.toChecksumAddress(address)
21 | contract_address_rinkeby = w3.toChecksumAddress('0xcEFBf0A9Ada7A03056dD08B470AA843ef8ca5D79')
22 | contract_address_mainnet = w3.toChecksumAddress('0xb4e903dc14dfe994fe291fc5b385c4718413366d')
23 |
24 | # get contract
25 | contract_address = contract_address_mainnet if network == 'mainnet' else contract_address_rinkeby
26 | abi = '[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"tokenURI","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"uint256","name":"_nonce","type":"uint256"}],"name":"createPassport","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_messageHash","type":"bytes32"}],"name":"getEthSignedMessageHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"string","name":"_message","type":"string"},{"internalType":"uint256","name":"_nonce","type":"uint256"}],"name":"getMessageHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_ethSignedMessageHash","type":"bytes32"},{"internalType":"bytes","name":"_signature","type":"bytes"}],"name":"recoverSigner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"splitSignature","outputs":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_signer","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"string","name":"_message","type":"string"},{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"verify","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}]'
27 | abi = json.loads(abi)
28 | contract = w3.eth.contract(contract_address_rinkeby, abi=abi)
29 |
30 | # get baseuri
31 | balance = contract.functions.balanceOf(address).call()
32 | if balance == 0:
33 | return 0
34 | index = contract.functions.tokenOfOwnerByIndex(address, 0).call()
35 | url = contract.functions.tokenURI(index).call()
36 |
37 | # for local testing
38 | # url = url.replace('https://persons.proofofpersonhood.com', 'http://localhost:8000')
39 |
40 | # get data from json
41 | try:
42 | response = requests.get(url).json()
43 | for attr in response['attributes']:
44 | if attr['trait_type'] == 'personhood_score':
45 | return attr['value']
46 | except:
47 | pass
48 | return 0
--------------------------------------------------------------------------------
/js/pop_api.js:
--------------------------------------------------------------------------------
1 | function makeRequest(method, url) {
2 | return new Promise(function (resolve, reject) {
3 | let xhr = new XMLHttpRequest();
4 | xhr.open(method, url);
5 | xhr.onload = function () {
6 | if (this.status >= 200 && this.status < 300) {
7 | var response = JSON.parse(xhr.response);
8 | response = response['attributes'];
9 | for(var i=0;i