In the previous post here we introduced TezID and why we built it.
In this post we will outline how to use TezID.
We will use an example of a ICO contract that required participants to have 2 valid TezID proofs in order to qualify for registration. We will use SmartPy code in our examples.
Let's dive right into code!
## Types
#
TGetProofsRequestPayload = sp.TRecord(
address=sp.TAddress,
callback_address=sp.TAddress,
callback_entrypoint=sp.TString
)
TGetProofsResponsePayload = sp.TRecord(
address = sp.TAddress,
proofs = sp.TMap(sp.TString, sp.TRecord(
register_date = sp.TTimestamp,
verified = sp.TBool
))
)
## Contract
#
class ICO(sp.Contract):
def __init__(self, tezid, requiredProofs):
self.init(
tezid = tezid,
requiredProofs = requiredProofs,
participants = {}
)
@sp.entry_point
def signup(self):
c = sp.contract(TGetProofsRequestPayload, self.data.tezid, entry_point="getProofs").open_some()
sp.transfer(sp.record(address=sp.sender, callback_address=sp.self_address, callback_entrypoint="register"), sp.mutez(0), c)
@sp.entry_point
def register(self, ptr):
sp.if sp.sender != self.data.tezid:
sp.failwith('Only TezID can register')
sp.set_type(ptr, TGetProofsResponsePayload)
validProofs = sp.local("validProofs", [])
sp.for requiredProof in self.data.requiredProofs:
sp.if ptr.proofs.contains(requiredProof):
sp.if ptr.proofs[requiredProof].verified:
validProofs.value.push(requiredProof)
sp.if sp.len(self.data.requiredProofs) == sp.len(validProofs.value):
self.data.participants[ptr.address] = {}
Ok, so here we have some datatypes and a Tezos Smart Contract called ICO.
The datatypes TGetProofsRequestPayload
and TGetProofsResponsePayload
represent the structure of the data payload when calling and receiving a callback from TezID.
The ICO contract has the __init__
function to set some initial storage data. In addition is has two entrypoints; signup
and register
.
@__init__
Init is called when we create the contract. It sets some intial data for the contract;
* tezid ~ this is the address of the TezID contract
* requiredProofs ~ a list of required TezID proofs
* participants ~ the registered participants of the ICO
@signup
Signup can be called by anyone that attempts to sign up for the ICO. Signup will trigger a call to TezID's getProofs
endpoint in order to retrieve the registered proofs for the calling address, and states that it would like it's own register
endpoint to be called with this information (this is what we call a callback pattern, and it's how Tezos contracts can interact - atleast for now).
@register
Register is where TezID will call back with the registered proofs for some address. Note that it is set up to only allow TezID to call this endpoint. It received some proofs from TezID, compares to the required proofs set in requiredProofs
, checks their validity and adds the address to the participants
map if it all checks out.
And that is it π
We now have created a contract that requires participants to have some proofs registered on TezID and we have more confidence that the registered participants are real people.
A SmartPy testcase
The test code for this ICO contract is quite long since it require all sorts of setup, but here it is if you are interested;
@sp.add_test(name = "Call TezID from other contract")
def test():
admin = sp.test_account("admin")
user = sp.test_account("User")
user2 = sp.test_account("User2")
user3 = sp.test_account("User3")
cost = sp.tez(5)
proof1 = sp.record(
type = 'email'
)
proof2 = sp.record(
type = 'phone'
)
proofVer1 = sp.record(
tzaddr = user.address,
type = 'email'
)
proofVer2 = sp.record(
tzaddr = user.address,
type = 'phone'
)
scenario = sp.test_scenario()
c1 = TezID(admin.address, cost)
scenario += c1
c2 = ICO(c1.address, ["email","phone"])
scenario += c2
## A user with the correct valid proofs can register as participant
#
scenario += c1.registerAddress().run(sender = user, amount = sp.tez(5))
scenario += c1.registerProof(proof1).run(sender = user, amount = sp.tez(5))
scenario += c1.registerProof(proof2).run(sender = user, amount = sp.tez(5))
scenario += c1.verifyProof(proofVer1).run(sender = admin)
scenario += c1.verifyProof(proofVer2).run(sender = admin)
scenario += c2.signup().run(sender = user)
scenario.verify(c2.data.participants.contains(user.address))
## A user without the correct valid proofs cannot register as participant
#
scenario += c1.registerAddress().run(sender = user2, amount = sp.tez(5))
scenario += c1.registerProof(proof1).run(sender = user2, amount = sp.tez(5))
scenario += c1.registerProof(proof2).run(sender = user2, amount = sp.tez(5))
scenario += c1.verifyProof(proofVer1).run(sender = admin)
scenario += c2.signup().run(sender = user2)
scenario.verify(c2.data.participants.contains(user2.address) == False)
## A user not registered on TezID cannot register as participant
#
scenario += c2.signup().run(sender = user3)
scenario.verify(c2.data.participants.contains(user3.address) == False)
## Only TezID can call register endpoiint
#
emailProof = sp.record(
register_date = sp.timestamp(0),
verified = True
)
phoneProof = sp.record(
register_date = sp.timestamp(0),
verified = True
)
proofs = {}
proofs['email'] = emailProof
proofs['phone'] = phoneProof
pr = sp.record(address = user3.address, proofs = proofs)
scenario += c2.register(pr).run(sender = user3, valid=False)
enjoy.