The purpose of this document is to provide instructions on how to load and decode Passport and Score attestions onchain for a given ETH address. It also outlines the implementation considerations for the GitcoinPassportDecoder
smart contract.
For details on the EAS schema used to store Gitcoin Passports onchain, please see: Onchain Passport Attestation
GitcoinPassportDecoder
is introduced to make the retreival of the on-chain data (passports and scores) easier and more convenient. This smart contract is tracking the provider maps (the allocation of bits in the Passport Attestation provider array) and also provides helper methods to allow for the easy retreival of passport, credentials and scores.
The most relevant methods introduced by this smart contract are:
getPassport()
- returns a list of valid credentials for a given ETH address. The validitygetScore()
- returns a users valid score, as a 4-digit value. This function will read the users score first from the cache in theGitcoinResolver
and fall back to reading the score from the EAS attestation. This method will revert with an error is a valid score is not found.isHuman()
- returns a single boolean, indicating if the users score is below or above the minimum threshold. It usesgetScore()
to retreive the value that is compared against the threshold.
Note: Even though at the time of vriting the Gitcoin EAS attestations do not have an expiration time, old attestations will be considered to be expired. The consensus for the maximum age of a attestation is 90 days (this is configurable in the smart contract using the
maxScoreAge
attribute). Attestations and scores in the cache older than this will be considered to be expired. This affects the functionsgetScore()
andisHuman()
.
In order to understand how Passports and Passports Scores exist onchain, take a read of Bringing Passport Data Onchain.
In order to load the latest Passport or Score attestations from EAS, we need to perform the following steps:
In order to find the attestation UID that is owned by a given ETH address for a given schema (like the passport schema) we will need to use the GitcoinResolver
as this plays the role of an 'indexer' and will store the latest attestation UID for a given schema and for a given recipient.
This means that knowing the schema UID (which will be attained internally) and an ETH address we can get the attestation UID for a users.
Having the attestaion UID from Step 1, we can just use the getAttestation
method of the EAS smart contract to load a user's attestation.
The schema of the passport attestation is documented in Onchain Passport Attestation. In order to decode it one will need to use the abi.decode
function as shown in the snippet below:
// Decode the attestion output
(providers, hashes, issuanceDates, expirationDates, providerMapVersion) = abi.decode(
attestation.data,
(uint256[], bytes32[], uint64[], uint64[], uint16)
);
The format of the passport attestation (what each individual field contains) is described in the document Onchain Passport Attestation.
In order to decode the stamps that are saved in a Passport attestation, one needs to understand and keep track of all the stamp providers.
To optimise for space and gas costs, the providers
field has been used as a bit array, with each bit being assigned to a given provider.
But this bit map is not fixed, and can potentially change over time as some providers are removed and others are added to the providers of Gitcoin Passport. This is why we need to track the versions of the provider map. This can be achieved in a simple mapping like:
mapping(uint32 => string[]) public providerVersions;
This is how the providerVersions
shall be used:
- keep an array of strings (provider names) for each version
- each position in the array coresponds to 1 bit in the
providers
field in the attestation - new providers can be added to any array in this map
This how the providerVersions
is meant to be used:
- the current version used for pushing stamps onchain is typically the latest version present in this map
- adding new providers for the current version of the providers list can be done by simply appending new elements to the array
- removing providers from an array is not possible. When providers are removed from the Passport Application, then there are 2 ways to deal with this in the
providerVersions
:- keep the current version of the providers array, and the deprecated providers will simply not be written onchain any more (1 bit from the
providers
field of the attestation will be unused). This typically makes sense when there is only a small number of unused field - create a new
providers
list version. This makes sense if the number of unused bits in theproviders
field is higher and we want to reset this
- keep the current version of the providers array, and the deprecated providers will simply not be written onchain any more (1 bit from the
Score attestations are much simpler than Passport attestations. A Score attestation only contains 3 fields in its paylod:
- uint256 score
- uint32 scorer_id
- uint8 score_decimals
You can follow Steps 1 and Step 2 above to retrieve the attestation UID and score attestation. In order to retreive a passport attestation, there are 2 options:
- Option A: read from EAS and decode the attestation similar like for Passport (see above)
- Option B: read from the 'cache' that has been implemented in the
GitcoinResolver
smart contract
Given that the caching was introduced at a later point and not all scores written to EAS have been cached, we recommend the reading a users score:
- use Option B (fast and gas efficient)
- if Option B did not yield any result (
time
field is 0) then fallback to Option A as it is possible that the score you are looking for was not yet cached (it might have been issued before the cache has been introduced)
You can read this from GitcoinResolver
, see Step 1
above
Read the attestation from EAS, see Step 2
above
Example how to decode a score attestation:
uint256 score;
uint32s score_id;
uint8 decimals;
(score, , decimals) = abi.decode(
attestation.data,
(uint256, uint32, uint8)
);
In order to be able to retreive an score more quickly and cheaper, a cache was introduced in the GitcoinResolver
contract that will store the score (as a 4 digit number), issuance date and expiration date. This can be retreived directly from a map for a given ETH address without the need for further lookups or decoding.
To retreive a cached score from the resolver simply call the getCachedScore(my_address)
function and the CachedScore
will be returned. The CachedScore struct looks as follows:
struct CachedScore {
uint32 score; // compacted uint value 4 decimal places
uint64 time; // issuance time
uint64 expirationTime; // expiration time
}
The getCachedScore
will return an empty struct if a cached value does not exist for a given ETH address. To check that a valid score was returned, check that the value of time
attribute (issuance time) is not 0
.