A guide on how to interact with Fractal's DID Registry.
See something wrong? Open up a pull request!
See something that's confusing or just not immediately obvious? Open up an issue!
We're still building this guide. If you need help today, get in touch through [email protected].
To get our hands dirty with some mock data, we will go through:
- Deploy our own copy of Fractal's DID Registry
- Operation of the contract
- Usage examples
- One person, one vote
- Require KYC approval for buying tokens
For these demonstrations, we'll deploy our own copy of Fractal's DID Registry. We'll be using Remix IDE, which includes an in-browser Ethereum implementation. This way we don't spend real money or have to chase down testnet faucets. Don't worry, real-world operation is effectively identical, everything you'll see here can also be done with Hardhat or any other EVM toolchain you prefer.
👁 Step-by-step demonstration
Let's get started! First off, let's start by deploying our own copy of a FractalRegistry.
-
Go to Remix IDE and clone this git repo as a workspace.
-
Compile and deploy the
contracts/1_FractalRegistry.sol
contract. Use your own address as theroot
constructor argument.
So, what does Fractal do with the contract? And, more importantly, what can we do with it?
When a user submits their documents and our identity specialist verify their identity, our servers call addUserAddress
with two arguments:
- the user's EVM address
- a personally unique identifier, called
fractalId
In order to be sure we're not duplicating users and we know their wallet address, a user needs to be onboarded with one of the following level combinations, with optionally more add-ons:
uniqueness+wallet
basic+uniq+wallet
plus+uniq+wallet
👁 Step-by-step demonstration
Let's use ourselves as an example. Let's pretend Fractal assigned us the fractalId
of 0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
.
-
Make a
addUserAddress
call with:addr
: our own addressfractalId
:0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
Fractal's servers also make a few addUserToList
calls with the relevant lists. Let's take a look at lists currently in use, and what it means for a user to be present in each of them.
listId | Meaning |
---|---|
basic |
The user has passed the basic KYC level. |
plus |
The user has passed the plus KYC level. |
fatf_grey |
Resident of a country that's present in the FATF's list of jurisdictions under increased monitoring. |
fatf_black |
Resident of a country that's present in the FATF's list of high-risk jurisdictions. |
👁 Step-by-step demonstration
Let's pretend we've passed the Plus KYC level (plus
).
-
Make a
addUserToList
call with:userId
:0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
listId
:plus
With this data on the contract, we're now able to preform two operations: check if an address belongs to a unique user, and check which lists the users belongs to.
By calling getFractalId
with an address, you get back its controlling user's fractalId
. If two addresses return the same identifier, you can be sure they belong to the same person. Conversely, if two addresses return different identifiers, you can be sure they belong to different people.
If getFractalId
returns 0
, that means the address isn't associated with any user known to Fractal. Be sure to check its return value!
👁 Step-by-step demonstration
Let's see how to contract responds to querying for own address, and an arbitrary address that's not in the contract.
-
Make a
getFractalId
call with:addr
: your own address
-
Verify that you get our
fractalId
back.0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
-
Make a
getFractalId
call with:addr
: some arbitrary valid address. Here's an example:0x05a56E2D52c817161883f50c441c3228CFe54d9f
-
Verify that you get back zero:
0x0000000000000000000000000000000000000000000000000000000000000000
After you get the user's fractalId
, you can then check their presence on the Registry's lists with isUserInList
, which enables you to effectively check their KYC status and/or FATF list presence.
Here are a few examples:
// Passed Basic KYC level
registry.isUserInList(fractalId, "basic")
// Passed Plus KYC level
registry.isUserInList(fractalId, "plus")
// Passed Basic or Plus KYC level
registry.isUserInList(fractalId, "basic") ||
registry.isUserInList(fractalId, "plus")
// Passed Plus KYC level and is not in either FATF lists
registry.isUserInList(fractalId, "plus") &&
!registry.isUserInList(fractalId, "fatf_grey") &&
!registry.isUserInList(fractalId, "fatf_black")
👁 Step-by-step demonstration
Let's see how to contract responds to querying for own lists, and an some other arbitrary ones.
-
Make a
isUserInList
call with:userId
:0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
listId
:plus
-
Verify that you get back
true
. -
Make a
isUserInList
call with:userId
:0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
listId
:basic
-
Verify that you get back
false
.
There's two other relevant public functions on the contract. Going over them briefly:
- When a user removes (or changes) their associated EVM address, Fractal's servers call
removeUserAddress
appropriately (and, on change, also calladdUserAddress
). - When a user submits new information that changes their KYC status, Fractal's servers will make the appropriate
removeUserFromList
(and possiblyaddUserToList
) calls to keep the user's on-chain information up-to-date.
An example use case of fractalId
's pre-person uniqueness is having a voting contract where you require the voter to be in the Registry and for a person to only be able to cast one vote, effectively requiring unique personhood and achieving Sybil-resistance.
Here's a simplified example:
function vote(uint8 option) external {
bytes32 fractalId = FractalRegistry(OXADDRESS).getFractalId(msg.sender);
require(fractalId != 0, "User must be present in FractalRegistry.");
require(!hasVoted[fractalId], "Same person can't vote twice.");
hasVoted[fractalId] = true;
votes[option] += 1;
}
👁 Step-by-step demonstration
The Voting contract, which can be found at contracts/2_Voting.sol
, only has 3 relevant operations:
- Being deployed. Its constructor requires two arguments: the number of options we're voting for (i.e., how different choices are in our ballot) and the address for the FractalRegistry contract.
- Vote. It takes a single argument, the option you're voting for.
- Tallying the results. This returns the current vote count for each option.
Let's try it out and see how it behaves!
-
Compile and deploy the
contracts/2_Voting.sol
contract. For constructor arguments, use:options
:4
. Nothing special about this choice.registryAddress
: the address of the FractalRegistry we've been using.
-
Let's call
currentTally
. It should show that all four options have zero votes. -
Let's call
vote
with option1
. It should succeed. -
Let's call
currentTally
again. It should show that option1
has one vote, and that options0
,2
, and3
have all zero votes. -
Let's try calling
vote
again. This time, it should fail, with the reason "Already voted: the same person can't vote twice." -
Let's try calling
vote
with a different address, one that's not registered in our FractalRegistry contract. It should fail, with the reason "Unregistered user: user must be present in FractalRegistry." -
Let's call
currentTally
again. Even though we tried to execute a bunch of invalid votes, it should return the same results as before.
An example use case of fractalId
presence in specific lists is, in order to buy some ERC20 token, check that a user has passed KYC.
Here's a simplified example:
function buy() external payable {
FractalRegistry registry = FractalRegistry(OXADDRESS);
bytes32 fractalId = registry.getFractalId(msg.sender);
require(
fractalId != 0 && registry.isUserInList(fractalId, "plus"),
"Non KYC-compliant sender: must have cleared `plus` level."
);
_mint(msg.sender, msg.value);
}
👁 Step-by-step demonstration
The DemoToken contract, which can be found at contracts/3_DemoToken.sol
, is a toy ERC20 token with a buy
function that, when it receives funds from a KYC-approved account with the plus
level, mints (i.e. creates) new tokens.
In order to make things simpler, we're going to be reusing the OpenZeppelin's ERC20 implementation. It brings along a lot of standard ERC20 functions but, for this demo, we only care about balance
, which will let us check our balance.
Let's try it out and see it working!
-
Compile and deploy the
contracts/3_DemoToken.sol
contract. For constructor arguments, use:registryAddress
: the address of the FractalRegistry we've been using.
-
Let's call
balance
for our own address. Since we didn't buy any tokens yet, it should be zero. -
Let's call
buy
with 42 Wei. Since we're in theplus
list, we should be successful! -
Let's call
balance
again. It should now return 42. -
In order to see an example of a non-compliant person trying to buy tokens, let's pretend our documents expired and Fractal's server took us off the
plus
list. -
Let's try calling
buy
again. Since we're not in theplus
list anymore, which is marked as required, the contract now refuses the transaction.
Since the main goal of the DID Registry is to be a readable resource, you can also use it off chain directly from your dApp's code!
⚠️ TODO Add a brief web3.js example.
⚠️ TODO Add contract addresses (for the various networks) and ABI. They're now at https://github.com/trustfractal/registry-deployer/tree/master/deployments
⚠️ TODO Point people at https://github.com/trustfractal/did-registry-demo-dapp