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 weird
Our 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.
Behind 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 via
DELEGATECALLto 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 hand
If 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 project
Through 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.sbt
It 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
Anyway, here’s my edited
From inside the
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 identity
We’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.
#Prepare to work with the Circles hub
Circles‘ 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
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!
Looks good. Let’s see if we can read stuff from the contract.
Yes we can!
#Fund our Circles identity
This 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 identity
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
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
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
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
That should be an ERC-20 token, so let’s use sbt-ethereum‘s built-in
erc20Summary task to check it out.
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
One token, also as expected. Yay!
#Trust trust trust
My “real” circles identity is
0x647E68F4BCBC843F39c80bb02da96dD13308f657. Let’s define an alias for that,
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
The transaction did succeed. We can verify that by checking the trust limit of
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
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.)
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 (
ethContractAbiDefaultImport) and sbt-ethereum will extract the 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
We can also check-out the methods that would modiy the blockchain and should be run in a transaction with
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:
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
It’s been credited with the expected additional 16.67 tokens. Yay UBI!
transferThrough to make a “hub transfer”#
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
From my main environment (rather than the temporary shoebox I’ve used for this exercise), I call
transferThroughbeginning 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. (
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.
So, 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.