We need a way for people with certain roles in the community to invite people who aren't yet in the community, and assign roles to them. The invitation is being sent off-chain in a side channel. The people who are being invited might install a totally new wallet and not have any gas in their new wallet. Here are the details.
When used by an admin A of the community, our Intercoin app will generate a payload P and generate the standard hash and signature Psig. P is a string that serializes some basic invite instructions, such as contract: 0x918aff...aec8 roles: judge member where the spaces are tabs \t. For now just support an array of role strings, but we might have other fields in the future, with ranges for allowed values. The serialization format (such as the one suggested in the example here) should be something that can be easily parsed and unserialized with solidity-stringutils or seriality. Send these to recipient R by any method.
R installs the Intercoin client app, receives P and Psig, and the Intercoin app helps them view the payload P, including the community and roles info from the blockchain. His app generates an ethereum wallet and uses its private key to sign a payload RP, which includes his wallet address and any other parameters he might have set in the app interface when accepting invite. Something like this: address: 0xac8178...fa8c name: Greg Magarshak (the last space here is a space, not a tab character). It generates the standard hash and signature RPsig
Any Intercoin client apps (some are owned by us) will be available to work with any web servers X (some are owned by us) where X controls an EOA with some ether. R sends three variables to X: contract (address of CommunityContract), Psig (signed by A) and RPsig (signed by R) to X. Then X reads CommunityContract mapping called inviteSignatures which is used to map Psig => RPsig. If it is not empty, X will complain to the client.
If it was empty, X will now continue to with a "commit and reveal" scheme on the blockchain. X tells its EOA account to spend some gas and do a simple transaction XT1 which calls method invitePrepare(Psig, RPsig) on the indicated CommunityContract address. All that happens in this method is assigning invitesPrepared[Psig] = RPsig.
Since Psig is practically impossible to guess in advance, it can be used as a key for the "commit" part of the scheme, and the most a rogue X EOA account could do is spam the inviteSignatures mapping. Each CommunityContact can optionally maintain a whitelist of such EOA accounts, which e.g. only the owner can add and remove.
Once the transaction XT1 has been mined, X notifies R of the transaction hash (or block number and transaction index) so R can verify it for themselves. Once R is satisfied that the correct Psig => RPsig mapping has been committed, it reveals P and RP to X (it may as well send along the signatures again, if the R<=>X communication is stateless).
Now, X will post a transaction XT2 which calls inviteAccept(P, Psig, RP, RPsig). This method will compute the hashes and verify the signatures match the ones in the invitesPrepared mapping, namely Psig => RPsig. If something doesn't match, roll back the transaction with a message like "Signatures don't match." Any other server X can try again with its EOA, until the signatures check passes correctly. Then, inviteAccept continues to the next step.
After the signatures matched, update mapping invitesAccepted[Psig] = P. Now if another attempt is made by anyone to call inviteAccept it will roll back saying "This invite was already accepted". Now it goes about the business of accepting the invite.
It parses P and loops through the roles. For each role, if A still has authority in CommunityContract with canAdd this role, then add it to the user with the newly accepted invite, even if this user is already a member of the community with some other roles. Any roles that A does not have authority to add, write in logs a WARN entry that says inviting user did not have permission to add role $roleName. But continue anyway to the next roles. If none of the roles can be added, then invite did nothing. Don't automatically make someone a "member" unless it was added as a role in the invite.
If everything worked well, the acceptInvite() method calls _reimburseCaller() and _rewardCaller() which will reward the EOA which initiated this XT2 transaction, with enough gas to cover both prepareInvite() and acceptInvite() processing. Since this may be a variable cost, look at the difference in msg.gas between start and end of function, and estimate the gas cost of prepareInvite which always has same cost. See this StackExchange answer and its code.
The _rewardCaller() will send some INTR tokens to this EOA, as "payment" for operating the X node. Paying nodes to help invite users in a user friendly way, is one of the organic ways in which demand for INTR tokens is generated.
The CommunityContractacceptInvite() method will also call the replenish(address) method at the end. This method will check the token balance of R address, and check the minimumGas[role] mapping (default for all roles: 0) of CommunityContract. It will get max over all roles of R in commmunity, of minimumGas. If replenish = max - balance > 0 then this replenish amount of ETH is sent to R address, so it can afford to make some some calls. This method is also supposed tot be called at the end of addRole() after it adds a new member to a community.
In the future, the acceptInvite method might call hooks on approved smart contracts, which may execute various actions correspond to additional instructions sent in RP.
Result:
CommunityContract paid gas to reimburse X, and INTR to reward it. It also gave gas to R for an additional transaction, and may reimburse R in the future for more transactions.
Meanwhile, R had no gas to start, but some X was online as a web server and its EOA paid the initial amount and got reimbursed. Now, R can participate in the community.
We can allow any contract to call the replenish() method of our CommunityContract. This method will top up ETH gas every time they use any of our contracts and are below a threshold amount. But if it runs out of gas, then it will have to buy more, ask someone to send, or use X mechanism to execute transactions on its behalf.
NOTES:
If ITR or INTR is used to pay for friendly invites, then it will become more expensive per invite if it goes up in price. Usually, the opposite is true - prices of services come down over time. Thus we may want to include some sort of gradually diminishing reward as the block number increases.
We need a way for people with certain roles in the community to invite people who aren't yet in the community, and assign roles to them. The invitation is being sent off-chain in a side channel. The people who are being invited might install a totally new wallet and not have any gas in their new wallet. Here are the details.
When used by an admin
A
of the community, our Intercoin app will generate a payloadP
and generate the standard hash and signaturePsig
.P
is a string that serializes some basic invite instructions, such ascontract: 0x918aff...aec8 roles: judge member
where the spaces are tabs\t
. For now just support an array of role strings, but we might have other fields in the future, with ranges for allowed values. The serialization format (such as the one suggested in the example here) should be something that can be easily parsed and unserialized with solidity-stringutils or seriality. Send these to recipient R by any method.R
installs the Intercoin client app, receivesP
andPsig
, and the Intercoin app helps them view the payloadP
, including the community and roles info from the blockchain. His app generates an ethereum wallet and uses its private key to sign a payloadRP
, which includes his wallet address and any other parameters he might have set in the app interface when accepting invite. Something like this:address: 0xac8178...fa8c name: Greg Magarshak
(the last space here is a space, not a tab character). It generates the standard hash and signatureRPsig
Any Intercoin client apps (some are owned by us) will be available to work with any web servers
X
(some are owned by us) whereX
controls an EOA with some ether.R
sends three variables toX
:contract
(address of CommunityContract),Psig
(signed byA
) andRPsig
(signed byR
) toX
. ThenX
readsCommunityContract
mapping calledinviteSignatures
which is used to mapPsig => RPsig
. If it is not empty,X
will complain to the client.If it was empty,
X
will now continue to with a "commit and reveal" scheme on the blockchain.X
tells its EOA account to spend some gas and do a simple transactionXT1
which calls methodinvitePrepare(Psig, RPsig)
on the indicatedCommunityContract
address. All that happens in this method is assigninginvitesPrepared[Psig] = RPsig
.Since
Psig
is practically impossible to guess in advance, it can be used as a key for the "commit" part of the scheme, and the most a rogueX
EOA account could do is spam theinviteSignatures
mapping. EachCommunityContact
can optionally maintain a whitelist of such EOA accounts, which e.g. only the owner can add and remove.Once the transaction
XT1
has been mined,X
notifiesR
of the transaction hash (or block number and transaction index) soR
can verify it for themselves. OnceR
is satisfied that the correctPsig => RPsig
mapping has been committed, it revealsP
andRP
toX
(it may as well send along the signatures again, if theR<=>X
communication is stateless).Now,
X
will post a transactionXT2
which callsinviteAccept(P, Psig, RP, RPsig)
. This method will compute the hashes and verify the signatures match the ones in theinvitesPrepared
mapping, namelyPsig
=>RPsig
. If something doesn't match, roll back the transaction with a message like "Signatures don't match." Any other serverX
can try again with itsEOA
, until the signatures check passes correctly. Then,inviteAccept
continues to the next step.After the signatures matched, update mapping
invitesAccepted[Psig] = P
. Now if another attempt is made by anyone to callinviteAccept
it will roll back saying "This invite was already accepted". Now it goes about the business of accepting the invite.It parses the signature
Psig
and uses “ecrecover” function of precompiled contracts to recover the address ofA
. Similarly, it should get the address ofR
.It parses P and loops through the roles. For each role, if
A
still has authority inCommunityContract
withcanAdd
this role, then add it to the user with the newly accepted invite, even if this user is already a member of the community with some other roles. Any roles thatA
does not have authority to add, write in logs aWARN
entry that saysinviting user did not have permission to add role $roleName
. But continue anyway to the next roles. If none of the roles can be added, then invite did nothing. Don't automatically make someone a "member" unless it was added as a role in the invite.acceptInvite()
method calls_reimburseCaller()
and_rewardCaller()
which will reward theEOA
which initiated thisXT2
transaction, with enough gas to cover bothprepareInvite()
andacceptInvite()
processing. Since this may be a variable cost, look at the difference inmsg.gas
between start and end of function, and estimate the gas cost ofprepareInvite
which always has same cost. See this StackExchange answer and its code.The
_rewardCaller()
will send someINTR
tokens to this EOA, as "payment" for operating theX
node. Paying nodes to help invite users in a user friendly way, is one of the organic ways in which demand for INTR tokens is generated.The
CommunityContract
acceptInvite()
method will also call thereplenish(address)
method at the end. This method will check the token balance ofR
address, and check theminimumGas[role]
mapping (default for all roles: 0) ofCommunityContract
. It will getmax
over all roles ofR
in commmunity, ofminimumGas
. Ifreplenish = max - balance > 0
then thisreplenish
amount of ETH is sent toR
address, so it can afford to make some some calls. This method is also supposed tot be called at the end ofaddRole()
after it adds a new member to a community.In the future, the
acceptInvite
method might call hooks on approved smart contracts, which may execute various actions correspond to additional instructions sent inRP
.Result:
CommunityContract
paid gas to reimburseX
, andINTR
to reward it. It also gave gas toR
for an additional transaction, and may reimburseR
in the future for more transactions. Meanwhile,R
had no gas to start, but someX
was online as a web server and itsEOA
paid the initial amount and got reimbursed. Now,R
can participate in the community.We can allow any contract to call the replenish() method of our
CommunityContract
. This method will top upETH
gas every time they use any of our contracts and are below a threshold amount. But if it runs out of gas, then it will have to buy more, ask someone to send, or useX
mechanism to execute transactions on its behalf.NOTES:
If ITR or INTR is used to pay for friendly invites, then it will become more expensive per invite if it goes up in price. Usually, the opposite is true - prices of services come down over time. Thus we may want to include some sort of gradually diminishing reward as the block number increases.