Discussion on the group currency interface

As discussed in a meeting with people from BitsPossed, Proof of Humanity, and Gnosis, we want to use this thread to discuss the final design of the Circles Group Currency contract.

Here’s my take on the issue:

Creating Group Currencies

All new Group Currencies must be created by a single ‘GroupCurrencyTokenFactory’ contract. The factory deploys a new GC, signs it up at the Circles Hub (GCs are organizations), and then emits a ‘GroupCurrencySetup’ event. Using the factory ensures that there is a ‘root’ on top of which a subgraph can be built.

function deployGroupCurrency(
    address _treasury
  , address _owner
  , address _discriminator
  , uint8 _mintFeePerThousand
  , string _memory name
  , string _memory symbol
) external

Managing memberships

The only customizable behavior of a Group Currency should be the way how members are added or removed. There are two different types of memberships which can be used together in the same GC:

  • Direct members are represented by their Circles token addresses. Only this specific token is accepted as a collateral to mint.
  • Delegate trustees are represented by their Circles safe address. All tokens trusted by this address can be added as a collateral.

Current implementation

This is currently implemented with the concept of an ‘owner’. Only owners can add or remove accepted collateral tokens or delegate trustees. The owner address is passed as a parameter to the GC factory and can be any of the following:

Single owner

In the simplest case, an owner is just an EOA. E.g., the one that happened to deploy the GC via the factory. This way one key would have the full power to decide who can participate in a group and who not. Adding and removing members is as straightforward as calling ‘addMemberToken’ of the GC with the owner EOA.

function addMemberToken(address _member) public onlyOwner

MultiSig owner

A more advanced variant would be to use a MultiSig as owner. This way, multiple signers could decide who’s in and who not or a threshold could be used. The ‘addMemberToken’ method is called by the MultiSig contract which, in turn, is called by the EOA.

The function signature is the same as for a single owner

Contract owner

Any arbitrary contract can also be used as an owner. An example would be the Proof of Humanity “PoHGroupCurrencyManager”. Such a contract can be used to “gate” the GC with any arbitrary logic. In the case of PoH, the owner requires a user either to:

  • Call ‘createProfile’ from the wallet that’s registered in the PoH registry and then ‘registerToken’ from the Circles Safe (first proof that you are in the PoH registry, then proof that you own the token that should be bound to the PoH soul id).
  • Or to call ‘directRegister’ in case the Circles token owner and the PoH registered wallet are the same.
// either:
function createProfile(address _token) external
function registerToken(bytes20 _pohId) external
// or:
function directRegister() external

Critique and proposed interface change

The interface can currently differ greatly when used with a contract owner. We should try to come up with an interface that will work out of the box for most custom implementations. Since, in this context (Managing memberships), a group is only defined by its discriminator, we should add a new primitive whose sole responsibility is to decide whether a collateral token/user is allowed into the group.

Adding a discriminator

I’d argue that most custom Group Currency implementations, in the end, will only check if a specific Circles token or user is listed in a registry or will otherwise evaluate a boolean expression with the user-address as an argument. For such GCs, it makes sense to use a common interface to add and remove members.

An example would be a GC that checks if a user owns e.g. a specific POA or Ticket. In this scenario, the user already fulfills all requirements by owning the NFT and can join the group right away with the familiar Circles wallet as soon as it’s created. Every wallet that supports Group Currencies allows the user to join any group without the need to know specific details about the implementation of the discriminator.

