Circles by hand with sbt-ethereum
Author: Steve Randy Waldman 2021-03-23
Here weâll go through the exercise of creating a Circles identity by hand with sbt-ethereum.
#
The web-app is weirdOur command-line adventure will be very, very different from the experience the Circles web application encourages of users. (See https://joincircles.net/ and https://circles.garden/.) The UI works to hide or abstract nearly all the details of working with an Ethereum-ish blockchain application. Users via the web UI donât use a browser-based crypto wallet like Metamask. Instead, they sign up as for a Web 2.0 application with an e-mail address and username. A private key is then generated for the user, presented as a BIP-39-style list of words.
An address â the userâs identity â is also defined for each user, but that address is not the same address as that for which the private key was generated. Instead it is as address to which a Gnosis Safe contract (or really a proxy thereof) can eventually be deployed via the EVM CREATE2
opcode. eventually. Eventually.
But, because in the web application Circles (or Gnosis, or someone) is handling the gas costs for user transactions, it had to be built with a degree of caution. If any signup provoked smart contract creations or transactions, miscreants could force arbitrary expenses on the payer just by spoofing new users. To prevent this, the Circles requires new users to become trusted by at least three existing users before their identity (the Gnosis Safe proxy) is actually materialized on the blockchain, along with their own ERC-20 token (the âme-coinâ) in which theyâll receive their UBI.
#
Circles contractsBehind the Circles application sits three contracts on the xDAI blockchain. Pretty much the only place I found the addresses of these contracts documented is in this tweet. The three contracts areâŠ
- The Hub @
0x29b9a7fBb8995b2423a71cC17cf9810798F6C543
: This is the core of the Circles application, the smart contract that tracks identities and trust relationships, and that launches token contracts for each Circles identity. We will interact with this contract. (Weâve reproduced its full source code as an appendix to this post.) - The Proxy Factory @
0x8b4404DE0CaECE4b966a9959f134f0eFDa636156
: This contract is invoked by the middleware / web-UI to create proxy Gnosis Safe contracts, representing user identities. - The Master Gnosis Safe @
0x2CB0ebc503dE87CFD8f0eCEED8197bF7850184ae
: Identities are efficiently implemented as tiny proxies to a Gnosis Safe implementation, and this is that implementation. Operations on the contracts that represent users happen viaDELEGATECALL
to this master implementation.
Note: When I first examined the hub on the xDAI block explorer, I thought it must be misidentified. Transactions involving it seemed too few and infrequent. But thatâs not right. Since nearly all activity involving the hub is initiated by a user identity, which are nearly all Gnosis Safe contracts, the calls to hub methods are treated as internal transactions, of which there are plenty.
#
Circles by handIf you want to play along, youâll need to have set up your sbt-ethereum environment to work on the xDAI blockchain. Please see previous posts to get started on xDAI, and to give yourself some xDAI to fund transactions.
Since youâre too lazy to review those posts (I would be!), Iâll work from an empty sbt-ethereum shoebox (by using the setting ethcfgShoeboxDirectory
in my build.sbt
file, so my main shoebox is not used). But if you are following along, and you want to make a Circles identity you will keep, donât do this!. Use your default shoebox directory, so that your identity is permanently available by default. You should be able to follow along with every step, except the part about having someone send you some xDAI so you can run transactions. Youâll have to find a friend, try the semi-abandoned faucet, or use the DAI-to-xDAI bridge yourself. (Feel free to hit me up for the few cents youâll need.)
Letâs get started! Iâll assume you have sbt-ethereumâs prerequisites, sbt and a JVM, on your machine already.
#
Create a new projectThrough the terminal we can create a project as shown below. When prompted for a project name I choose circles-tutorial
. Then change into the new project directory.
#
Check out build.sbtIt looks like this:
Because I already have a Circles identity, I donât want to create a new one in my default sbt-ethereum shoebox. So Iâll edit my build.sbt
to use an empty, throwaway shoebox. You probably donât want to do this! You should make your own identity to keep, in your default sbt-ethereum shoebox!
Since weâll be working on the xDAI chain (chain ID 100), weâll also make that the default chain for this project. Thatâs only a convenience. We can always switch to xDAI by typing ethNodeChainIdOverride 100
.
Anyway, hereâs my edited build.sbt
:
#
Startup sbt-ethereumFrom inside the circles-tutorial
directoryâŠ
Because Iâm using a fresh, empty shoebox, sbt-ethereum asked if I wanted to generate a wallet address or download a solidity compiler. We will want to generate a wallet address, but weâll do it explicitly, since readers may not be prompted to create one on startup. Letâs do that!
#
Create a wallet for our Circles identityWeâll use the task ethKeystoreWalletV3Create
. Iâm going to call this identity âcircles-snoopyâ, because it ainât me babe! Itâs just a throwaway identity for this tutorial. If you are making an identity to keep, you might use your own name rather than âsnoopyâ.
Note: Scroll right to see the part where we entered in the alias circles-snoopy.
Cool.
#
Prepare to work with the Circles hubCirclesâ core contract is the hub, which is on the xDAI chain at address 0x29b9a7fBb8995b2423a71cC17cf9810798F6C543
. Letâs use ethAddressAliasSet
to give that an alias:
Notice that address aliased are scoped to the Chain ID. Make sure that your output includes the for chain with ID 100
, the xDAI chain. If you are on the wrong chain, you can drop the alias with ethAddressAliasDrop
, switch to the xDAI chain with ethNodeChainIdOverride 100
, and rerun the command.
Before we can interact with circles-hub
weâll need to import its ABI. When weâre working on the Ethereum main chain, we can often automatically import ABIs from Etherscan. We canât do that for contracts on the xDAI blockchain. Instead, we browse the xDAI Blockscout to find the verified contract ABI, and let sbt-ethereum scrape the from the page (a new sbt-ethereum 0.5.3 feature!):
Oopsie! The important bit there was Please ensure that you have a node URL defined for the current chain ID. Try 'eth'.
Since Iâm working with a fresh environment (shoebox) that has never interacted with the xDAI chain, I need to define the URL to a nodeâs JSON-RPC service! Letâs do that:
Cool. Our error suggested we type eth
to check out our environment. Letâs do that.
Weâre advised to set a sender for our session. Letâs do that to. In my case, working from a fresh environment, Iâll just make circles-snoopy
the default sender with ethAddressSenderDefaultSet
. If you already have a default sender set-up for xDAI, and want to keep it, you can temporarily override it for this session with either ethAddressSenderOverrideSet
or [ethAddressSenderOverride
)(https://www.sbt-ethereum.io/tasks/eth/address/sender.html#ethaddresssenderoverride). (The two are synonyms.) Anyway, you do you. Hereâs me:
Letâs check out our environment again with eth
:
Okay. Anyway, letâs try importing the contract ABI again. Remember, we can find the ABI embedded in the URL https://blockscout.com/poa/xdai/address/0x29b9a7fBb8995b2423a71cC17cf9810798F6C543/contracts
Importing the ABI will be very little work, but will yield a very long output. You can expand below to view it!
Show ABI
Looks good. Letâs see if we can read stuff from the contract.
Yes we can!
#
Fund our Circles identityThis is the part that will require a bit of help from outside this tutorial. Our new identity (circles-snoopy
in my case) will want to call the signup
method on the Circles hub. But we canât call methods that modify the blockchain unless we have âetherâ (xDAI on the xDAI chain) with which to pay for gas. First, letâs checkout our identityâs current balance:
Weâre broke. So now weâll have to find a friend, try the semi-abandoned faucet, or use the DAI-to-xDAI bridge to get some funds. Feel free to hit me up for the few cents youâll need. Iâll send myself a few cents worth of xDAI from elsewhere, then recheck my balance.
We have ca$h!
#
Sign up for Circles as our new identityNow that circles-snoopy
is funded we can sign up. Weâve already made this address our default sender, but you may not have, so letâs make sure that account our session sender using ethAddressSenderOverride
:
Okay. Now letâs sign up with the hub.
Uh-oh. Some weird shit happened there. We successfully submitted the transaction, but while we were waiting for a transaction receipt to prove it had been mined, some Parity/openethereum-client thing went wrong. We have the transaction hash, 0x0ed96eff6caeeb3c92896bfd77165b2ad5349b91e5b6126c1baf7baf3a208451
. (Scroll all the way to the right to see it.) We can manually check for the transaction receipt to see how our signup attempt turned out using ethTransactionLookup
:
Our signup did succeed! Notice a âtrustâ event also occurred. If we look at the hub source code, weâll see that as part of the signup process, we trust ourselves 100%!
#
Working with our âme-coinâLooking at the transaction above, we can see in the Signup
event the address of our new ERC-20 token contract, 0xf910549FdbA1083B7E515029601e8Bc748774C64
. We can also find the address of our token by accessing the userToToken
mapping:
They agree! Letâs give our new token an alias. Usually I alias ERC-20 tokens by their self-reported symbols. For Circles tokens, every userâs token advertises the symbol CRC. So weâll call our new token CRC-snoopy
:
That should be an ERC-20 token, so letâs use sbt-ethereumâs built-in erc20Summary
task to check it out.
Looks good!
Letâs check our own balance:
We own all 50 of the current total supply. Weâre rich!
This should be a perfectly normal ERC-20 token. It will have some special functionality â most importantly an update
method through which the token owner, circles-token
, can receive its âUBIâ, and the capacity to call transferThrough
on the hub, which restricts transfers according to trust relationships and emits a special HubTransfer
event. But we can use our new token just like a plain old ERC-20 too. Letâs give that a try. We should be able to send our token to an arbitrary Ethereum address. (Ideally one that we control, if we want our token back!):
It seems to have worked! Weâve sent one token from circles-snoopy
(which owned all 50 of the initial supply) to 0x72a8a15ECa1f824ADE35cdEB2148223402f23448
(an address I control). We expect circles-snoopy
to now have a balance of only 49 tokens, and 0x72a8a15ECa1f824ADE35cdEB2148223402f23448
to have a balance of one token. First letâs check circles-snoopy:
49 tokens as expected! Now letâs check the balance of 0x72a8a15ECa1f824ADE35cdEB2148223402f23448
:
One token, also as expected. Yay!
#
Trust trust trustMy ârealâ circles identity is 0x647E68F4BCBC843F39c80bb02da96dD13308f657
. Letâs define an alias for that, circles-steve
:
The hub has a trust
method that accepts an address and a limit (from 0 to 100, interpreted as degree of trust in the form of a percentage. Letâs trust that bastard steve 50%:
Grrr. That weird Parity/openethereum-client thing occurred again, while we were waiting for a transaction receipt for transaction 0x4b76f6fdfde76c2dbb26b790f9184e2315cb60f234b637337dd14ac219426f64
. (Scroll all the way to the right to see this hash.) We again have to verify the mining of that transaction by hand, using ethTransactionLookup
:
The transaction did succeed. We can verify that by checking the trust limit of circles-snoopy
for circles-steve
using ethTransactionView
:
As expected, itâs 50%. Which seems generous, considering.
#
Collecting our âUBIâWe let some time pass, and start a session in our circles-tutorial
project. Letâs check circles-snoopy
âs current balance of CRC-snoopy
:
Itâs the same as before, 49 tokens, we havenât received any UBI yet. But wait! Weâre not supposed to until we call an update
method. (See the source code in the appendix, which includes the token contract.)
Since update()
is not a standard ERC-20 method, weâll need to import the ABI for Circles-specific tokens. We can find that on the Circles github site. As of sbt-ethereum 0.5.3, we can just provide the URL to ABI importing tasks (ethContractAbiImport
and ethContractAbiDefaultImport
) and sbt-ethereum will extract the ABI.
Show ABI
Now we can see and use methods that are not standard to ERC-20 tokens. We can check-out the read-only methods with ethTransactionView
:
We can also check-out the methods that would modiy the blockchain and should be run in a transaction with ethTransactionInvoke
If we look at the hub source code (which includes the source code for the Circles tokens it produces), weâll learn that the read-only look
method tells us how much âUBIâ a Circles token in surrently entitled to, while the blockchain-modifying update
method actually delivers the new tokens. Letâs try them out. First letâs call look:
Since CRC-snoopy
is an ERC-20 token with the standard 18 decimals()
, this will correspond to 16.67 tokens.
Now letâs go ahead and call the blockchain-modifying function update
in a trasaction:
Excellent. It looks like it succeeded. Now letâs check CRC-snoopy
âs balance:
Itâs been credited with the expected additional 16.67 tokens. Yay UBI!
transferThrough
to make a âhub transferâ#
Using You can send your Circles tokens to any Ethereum address. You can receive Circles tokens from any Ethereum address. But given how easy it is to create a new identity endowed with Circles tokens, if someone sends you some random Circles tokens, you shouldnât feel very much obliged to provide some form of value to reciprocate for the value received.
However, if you receive tokens from an address that youâve trusted, directly or indirectly according to the rules of the Circles app, then you are arguably duty bound to treat those tokens as if they were as valuable as your own, so to provide value or perform a favor as if the payer had redeemed one of your own tokens.
If you wish to pay a Circles participant in a way that makes clear that your token is trusted and should be honored, you can perform a hub transfer. When you do so, you specify the trust path through which your tokens will travel, and the transfer will only succeed if the hub smart contract can validate that path.
In this tutorial, the identity circles-snoopy
trusted my real identity circles-steve (0x647E68F4BCBC843F39c80bb02da96dD13308f657)
.
From my main environment (rather than the temporary shoebox Iâve used for this exercise), I call transferThrough
beginning with my own Circles tokens, ending with circles-snoopy
, generating a hub transfer event. The syntax will be a bit arcane, though. And note the different environment weâre in, different project, different sender, different aliases. (circles-steve
is circles-identity
here; circles-hub
is gnosis-circles-hub
.)
The tranferThrough
call is a bit difficult to understand, for two reasons. Letâs look at the function signature:
Instead of transfering from one party to another in the usual ERC-20 way, here we specify potentially a series of one-to-one token exchanged between trusted parties until finally the last payer (the last element in srcs
) pays the final recipient (the last element of dests
). So we have to specify not a single transfer, but (potentially) a whole sequence. Thatâs why we have an array of sources, destinations, coins (identified by their owners), and amounts, rather than a single transfer.
This would always be a complicated function, but it looks even more arcane than it needs to, because sbt-ethereum doesnât (yet) support resolution of address aliases within array brackets. So we had to supply all the addresses in the arguments as hex. This is⊠not ideal.
But. We did manage to execute transferThrough
, generating a hub transfer event, signalling to the final recipient that, not only did she receive tokens, but she received a token that directly or indirectly she has promised to trust and treat as on par with her own. She is, arguably, duty bound to provide some value in return for this transfer, in a way she would not be from some unsolicited âordinaryâ transfer.
#
ConclusionSo, weâve succeeded âby handâ at creating an identity, funding it on the xDAI block chain, and signing it up for Circles on the hub contract. That generated a new ERC-20 token, of which our registered identity had an initial balance of 50 tokens. But, so long as anybody periodically âtouchesâ the token contract by calling update
, weâve seen that circles-identity
âs balance will grow over time, receiving its âUBIâ in its own scrip.
Weâve also seen that we can trust other identities, fully or partially, to define the âcircles of trustâ through which âhub transfersâ occur. Although you can send Circles tokens to, or receive them from, any address, âhub transfersâ have a special meaning. If you receive a token by hub transfer, it means that youâve been paid by someone whose tokens youâve directly or indirectly promised to honor. In theory, you owe them a favor in return.