interface IGroupMembershipDiscriminator {
   function requireIsMember(address _user) external
   function isMember(address _user) public returns(bool)

The implementations of ‘requireIsMember’ must return meaningful error messages that are suitable to explain to the user why he cannot join a group. Instead of using the token-address, it is more consistent to use the user’s Circles address in every case.

The add/removeMember functions, utilizing the above discriminator could look like the following:

contract GroupCurrencyToken {
  address public hub; // The Circles hub
  address public discriminator; // The discriminator implementation

    function addMember(address _user) external {
        IGroupMembershipDiscriminator(discriminator).requireIsMember(_user); // Must revert if not a member, everyone can add qualified members
        // Add a trust relation between this org and the user
    function removeMember(address _user) external {
        if (!IGroupMembershipDiscriminator(discriminator).isMember(_user)) {
          // remove member, no matter who called the function
        // If sender is the member in question, remove him from the group.
        // If sender is the owner, also remove the member from the group.

Removing addDelegatedTrustee

The current implementation of delegate trustees allows users to invite themselves to the group if they are trusted by a delegate trustee who’s already added to the group. However, it doesn’t look like the delegate trustee itself is actually trusted by the GC which is a bit strange but could be ‘fixed’ by simply adding the delegate trustee’s token as a member.

All in all, I have the impression that this feature was implemented mostly as a workaround to let users invite themselves to the group if someone they know is already in the group. If really necessary, the same behavior can be implemented in the discriminator, so I’d advocate to remove this distinction.

Resulting new possibilities

With the proposed changes, groups can be much more dynamic. Not only can users add and remove themselves to/from the group, other people can too if the discriminator matches or doesn’t match. It also allows users to interact with the basic functions of all GC implementations from any wallet that supports the common GC interface.

Reverse trust (optional - maybe off-topic :wink: )

If we decide to implement the GC as described above, users could remove themselves from a group. In normal use, this would give users a bit more flexibility with their memberships, but it won’t help against e.g., spam groups or other malicious groups that add users against their will.

For cases like this, and generally to measure the reputation of groups or organizations, I propose an additional trust hub that allows expressing trust between users and organizations and maybe even from organization to organization. This way, Circles wallets could show a metric on how reputable a group or organization is. Users who don’t want to be part of a group or organization can express their distrust explicitly, which also makes it easier for external reputation systems.

This would also make it easy for organizations (like e.g. the wallet operator) to maintain a list of recommended/supported GCs solely on-chain.


Some of the ideas already came up in Proposal to update the Hub contract (Circles v2). The missing “reverse trust” limits some possibilities discussed in GitHub - jaensen/circles-reputation-metrics: Collection of resources needed to build a reputation/recommodation system for Circles

As always, with the kind request for comments. Have a good night :first_quarter_moon_with_face:

1 Like

Dear @jaensen and all contributors to the proposal,

thanks for your work first of all. I’m no programmer, so I won’t be able to dig into the code, thus let me play with a scenarios on the level of the of use cases.

I understand that the managing memberships section is defining whose individual tokens can be used to mint a GC. I wonder if “The only customizable behavior of a Group Currency should be the way how members are added or removed.” is a sufficient condition or leaving room for unintended user behaviour. Either direct members (with their own individual token) or delegated trustees (with all token they accepted) seem to be able to mint token a GC with the respective tokens. While I understand that direct members and delegated trustees are enabled to perform the minting, I have not fully comprehended whether non-members of the GC Token Factory who are holding the tokens that are eligible for the minting are also enabled to mint. If your criteria is a hard one and excluding the users I describe, then there seems to be no room for unintended behaviour. If not, perhaps it is necessary to create another criteria asking for who can mint, besides the criteria of whose token are eligible. In any regards, it might be worthwhile to evaluate whether separating the membership setup based on two criteria is beneficial.


1 Like

Hello @pontenegro thanks for sharing your thoughts! Here in this post, Martin explains how the types of minting take place. As you will see, there is a type of minting that allows anyone who possesses a group member token to mint, but also, there are 2 other types more restrictive.

1 Like

First of all, @jaensen thank you very, very much for your huge effort of creating this post and propose this discussion. This is no simple matter so, much appreciated!

After reading thoroughly, here are my thoughts:

  • I totally agree that we need some kind of entity on top of GCs so we can standardize and manage general events to be indexed in a subgraph. However, we need to specify more in detail how this entity is going to manage the deploy/creation of GCs. In my opinion, it would be desirable a way that does not need maintenance if a new GC appears.

  • I do not quite understand the part of Critique and proposed interface change. Are you suggesting to define and design a general GroupCurrencyToken that serves for most use cases? Because if so, is the idea of a discriminator to let users to customize this general approach only when adding or removing members from the group?

  • One point that I think is missing is what happens with the behaviour of the Treasury. Here, Martin exposes that the treasury could be just a burn address or in another setup the treasury could be some logic that allows users to redeem their CRC again if they want. If we are working on a general GroupCurrencyToken, do we want to let the treasury behaviour be customizable?

That’s all! =)

Hi together!

@pontenegro The way that GC can be minted will stay the same if the proposed changes are implemented. The possible modes are: admin-only, member-only and everyone can mint. Also the treasury and minting fees stay configurable as is.


  1. Factoy/Index-root
    Generally we would re-use the existing factory as the “root” and just add more parameters: https://github.com/ice09/circles-contracts/blob/hub-v1-comp/contracts/GroupCurrencyTokenFactory.sol Wallets only need to call the createGroupCurrencyToken function to create a new token. This emits a GroupCurrencyTokenCreated(address indexed _address, address indexed _deployer) event with the required metadata.

  2. General GC implementation
    Yes, I propose an interface design that allows any wallet to interact with all methods of the GC while still retaining the possibility to deeply customize the membership rules. The problem with the current PoH implementation (and with any other custom implementation for that matter) is, that it requires a “wrapper” that’s also the owner of the GC. In the current implementation, only the owner can add or remove people to/from the GC. If this owner is a custom contract, then the wallets would need to support each owner-contract’s ABI in order to add/remove users which becomes unmanageable down the road.
    With the proposed interface, the effective add/remove methods stay on the GC contract, and only the discriminator decides if a user can be added or not. This way all wallets can just offer to add/remove users using this methods instead of using the owner-contract’s methods.
    If a user is not allowed in the group, the add method would just fail with an error message that explains why. The discriminator-contract could still have it’s own dapp to manage it but once set up, any wallet can be used to add/remove/mint.
    To be a bit more user friendly, the wallet could just call the ‘requireIsMember’ method of the GC’s discriminator. If an error is returned, the button should be greyed out and the error message is displayed as a hint. If the discriminator matches, the button is enabled and users can add themself.

  3. Treasury
    Stays the same

Hi @jaensen !

About the Factoy/Index-root. If i understood correctly, the idea is to have a contract GroupCurrencyToken with all the general logic needed for us and if someone needs custom behaviour, they can do so specifiyng a contract as owner. Is that right?

About the general GC implementation, thanks for explaining it, now i see your point. Makes so much sense to have only the GC as entrypoint for everyones sake.

Thank you very much <3


GroupCurrencyToken with all the general logic needed for us and if someone needs custom behaviour, they can do so specifiyng a contract as owner. Is that right?

Nearly :slight_smile: They would create a custom discriminator-contract instead of implementing this logic in an owner-contract.

1 Like