Only this pageAll pages
Powered by GitBook
1 of 52

Home

Loading...

Overview

Loading...

Loading...

Loading...

Loading...

Loading...

Circles SDK

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Tutorials and Examples

Loading...

Querying Circles profiles and data

Loading...

Loading...

Loading...

Loading...

Circles SDK Reference

Loading...

Loading...

Loading...

Developer Support

Loading...

Loading...

Loading...

User Guides

Loading...

Loading...

Loading...

Loading...

Loading...

Welcome to Circles

We introduce Circles, a new currency framework that leverages modern cryptography to build trust without centralized gatekeepers. By decentralizing money creation, Circles distributes the privilege of issuing currency to every individual, fostering a more inclusive and resilient monetary system.

Circles is built on a few powerful principles that make money creation fair, efficient, and resilient. By moving money creation from centralized institutions to individuals—and by harnessing real-life trust—Circles reimagines currency.

Build on the Circles Protocol

About Circles

Under the hood, Circles relies on five simple rules:

  • Universal access allows anyone to open an account.

  • Distributed issuance grants each account the right to mint 1 CRC per hour.

  • Demurrage applies a 7 % yearly decay to every CRC, preventing early adopters from dominating the supply.

  • A Rule of Trust lets users swap CRC 1:1 along their social trust graph, so individual non-fungible “personal monies” converge into a commonly spendable currency.

Together these rules guarantee fair access to new money while deterring Sybil attacks and other abuses.

Because payments flow along real-world social links, Circles is both resilient and versatile. Trust can be revoked at any time, shielding honest users from malicious actors, yet the transitive nature of swaps means you can still pay strangers seamlessly. Groups and Organizations add a “fast lane” for adoption: they pool liquidity, act as local or thematic currencies, and let communities balance efficiency against security. The result is a self-governing monetary network in which issuance, security and economic coordination rest with the people who use it.

This documentation helps developers explore Circles’ capabilities and delve into the Circles SDK and its various components.

  • Members of a Circles Groups can mint a shared CRC backed by members’ coins, giving communities their own monetary layer.

  • Group Currencies

    In addition to individual CRC currencies, Circles has introduced the concept of Group Avatars and Currencies. The idea behind Group currencies is to aggregate social-economic value amongst groups, without the geographical bounds of where groups are generated or created. Group avatar tokens can't be minted based on time, rather they depend on collateral deposits from trusted tokens.

    Group participation operates through Circles' trust network rather than formal membership. To receive and accept group tokens, you need to trust the group address, which allows you to hold and transact with their tokens. The group, in turn, must trust the types of tokens it will accept as collateral for minting.

    Group token minting is a permissionless process open to anyone holding tokens that the group trusts. When you want to mint group tokens, you deposit your trusted tokens (which can be personal CRC, other group tokens, or any Circles tokens) as collateral through the groupMint() function. The group mints new group tokens proportional to the collateral deposited, while your collateral tokens are held in the group's treasury.

    This is not a direct swap but rather a collateralization mechanism - your original tokens remain in the group's treasury and can be redeemed later by burning group tokens through the groupRedeem() function. The current system allows minting based on any quantity of trusted Circles tokens you hold, not limited to personal CRC tokens.

    The trust relationships work bidirectionally: you trust the group to accept their tokens, and the group trusts specific token types to accept them as collateral. This creates a flexible, decentralized system where group currencies can be backed by diverse collateral from across the Circles ecosystem, enabling economic coordination without geographic or membership constraints.

    Organization Avatars

    In the Circles SDK, an Organization Avatar represents a non-human entity within the ecosystem. Unlike human or group avatars, it has distinct capabilities:

    • Cannot mint tokens

    • Can participate in the trust network

    • Supports profiles for organization

    Unlike group avatars, organizations don't have membership conditions. Organizations are designed to represent entities that participate in the Circles economy without issuing their own currency.

    Understanding Personal and Group Currencies

    In Circles, money is generated collectively by many individuals over time rather than by a central authority at specific events.

    When you become part of the Circles network, every person has their own personal Circles token and receives one Circle (CRC) per hour, unconditionally.

    The trust network forms a social graph where each trust relationship acts as a link, allowing tokens to flow between individuals through these connections. This ensures that the value of Circles is supported by genuine social relationships, fostering a community-driven economy.

    At the same time, this mechanism is an effective soft sybil protection, as it decentralises the verification process and relies on human judgment for trust, preventing fake accounts from compromising the network. Since the connections between Circles accounts have a limited capacity (defined by their balances of fungible tokens), this limits the impact of fake accounts that manage to get trusted by a credible member of the network to their direct surroundings.

    Get token balances of an avatar

    You can use the avatar.balances helpers to read Circles balances with automatic v1/v2 handling. These methods rely on RPC only and work as soon as the SDK instance is initialized with a runner.

    Get Total Balance

    const totalBalance = await avatar.balances.getTotal();
    console.log(`Total Circles balance: ${totalBalance.toString()}`);
    • Automatically checks the correct hub version based on avatar.avatarInfo.

    • Returns the sum of all Circles holdings (unwrapped + wrapped) for the avatar address.

    Get Token Balances

    • Returns an array of TokenBalanceRow entries for every token relevant to the avatar in the current context.

    Mint group tokens

    Mint Base Group tokens by sending collateral through the trust graph to the group’s mint handler. The groupToken helpers on HumanAvatar and OrganisationAvatar handle pathfinding and wrapped balances automatically.

    Mint Group Tokens

    const groupAddress = '0xGroupAddress';
    const amount = BigInt(100); 
    
    try {
      const receipt = await avatar.groupToken.mint(groupAddress, amount);
      console.log('Minting successful:', receipt.hash);
    } catch (error) {
      console.error('Minting failed:', error);
    }
    • Uses the pathfinder to route collateral (including wrapped tokens) to the group’s mint handler.

    • It will fail if trust or liquidity is insufficient to deliver the requested amount.

    Check Maximum Mintable (Recommended)

    This will compute the largest flow to the group’s mint handler with the current trust relations and balances.

    Circles Architecture

    An overview of different components of circles architecture.

    Circles V2 Core Components

    Manage trust connections

    Trust determines which avatars will accept Circles issued by others. Circles SDK exposes avatar.trust helpers that batch safely (via Safe runner) or send single transactions (via EOA runners) while keeping expiry handling consistent and default under the hood.

    Add Trust

    • Trusting an avatar means you are willing to accept Circles they issue, transfers from them or along paths that rely on them are allowed while the trust is active.

    Group Avatars

    Group avatars are a type of avatar in the Circles ecosystem that represent collective entities rather than individual users.

    Group Structure

    Each group in Circles has:

    • A unique blockchain address (similar to individual avatars)

    Why Build on Circles?

    Circles is a decentralized protocol with a social and economic value for humans, communities and groups. Circles is not a crypto startup or product in general, but a collective effort to bring a fair distribution model for the money created, owned and shared by people.

    Explore our developer documentation, and join us in our mission to create a fair and sustainable economy for all.

    Managing group invites

    Base groups “invite” members by trusting their personal avatars; members must also trust the group avatar to mint group tokens as collateral.

    Invite a Member (Trust Them)

    • Trusting a member signals that the group will accept their personal token as collateral.

    A specific type identifier

  • An owner who has administrative control

  • A treasury where tokens are stored

  • A mint policy that governs token creation

  • A name and symbol for its tokens

  • A membership system

  • Groups also maintain a count of their members and can store metadata via IPFS CIDs (Content Identifiers).

  • Groups support following CRC token operations:

    • Minting: Groups can mint their own tokens using collateral from members

    • Redemption: Members can redeem group tokens for the underlying collateral

    Understanding purpose of Groups

    Groups within the Circles ecosystem serve several important purposes:

    1. Enabling Collective Economic Action: Groups allow multiple individuals to create shared economic entities with their own currencies, extending Circles beyond personal tokens.

    2. Creating Community Currencies: Groups can mint their own tokens backed by collateral, allowing communities to create currencies tailored to their specific needs.

    3. Pooling Resources: The group treasury system allows pooling of resources from members.

    4. Collective Governance: Groups implement membership systems that can include conditions and expiry times.

    Personal Currencies and Trust Path

    Personal currencies are unique and require trust to be transferred to others. To send tokens, other users must first trust your personal currency. When someone trusts your tokens, they become fungible with their own tokens.

    This fungibility allows for the transfer of tokens along a trust path, enabling transactions even between people who do not directly trust each other.

    Group Currencies

    Additionally, Circles v2 introduces support for group currencies, allowing communities to share a currency backed by their members' personal tokens.

    This collective currency reduces risk, simplifies payments, and enhances market creation by aggregating individual tokens into a more stable and fungible group currency. Group currencies make it easier to integrate Circles into existing economic structures and protocols, further promoting a robust and interconnected community economy.

    A flow of architecture for Circles Protocol

    Hub V2 Contract

    An ERC-1155 standard contract for registeration of

    • human,

    • groups and

    • organisation avatars.

    Manages trust relations, minting of personal CRC tokens, group currencies and demurrage.

    Migration Contract

    Allows transition from Legacy V1 hub avatars to V2 hub. Migration will lock V1 CRC tokens, stop minting V1 tokens and convert into V2 tokens.

    Name Registry

    NameRegistry contract manages names, symbols and metadata for avatars (humans, groups, and organizations).

    The name would be of 12 characters with a base58 encoding and store metadata for avatar profiles.

    Base Mint Policy

    Base mint policy is standard contract is utilized group registration. Once registered the policy address is immutable for the group address. This is a reference implementation for minting, burning and redeeming the group currencies and developers can build their own custom policies as well.

    Vaults

    Vaults is a factor contract that holds the personal CRC collateral against the group currencies. Every group, there is single vault to query balance. This contract is deployed by Standard treasury and is utilized during redemption of group circles token.

    Standard Treasury

    The Standard Treasury handles minting and redemption of group Circles by managing collateral transfers. It ensures collateral is forwarded to the correct vault based on structured data from the Hub contract. Additionally, it verifies data during redemption to release or burn collateral as specified by the group's mint policy.

    Each user generates tokens continuously at a rate of one Circle (CRC) per hour, ensuring equal opportunity regardless of when they join. This promotes fairness as everyone has a chance to accumulate Circles.

    Circles operates on a trust-based model, forming a social graph where users trust each other's CRC tokens. Trusted tokens become fungible, creating a dynamic economy backed by genuine social connections.

    Circles integrates a demurrage mechanism, applying a 7% annual fee on held tokens. This keeps the currency circulating, benefiting the overall economy and promoting continuous engagement within the community. For most human users, the fee is offset by the steady income of 1 CRC per hour. Organizations always have to pay the fee.

    By decentralising the verification process and leveraging human judgment for trust, Circles addresses the issue of fake accounts. This fosters a secure and resilient ecosystem, where the authenticity of each user is ensured by their social connections rather than centralised authorities or algorithms.

    Circles allows creation of group currencies, enabling communities to share a currency that's backed by their members' personal tokens. This reduces risk, simplifies payments, and enhances market creation by aggregating individual tokens into a more stable group currency that's easy to integrate into existing protocols.

    By joining the Circles developer community, you can play a crucial role in building a more equitable and inclusive financial system. You can build applications, support developers, and invite more people to join your trust network.

    const tokenBalances = await avatar.balances.getTokenBalances();
    
    tokenBalances.forEach((balance) => {
      console.log(`Token: ${balance.token}, Balance: ${balance.amount}`);
    });
    const maxMintable = await avatar.groupToken.getMaxMintableAmount(groupAddress);
    console.log('Maximum mintable amount:', maxMintable.toString());

    Getting total supply of group tokens available

    Use balances.getTotalSupply() on avatars that expose a minted token (currently Base Groups).

    Get Total Supply for a Base Group

    import { Sdk, BaseGroupAvatar } from '@aboutcircles/sdk';
    
    const sdk = new Sdk({ rpcUrl: 'https://rpc.aboutcircles.com' }, runner);
    const avatar = await sdk.getAvatar('0xGroupAddress');
    
    if (avatar instanceof BaseGroupAvatar) {
      const totalSupply = await avatar.balances.getTotalSupply();
      console.log('Total group token supply:', totalSupply.toString());
    } else {
      console.log('Total supply is only available for BaseGroup avatars.');
    }
    • Returns the ERC‑1155 total supply for the group’s token ID.

    If you call getTotalSupply() on a Human or Organisation avatar you’ll receive an unsupportedOperation error; use balance methods (balances.getTotal, balances.getTokenBalances) instead for those types.

    Passing an array batches all trust updates into one Safe transaction. EOA runners only support single addresses per call.

    Remove Trust

    Check Trust Direction

    Inspect All Trust Relations

    getAll() returns aggregated links where relation is one of:

    • trusts – avatars you trust.

    • trustedBy – avatars that trust you.

    • mutuallyTrusts – avatars where trust is bidirectional.

    Safe runners can batch invites: groupAvatar.trust.add(['0xA', '0xB']);.

    Member Accepts (Trust the Group)

    • Members must trust the group avatar before they can mint group tokens.

    Revoke a Member (Untrust Them)

    • Batching works the same way: groupAvatar.trust.remove(['0xA', '0xB']);.

    const trustReceipt = await groupAvatar.trust.add('0xMemberAvatar');
    console.log('Invitation sent (trust added):', trustReceipt.hash);
    const memberAvatar = await sdk.getAvatar('0xMemberAvatar');
    await memberAvatar.trust.add(groupAvatar.address);
    // Trust indefinitely (default expiry = max uint96)
    const receipt = await avatar.trust.add('0xOtherAvatar');
    console.log(receipt.hash);
    
    // Trust with custom expiry (seconds since epoch)
    const oneYear = BigInt(Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60);
    await avatar.trust.add('0xOtherAvatar', oneYear);
    
    // Trust multiple avatars at once (Safe runner only)
    await avatar.trust.add(['0xA', '0xB', '0xC']);
    const receipt = await avatar.trust.remove('0xOtherAvatar');
    console.log(receipt.hash);
    
    // Batch revoke (Safe runner only)
    await avatar.trust.remove(['0xA', '0xB']);
    const youTrustThem = await avatar.trust.isTrusting('0xAvatar'); // you -> them
    const theyTrustYou = await avatar.trust.isTrustedBy('0xAvatar'); // them -> you
    console.log({ youTrustThem, theyTrustYou });
    const relations = await avatar.trust.getAll();
    
    relations.forEach((relation) => {
      console.log(`${relation.subjectAvatar} ${relation.relation} ${relation.objectAvatar}`);
    });
    const revokeReceipt = await groupAvatar.trust.remove('0xMemberAvatar');
    console.log('Revoked member (trust removed):', revokeReceipt.hash);

    Circles SDK Overview

    The Circles SDK is a TypeScript library that allows you to integrate Circles protocol into your dApp to implement avatars, profiles, token transfers and build trust connections for your use-case. Under the hood, Circles SDK utilizes deployed hub contracts on Gnosis Chain, pathfinder for finding trust network paths and profile service and Circles RPC.

    Circles SDK :

    To use the Circles sdk, install the primary npm package

    Features:

    • Instantiate Sdk once and work with human, organization, or group avatars through a unified interface.

    • Fetch total and per-token Circles balances (demurraged and static) for any avatar.

    • Add, remove, batch, and audit trust relationships across the network.

    • Execute direct or advanced multi-hop transfers with automatic pathfinding and flow-matrix generation.

    • Check mintable amounts, mint daily issuance, stop minting, and manage ERC20 wrapper states.

    • Store, update, and resolve avatar profiles, metadata digests, and short names via the profile service.

    • Register humans, organizations, and groups while handling invitation escrows end-to-end.

    • Query trust graphs, avatar info, wrapper addresses, token holders, and group memberships from convenience namespaces.

    • Deploy BaseGroups, configure services and membership conditions, and analyze holders or memberships.

    • Compute liquidity paths, unwrap/re-wrap flows, and generate operateFlowMatrix payloads programmatically.

    • Plug in EOA or Safe runners to batch transactions and execute all avatar writes with consistent error reporting.

    Dependency packages

    Package
    Purpose

    Personal Currencies

    Circles is all about creating a fairer money system. Our current monetary system and most cryptocurrencies tend to benefit those with established wealth and market positions, making it challenging for newcomers to catch up. Wealth grows disproportionately for those already in the market, as time in the market often beats time to market.

    Circles aims to solve this by ensuring equal opportunity for all. Each user continuously generates tokens at a rate of one Circle (CRC) per hour, regardless of when they join. Additionally, existing Circles incur a demurrage fee of 7% per year. By tying the issuance of tokens to time, a resource everyone has, Circles promotes fairness by giving everyone a chance to accumulate tokens and encouraging the active circulation of currency.

    Issuance

    Circles employs a unique token issuance mechanism where each user generates their own currency (CRC) at a steady rate of one token per hour. The minting rules are encoded in the so-called Hub, a token factory from which all individual Circles tokens are created.

    All tokens existing in the system are ultimately rooted in personal tokens (Circles v1 only includes personal tokens, while v2 adds group currencies which are collateralized by personal tokens).

    Demurrage

    All tokens are automatically demurred at a rate of 7% per year.

    For human users, the demurrage is offset by the steady income of new CRC. Only after minting for 80 years (roughly a human lifespan) or through economic activity can a person become negatively affected by demurrage. Once the person dies, no new tokens are minted and all of the person's tokens are subjected to demurrage.

    Organizations cannot mint tokens. Therefore, their balance is always subject to a demurrage of 7% per year.

    The following graph visualizes the balance of an account over time, assuming no economic activity and continuous minting of all CRC.

    If understood as a tax, then the tax would be negative at first (you get money), but once your balance reaches the threshold of 125.000 Circles, it turns positive (you loose money).

    Implementation

    Circles v2 is built on the ERC1155 token standard. Here, the Hub uses a standard allowance to facilitate path transfers. The token metadata mimics as profile.

    Halting Mint

    In Circles v2, there is a limit that allows a maximum minting amount of 14 days' worth of Circles. Minting can be stopped manually if required.

    Find groups and memberships

    You can use the RPC helpers on sdk.rpc.group to search for Base Groups and to fetch memberships for an avatar. These methods work with cursor-based pagination via PagedQuery.

    Initialize RPC Access

    import { Sdk } from '@aboutcircles/sdk';
    import { CirclesRpc } from '@aboutcircles/sdk-rpc';
    
    // Preferred: reuse the RPC client from an SDK instance
    const sdk = new Sdk({ rpcUrl: 'https://rpc.aboutcircles.com' });
    const rpc = sdk.rpc;

    Find Groups

    Fetch groups with an optional filter and limit:

    • findGroups(limit, params?) pulls pages under the hood and returns up to limit rows.

    Get Group Memberships for an Avatar

    getGroupMemberships returns a PagedQuery; call queryNextPage() to iterate.

    You can continue paging while currentPage?.hasMore is true.

    Full Example

    Transfer personal Circles tokens to different avatar

    You can use avatar.transfer utilities to reason about and send Circles with or without pathfinding.

    Get Maximum Transferable Amount

    Pathfinder-based max flow from your avatar to a recipient:

    const maxTransferable = await avatar.transfer.getMaxAmount('0xRecipient');
    console.log(`Maximum transferable amount: ${maxTransferable.toString()}`);
    • Computes the largest flow permitted by your trust graph and current balances.

    Transfer CRC with Pathfinding

    • Selects routes through trusted avatars and wrapped tokens automatically.

    • Maximum transferable may be lower than your raw balance if trust is missing or tokens are locked in wrappers; check getMaxAmount first.

    Direct Token Transfer (Specific Token)

    • Bypasses pathfinding; only works if you already hold the target token (personal ERC1155 or wrapped ERC20).

    • Provide a token address to send wrapped balances or omit to use your personal token.

    Inviting and accepting human avatars

    Circles allows you to join the network as a human with a token ERC 1155 standard. You would have a profile and would require to be invited to join the network and start minting personal CRC token

    Inviting a human to Circles

    // Inviter side: send invite (escrows 100 CRC + trusts the invitee)
    const inviter = await sdk.getAvatar('0xInviterAvatar'); // HumanAvatar
    await inviter.invite.send('0xInviteeEOAAddress');
    • Now, your invitee can accept the invitation / register by:

    //Invitee side: accept Invitation
    const inviteeAvatar = await inviteeSdk.register.asHuman('0xInviterAddress', {
      name: 'Alice',
      description: 'New Circles member',
    });
    
    // (Optional) If multiple inviters, redeem a specific one
    await inviteeAvatar.invite.redeem('0xSpecificInviter');

    Get an existing avatar

    If you have the address of an existing avatar, you can get the avatar details by:

    Building with different Circles Avatars

    An avatar represents a Circles user.

    The SDK is built around the concept of avatars. An avatar is a Circles user and is used to interact with other Avatars.

    • New Circles users must sign up.

    • For existing Circles users, you can simply get exisiting avatars by address.

    Creating a new avatar

    You will need a Metamask account with some xDAI to be able to interact with contracts and follow along the steps. Make sure you configured MetaMask with the correct ChainID (Gnosis Chain or Chiado) before you continue.

    Create Base Groups for your community

    Base Groups are capable of following:

    • They can set membership conditions to define who can be part of the group

    • They can register short names with a nonce

    Mint personal tokens

    Human avatars accrue issuance continuously (capped at roughly 24 CRC per day) and expose helpers under avatar.personalToken:

    • getMintableAmount() returns the currently claimable amount plus the accrual window.

    • mint() calls hubV2.personalMint() via the configured runner.

    Fetching profile of an human avatar

    This section is dedicated to handling/updating the profiles of an avatar.

    Get a profile for the avatar

    This function fetches the current profile associated with the avatar. If no profile exists, it will return undefined.

    Managing trust connections via Org avatar account

    Organizations use the same trust surface as humans. Trust determines which avatars’ Circles they accept and who can route transfers through them.

    Trust Another Avatar

    • Default expiry is max uint96 (indefinite). Optionally pass an expiry timestamp (seconds since epoch).

    npm install @aboutcircles/sdk

    Personal / Human Avatars

    ERC-1155 standard avatars, which allows you to mint your personal Circles token (CRC) every hour, accumulating 24 CRC per day with an applied demurrage of 7%.

    Group Avatars

    Created by an owner, these avatars allow groups to trust human avatars within the group. Group tokens are utilized by collateralizing personal tokens, following the ERC-1155 standard.

    Organization Avatars

    As an organization, you are an avatar without any minting of new tokens. With your name and metadata file, which will be used for identification and can trust other avatars to receive Circles, with all owned Circles earned by avatars rather than minted.

    Builds Circles SDK transfer payloads by combining pathfinding results with the data and RPC utilities needed to execute a transfer.

    @aboutcircles/sdk-core

    Core contract interactions

    @aboutcircles/sdk-rpc

    RPC client for Circles-specific methods

    @aboutcircles/sdk-profiles

    Profile management

    @aboutcircles/sdk-types

    TypeScript type definitions

    @aboutcircles/sdk-utils

    Utility functions

    @aboutcircles/sdk-runner

    Safe multisig wallet integration for executing blockchain operations with the Circles SDK.

    @aboutcircles/sdk-transfers

    const groups = await rpc.group.findGroups(10, {
      nameStartsWith: 'Community', // optional filters
      // ownerIn: ['0xOwner...'],
      // typeIn: ['BaseGroup'],
    });
    
    console.log('Retrieved groups:', groups);
    const amount = BigInt(10e18); // 10 CRC
    const receipt = await avatar.transfer.advanced('0xRecipient', amount);
    console.log(`Transfer successful! Tx: ${receipt.hash}`);
    const profile = await inviteeAvatar.profile.get();
    if (profile) {
      console.log(profile.name, profile.description);
    }
    const mintable = await avatar.personalToken.getMintableAmount();
    console.log(`Mintable CRC: ${mintable.amount}`);
    
    if (mintable.amount > 0n) {
      const receipt = await avatar.personalToken.mint();
      console.log('Mint transaction hash:', receipt.hash);
    }

    Setting up Circles SDK with React

    Here is an example code on how you can setup Circles SDK with a front-end framework like React.

      import React, { createContext, useCallback, useEffect, useState } from 'react';
      import { Sdk } from '@aboutcircles/sdk';
      import { circlesConfig } from '@aboutcircles/sdk-core';
      import { SafeBrowserRunner } from '@aboutcircles/sdk-runner';
      import { createPublicClient, http } from 'viem';
      import { gnosis } from 'viem/chains';
    
      export const CirclesSDKContext = createContext(null);
    
      export const CirclesSDKProvider = ({ children, safeAddress }) => {
        const [sdk, setSdk] = useState(null);
        const [runner, setRunner] = useState(null);
        const [address, setAddress] = useState(null);
        const [isConnected, setIsConnected] = useState(false);
    
        const initSdk = useCallback(async () => {
          try {
            if (!window.ethereum || !safeAddress) {
              // Read-only SDK without runner
              setSdk(new Sdk(circlesConfig[100]));
              setIsConnected(true);
              return;
            }
    
            const publicClient = createPublicClient({
              chain: gnosis,
              transport: http(circlesConfig[100].circlesRpcUrl),
            });
    
            const safeRunner = new SafeBrowserRunner(publicClient, window.ethereum, safeAddress);
            await safeRunner.init();
    
            setRunner(safeRunner);
            setAddress(safeAddress);
    
            const sdkInstance = new Sdk(circlesConfig[100], safeRunner);
            setSdk(sdkInstance);
            setIsConnected(true);
          } catch (err) {
            console.error('Error initializing Circles SDK', err);
            setIsConnected(false);
          }
        }, [safeAddress]);
    
        useEffect(() => {
          initSdk();
        }, [initSdk]);
    
        return (
          <CirclesSDKContext.Provider
            value={{ sdk, runner, address, isConnected, initSdk }}
          >
            {children}
          </CirclesSDKContext.Provider>
        );
      };

    They can trust avatars individually or in batches with condition checks.

    Create a Base Group

    sdk.register.asGroup wraps profile pinning, factory deployment, and address extraction.

    The returned instance is a BaseGroupAvatar, ready for trust, membership, and admin actions.

    Membership Management

    Trust Management

    Group Administration

    You can also update metadata with groupAvatar.profile.update({...}), which pins a new CID and updates on-chain metadata via the group contract.

    Update profile of the avatar

    This function pins JSON to the Profiles service and updates the metadata digest on-chain. Returns the new CID.

    If you already have your CID, you can use the function below to update the on-chain pointer:

    const profile = await inviteeAvatar.profile.get();
    if (profile) {
      console.log(profile.name, profile.description);
    }
    import type { Profile } from '@aboutcircles/sdk-types';
    
    const newProfile: Profile = {
      name: 'Avatar Name',
      description: 'Updated description for the avatar.',
      avatarUrl: 'ipfs://QmYourImageCIDHere', // optional
    };
    
    try {
      const newCid = await inviteeAvatar.profile.update(newProfile);
      console.log('Profile updated. New CID:', newCid);
    } catch (err) {
      console.error('Error updating profile:', err);
    }
    Safe runners can batch: orgAvatar.trust.add(['0xA', '0xB'], expiry).

    Remove Trust

    Inspect Trust Direction

    List All Trust Relations

    • relation is trusts, trustedBy, or mutuallyTrusts. Use this to identify who the org accepts and who accepts the org for routing and payments.

    const orgAvatar = await sdk.getAvatar('0xOrgAvatar');
    
    const receipt = await orgAvatar.trust.add('0xCollaborator');
    console.log('Trust added:', receipt.hash);
    const membershipsQuery = rpc.group.getGroupMemberships('0xAvatar', 5);
    
    await membershipsQuery.queryNextPage(); // first page
    console.log('Memberships:', membershipsQuery.currentPage?.results);
    const sdk = new Sdk({ rpcUrl: 'https://rpc.aboutcircles.com' });
    const rpc = sdk.rpc;
    
    // Find groups whose names start with "Community"
    const groupsResult = await rpc.group.findGroups(10, { nameStartsWith: 'Community' });
    console.log('Found groups:', groupsResult);
    
    // Find memberships for a specific avatar
    const avatarAddress = '0x123...';
    const memberships = rpc.group.getGroupMemberships(avatarAddress, 10);
    await memberships.queryNextPage();
    console.log('Group memberships:', memberships.currentPage?.results);
    // Send your personal CRC directly (no pathfinding)
    await avatar.transfer.direct('0xRecipient', BigInt(5e18));
    
    // Send a specific wrapped token directly
    await avatar.transfer.direct('0xRecipient', BigInt(5e18), '0xWrappedToken');
    import { Sdk } from '@aboutcircles/sdk';
    
    const sdk = new Sdk(
      { rpcUrl: 'https://rpc.aboutcircles.com' },
      runner // ContractRunner tied to your wallet
    );
    
    const groupAvatar = await sdk.register.asGroup(
      '0xOwner',
      '0xService',
      '0xFeeCollection',
      ['0xCondition1', '0xCondition2'], // initial membership conditions
      'My Base Group',
      'MBG',
      {
        name: 'My Base Group',
        symbol: 'MBG',
        description: 'A base group for coordination',
        imageUrl: '',        // optional
        previewImageUrl: '', // optional
      }
    );
    
    console.log('Base group created at:', groupAvatar.address);
    // List current conditions
    const conditions = await groupAvatar.properties.getMembershipConditions();
    
    // Enable/disable a condition
    await groupAvatar.setProperties.membershipCondition('0xCondition1', true);
    // Trust avatars (default expiry = max uint96)
    await groupAvatar.trust.add(['0xA', '0xB']);
    
    // Untrust
    await groupAvatar.trust.remove('0xA');
    
    // Batch trust with membership checks
    const oneYear = BigInt(Math.floor(Date.now() / 1000)) + 31536000n;
    await groupAvatar.trust.addBatchWithConditions(['0xC', '0xD'], oneYear);
    // Change ownership / service / fee collection
    await groupAvatar.setProperties.owner('0xNewOwner');
    await groupAvatar.setProperties.service('0xNewService');
    await groupAvatar.setProperties.feeCollection('0xNewTreasury');
    
    // Register a short name (nonce required)
    await groupAvatar.profile.registerShortName(42);
    const cid = 'QmYourIPFSCIDHere';
    
    try {
      const receipt = await inviteeAvatar.profile.updateMetadata(cid);
      console.log('Metadata updated, tx:', receipt.hash);
    } catch (err) {
      console.error('Error updating metadata:', err);
    }
    const revoke = await orgAvatar.trust.remove('0xCollaborator');
    console.log('Trust removed:', revoke.hash);
    const trusts = await orgAvatar.trust.isTrusting('0xPeer');   // org -> peer
    const trustedBy = await orgAvatar.trust.isTrustedBy('0xPeer'); // peer -> org
    console.log({ trusts, trustedBy });
    const relations = await orgAvatar.trust.getAll();
    relations.forEach((rel) => {
      console.log(`${rel.subjectAvatar} ${rel.relation} ${rel.objectAvatar}`);
    });

    Wrapping and Unwrapping

    Circles tokens (CRC) are primarily ERC‑1155 balances, but most DeFi tooling expects ERC‑20s. Wrapping converts CRC into ERC‑20 wrappers so you can reach AMMs, lending markets, or bridges while keeping Circles' properties.

    Why wrap?

    • AMMs and routers typically accept only ERC‑20 tokens.

    • Lending/borrowing markets need ERC‑20 collateral and debt assets.

    • Bridges and cross‑chain tools usually whitelist ERC‑20s.

    Wrapper choices

    • Demurraged (decaying): Keeps Circles' time‑based decay. Wrapped balances shrink as demurrage accrues. Use when external protocols should still respect Circles' economics.

    • Inflationary/static: Balance stays fixed for ERC‑20 compatibility. Use for AMM liquidity, lending collateral, or bridging where decay would break integrations.

    Wrapping via avatar.wrap

    Transactions require a configured runner (EOA or Safe) with the SDK.

    Demurraged ERC‑20

    Deploys (or reuses) a demurraged ERC‑20 wrapper for the avatar’s CRC and transfers the specified amount into it.

    Inflationary/static ERC‑20

    Creates or fetches a static ERC‑20 wrapper so balances never decay while wrapped.

    Unwrapping back to ERC‑1155

    Pass the ERC‑20 wrapper contract address you received (via the helper above or your own tracking).

    Demurraged unwrapping

    Demurrage continues while wrapped; the redeemable amount may be lower than what was deposited.

    Static wrapper unwrapping

    Balances are static while wrapped; unwrapping returns the same amount of CRC.

    Note:

    • Pick demurraged wrappers to preserve Circles' economic incentives in external protocols; pick static wrappers for maximal ERC‑20 compatibility.

    • You can wrap any ERC‑1155 Circles token you hold—not just your personal token—by passing its avatar address to asDemurraged or asInflationary.

    • Store or fetch wrapper addresses for DeFi integrations; use sdk.core.liftERC20.erc20Circles(type, avatar) to look them up after deployment.

    Past Hackathon Projects on Circles

    Here are some of the past hackathon projects built with Circles. You can use these as a reference or to get inspiration of what kind of applications to build.

    1. EthGlobal Brussels 2024

    BraceBuddy allows to onboard easily and in a fun way people to the Circles ecosystem with NFC!

    Woleth is an easy to use EVM wallet embed into Telegram designed to build a Social Graph utilizing Circle's invite mechanism.

    Famjam is a dapp that uses Circles to create a family currency to incentivize kids for good behaviour.

    2. EthGlobal Singapore 2024

    Voting with UBI is a dapp which implements a voting mechanism for DAOs by utilizing Circles group tokens.

    3. EthGlobal Prague 2025

    Circles Subscriptz allows users to securely, trustlessly & smoothly authorize recurring payments.

    Validation and Use Cases

    Get Validated

    After you have onboarded at least 10 members into your Circles Groups, please reach out to us via Telegram or Discord to get your group validated.

    You can reach out Circles team via Telegram ord Discord #Groups channels.

    When people join your group, you can support your group by trusting and transacting in CRC to create an ecosystem of reciprocal value, where Circles can become the social and economic glue for your group.

    Once your group has been created, the next step is to integrate Circles into what your group already does. The goal is to find a use case that naturally connects your group’s mission or activity with the core features of Circles.

    Propose a Use Case

    Think about how Circles can enhance your community’s existing work. For example:

    • Joining your group in Circles could signal alignment or participation in your project or initiative.

    • Circles can allow your community to build a trust graph among members who already support each other in other contexts.

    • Circles groups can help members coordinate, reward, or recognise contributions.

    Brainstorm & Iterate with your group

    We recommend gathering your core group and holding a brainstorming session:

    • What do we value as a group?

    • How can Circles help us circulate trust, care, or currency among ourselves?

    • How might we invite others into this ecosystem meaningfully?

    Example Use Cases

    Here are some real-world examples of how communities are already using Circles Groups. These are just a starting point. Your group can create its own unique use case that fits your goals, values, or community needs. We encourage you to explore what’s possible and make it your own.

    Event/Project
    Use Case Description

    Helpful Resources

    Contributing Guide

    Thanks for your interest in contributing! Follow these quick steps to open a pull request. We would encourage both technical and non-technical contributions. Technical contributions may include improvisations and addition of new details for the Circles SDK while non-technical contributions can be creating detailed user oriented guides on various Circles features. All non-technical guides should be created under the User Guides folder.

    Prerequisites

    - Node.js (v18+ recommended)

    - Git + GitHub account

    - Markdown editor or IDE (e.g., VS Code)

    Steps to Contribute

    1. Fork & Clone

    Fork the repo -

    2. Create a New Branch

    3. Install Dependencies & Start Dev Server

    Preview: http://localhost:3000

    4. Make Your Changes

    • Edit or create .md files inside the docs/ folder.

    • Follow the tone and structure of existing content.

    5. Commit & Push

    6. Open a Pull Request

    • Visit your fork on GitHub.

    • Click “Compare & pull request”.

    • Base repo: aboutcircles/circles-docs, branch: main.

    After that, your contribution will be reviewed shortly. Thank you for helping improve Circles documentation!

    Query Circles profiles

    💡 Query Circles Profiles

    Retrieve user profile data using the Profiles Nethermind plugin. You can:

    • Create new user profiles

    • Search existing profiles using parameters such as:

      • Name

      • CID (Content Identifier)

      • Description

      • Wallet Address

    1. Create a Profile (POST request)

    2. Get a Profile by CID (GET request)

    3. Get Multiple Profiles by CIDs (GET request)

    4. Search Profiles by Name (GET request)

    5. Search Profiles by Description (GET request)

    6. Search Profiles by Address (GET request)

    7. Search Profiles by CID (GET request)

    8. Search Profiles with Multiple Criteria (GET request)

    Creation of Organizations

    Organizations join Circles without invitations and don’t mint personal tokens.

    Register the Organization

    import { Sdk } from '@aboutcircles/sdk';
    
    const sdk = new Sdk({ rpcUrl: 'https://rpc.aboutcircles.com' }, runner);
    
    const orgAvatar = await sdk.register.asOrganization({
      name: 'My Organization',
      description: 'Circles-enabled community treasury',
      avatarUrl: '',         // optional
      previewImageUrl: '',   // optional
    });
    
    console.log('Organization avatar:', orgAvatar.address);
    • The SDK pins the profile to the Profiles service, converts the CID to a metadata digest, and calls hubV2.registerOrganization.

    • The returned OrganisationAvatar is ready for balances, transfers, trust, wrapping, group participation, etc.

    Using an Existing Profile CID

    The SDK fetches the profile to validate the name field before registering.

    Technical Group Details

    This section is for developers, advanced users, and those curious about the underlying mechanics of Circles Groups.

    Protocol Level Group

    The most basic group construct is defined in the Hub contract.

    Every group has its own CRC–token. Groups have members (defined as accounts they trust). In theory, groups can also trust other groups.

    Subscribing to Avatar events

    The Circles SDK let's you subscribe to protocol events. Either filtered for an avatar or as a complete stream. There is also a way to query all past events in a block range.

    Subscribe

    To subscribe, you need an initialized CirclesData class.

    Then call the subscribeToEvents() method and supply the address of the avatar to subscribe to:

    The Circles Stack

    Developers Hub for building with Circles.

    Our developer documentation portal provides comprehensive guide to build using Circles SDK, SDK references, contract addresses, and code examples to help you integrate Circles into your applications and build upon our ecosystem.

    Circles SDK

    If you want to develop a server or client application that utilizes Circles, and allow you to utilize trust connection and personal/group currencies, then Circles SDK would be your entry point. Based on your need, you can pick to develop any avatar post initialization of SDK.

    Blockscout

    Blockscout is launching a dedicated app allowing its "Merits" holders to redeem points for exclusive access to a private Circles group. This demonstrates how Circles can be used as a membership token, offering unique benefits and fostering a stronger community among engaged users.

    ZuBerlin

    ZuBerlin utilized CRC for local commerce, enabling members to purchase merchandise directly with CRC. This initiative highlighted CRC's utility for everyday transactions within a specific community, strengthening its internal economy and demonstrating tangible value.

    ETHCC

    During ETHCC, Circles groups facilitated micro-economies where attendees could buy ice cream using CRC. This simple yet effective use case showcased the ease of peer-to-peer transactions with Circles, fostering a sense of shared value and practical application at a large event.

    Dappcon

    Dappcon successfully integrated CRC by allowing participants to purchase conference tickets with CRC. This provided a direct, high-value utility for CRC, promoting its adoption among a tech-savvy audience and demonstrating its potential for event-based economies.

    Why Build on Circles
    Marketing Assets
    Circles FAQ

    Circles Groups

    Unlock the power of community with Circles Groups: A comprehensive guide to creating, managing, and leveraging your group's unique economy.

    • What are Circles Groups?

    • How To Create a Group?

    • Technical Group Details

    • What's Next?

    What are Circles Groups?

    Circles Groups allow communities to engage with a new fair currency protocol, earn rewards, and grow their networks meaningfully. Groups have the freedom to define their own rules, membership criteria, and internal economies. Each group can decide who qualifies to join, whether it's based on location, shared values, participation level, or existing trust relationships.

    Circles groups help communities build trust, collaborate, and create shared economic value. Each group has its own currency, allowing members to exchange their personal CRC tokens for group tokens. This system enables real-world use cases and empowers communities by increasing the utility of CRC tokens through demand and adoption. In the future, demand for group tokens can grow as local donors and businesses begin to accept them.

    Why Groups Matter

    • Group currencies increase daily CRC usability

    • They promote internal circulation and demand for CRC

    • They power real-world use cases, all while staying connected to the broader Circles network

    Key Takeaways

    • You only need 3 people to start a group

    • Group tokens are backed by the personal CRC of members

    • Groups set their own fees for users, which are completely customizable

    • The more your group uses and promotes its token, the more valuable it becomes.

    Ready to build your community? In the next section, you'll find a clear, step-by-step guide to creating your Circles Group. We've broken down the process into easy-to-follow actions, ensuring you can set up your group quickly and efficiently.

    https://github.com/aboutcircles/circles-docs
    const orgFromCid = await sdk.register.asOrganization('QmExistingProfileCid');
    If you want to subscribe to all events, call it without parameter:

    Query past events

    If your client missed some events, you can query all events for a specific avatar in a block range.

    You can omit the last parameter (toBlock) to query from fromBlock to the latest block:

    Event types

    The above methods yield CirclesEvents. All events have at least the following base properties:

    • $event: CirclesEventType One of the event types listed below

    • blockNumber: number In which block the event occurred

    • timestamp?: number When the event occurred

    • transactionIndex: number The index of the transaction in the block

    • logIndex: number The index of the log entry in the transaction

    • transactionHash?: string The transaction hash

    Here's a list of all event types. Please refer to the source code for the event properties.

    const circlesRpc = new CirclesRpc("https://rpc.aboutcircles.com/");
    const data = new CirclesData(circlesRpc);
    const circlesRpc = new CirclesRpc("https://static.94.138.251.148.clients.your-server.de/rpc/");
    const data = new CirclesData(circlesRpc);
    import { Sdk } from '@aboutcircles/sdk';
    import { CirclesType } from '@aboutcircles/sdk-types';
    
    const sdk = new Sdk(); 
    const avatar = await sdk.getAvatar('0xYourAvatar');
    
    const wrapAmount = BigInt('1000000000000000000'); // 1 CRC
    const receipt = await avatar.wrap.asDemurraged(avatar.address, wrapAmount);
    
    // Wrapper address 
    const demurragedWrapper = await sdk.core.liftERC20.erc20Circles(
      CirclesType.Demurrage,
      avatar.address
    );
    import { Sdk } from '@aboutcircles/sdk';
    import { CirclesType } from '@aboutcircles/sdk-types';
    
    const sdk = new Sdk();
    const avatar = await sdk.getAvatar('0xYourAvatar');
    
    const wrapAmount = BigInt('5000000000000000000'); // 5 CRC
    const receipt = await avatar.wrap.asInflationary(avatar.address, wrapAmount);
    
    const staticWrapper = await sdk.core.liftERC20.erc20Circles(
      CirclesType.Inflation,
      avatar.address
    );
    const demurragedWrapper = '0xYourDemurragedWrapper';
    const unwrapAmount = BigInt('800000000000000000'); // 0.8 CRC
    
    const receipt = await avatar.wrap.unwrapDemurraged(demurragedWrapper, unwrapAmount);
    const staticWrapper = '0xYourStaticWrapper';
    const unwrapAmount = BigInt('2000000000000000000'); // 2 CRC
    
    const receipt = await avatar.wrap.unwrapInflationary(staticWrapper, unwrapAmount);
    git clone https://github.com/your-username/circles-docs.git
    cd circles-docs
    git remote add upstream https://github.com/aboutcircles/circles-docs.git
    git checkout -b your-contribution-topic
    npm install
    npm run dev
    git add .
    git commit -m "docs: your concise message"
    git push origin your-contribution-topic
    curl -X POST "https://rpc.aboutcircles.com/profiles/pin" \
         -H "Content-Type: application/json" \
         -d '{
               "name": "John Doe",
               "description": "A blockchain developer",
               "previewImageUrl": "https://example.com/preview.jpg",
               "imageUrl": "https://example.com/image.jpg",
               "extensions": {
                 "twitter": "@johndoe",
                 "github": "johndoe"
               }
             }'
    curl -X GET "https://rpc.aboutcircles.com/profiles/get?cid=Qm12345abcdef"
    curl -X GET "https://rpc.aboutcircles.com/profiles/getBatch?cids=Qm12345abcdef,Qm678bbdj
    curl -X GET "https://rpc.aboutcircles.com/profiles/search?name=John"
    curl -X GET "https://rpc.aboutcircles.com/profiles/search?description=Circles"
    curl -X GET "https://rpc.aboutcircles.com/profiles/search?address=0x1234567890abcdef"
    curl -X GET "https://rpc.aboutcircles.com/profiles/search?CID=Qm12345abcdef"
    curl -X GET "https://rpc.aboutcircles.com/profiles/search?name=John&description=blockchain&address=0x1234567890abcdef&CID=Qm12345abcdef"
    const avatarEvents = await data.subscribeToEvents("0x...");
    avatarEvents.subscribe(event => {
        console.log(event);
    });
    const avatarEvents = await data.subscribeToEvents("0x...");
    avatarEvents.subscribe(event => {
        console.log(event);
    });
    const avatarEvents = await data.getEvents("0x..", 9000000, 10000000);
    const avatarEvents = await data.getEvents("0x..", 10000000);
    export type CirclesEvent =
    
    // CrcV1 Events
    
    | CrcV1_HubTransfer
    | CrcV1_Signup
    | CrcV1_OrganizationSignup
    | CrcV1_Trust
    | CrcV1_Transfer
    
    // CrcV2 Events
    
     | CrcV2_InviteHuman
     | CrcV2_PersonalMint
     | CrcV2_RegisterGroup
     | CrcV2_RegisterHuman
     | CrcV2_RegisterOrganization
     | CrcV2_Stopped
     | CrcV2_Trust
     | CrcV2_TransferSingle
     | CrcV2_Erc20WrapperTransfer
     | CrcV2_Erc20WrapperDeployed
     | CrcV2_URI
     | CrcV2_ApprovalForAll
     | CrcV2_TransferBatch
     | CrcV2_RegisterShortName
     | CrcV2_UpdateMetadataDigest
     | CrcV2_CidV0
     | CrcV2_StreamCompleted
     | CrcV2_CreateVault
     | CrcV2_GroupMintSingle
     | CrcV2_GroupMintBatch
     | CrcV2_GroupRedeem
     | CrcV2_GroupRedeemCollateralReturn
     | CrcV2_GroupRedeemCollateralBurn
     | CrcV2_DepositDemurraged
     | CrcV2_DepositInflationary
     | CrcV2_WithdrawDemurraged
     | CrcV2_WithdrawInflationary
    
    Minting & Redemption

    Minting: If account A is a member of group G, then any account that holds A-CRC can turn them into G-CRC, at a rate 1:1.

    The A-CRC enter the “vault” of the group.

    Redemption: In turn, anybody in the network in possession of G-CRC can redeem them, at a 1:1 rate, against any CRC currently present in the vault.

    Groups (protocol) - Custom Policies

    In the contracts, there are three “entry” points for groups to be customised.

    Mint Policy

    An external contract that is called as part of the minting flow. It gives permission or rejects the mint.

    Redemption Policy

    An external contract is part of the redemption flow. Returns a “redemption plan” that will be executed if it satisfies certain conditions.

    Burn Policy

    An external contract is called when burning. Can veto a burn.

    Custom Policies

    To register any group, all three policies must be provided; however, the implementation offers a trivial BaseMintPolicy That is deployed and can be referenced.

    Base Group

    Base groups are groups with additional “structure” to protocol-level groups. Specifically, they are collections of contracts that are deployed per group. In addition to the default groups, they accept as inputs.

    Inputs

    • An owner

    • A service address

    • A Fee collection address

    • A set of membership conditions

    Screenshot 2025-07-14 at 16.10.55

    There are also the following “auxiliary” contracts deployed for every group, which are both registered as organisations on Circles.

    Base Treasury

    • All funds from the minting of group tokens are forwarded to this treasury. It trusts the group.

    Base Mint Handler

    • This org mirrors the group’s trust, trusting all the group members.

    Base Groups have several advantages:

    • Fee Collection allows the specification of where fees should be forwarded (although no fee collection is enforced via base groups

    • The service address lets owners automate the updating of group membership

    • The base treasury allows “redemption along a path”

    • The mint handler lets users mint group tokens simply by sending CRC to an address (abstracting the requirement to interact with the hub contract)

    • The deployment of ERC20 tokens reduces hiccups with gas estimation.

    • Membership Conditions (which, unlike policies, can be updated by owners) allow sophisticated composition of group membership.

    Affiliate Group

    As a member of the Circles ecosystem, you can join a primary group while also supporting and interacting with affiliate groups. Affiliates do not necessarily have to be members of groups. They are members who want to support groups through their Circles.

    Choosing your affiliate group directly affects your daily CRC flow and trading options. Choose a group that aligns with your values, interests, and needs.

    You're free to change your primary affiliate group at any time and be affiliated with multiple groups at the same time. To set or change your affiliate group:

    1. Navigate to the group you want to select.

    2. Click the star icon in the top right corner of the group page.

    Why Affiliate Groups Matter?

    1. CRC Distribution - Your selected affiliate group receives 1/12th of the Circles tokens (CRC) you create daily, that’s 2 out of 24 CRC per day.

    2. Competition and Value Creation - Users can freely change their affiliate group at any time. Because of this, groups are encouraged to offer valuable incentives and meaningful engagement to attract and retain members.


    Now that you've successfully created your group and have all the technical details, we bet you're wondering what's next! In the final section, you can explore the details on group validation and crafting compelling use cases for your community.

  • Circles SDK source code

  • Circles Infrastructure

    Circles Hub Contracts

    Circles protocol relies on the hub contract that you can utilize directly within applications. These deployed contracts exist on Gnosis Chain and are required by the SDK for configuration and initialization.

    Here are the deployed addresses for V1 hub and V2 hub that you should consider for configuration or building tools on Circles protocol:

    Contract Name
    Deployed addresses

    Hub contract

    0xc12C1E50ABB450d6205Ea2C3Fa861b3B834d13e8

    Name registry

    0xA27566fD89162cC3D40Cb59c87AAaA49B85F3474

    Migration contract

    0xD44B8dcFBaDfC78EA64c55B705BFc68199B56376

    Base mint policy

    0xcCa27c26CF7BAC2a9928f42201d48220F0e3a549

    If you want to skip directly to setting up Circles SDK in your application, you can jump to the SDK Configuration Guide which covers all the setup parameters.

    Join the Technical Community

    To collaborate with fellow developers, ask questions, and share your insights, join our technical community channels on Discord, GitHub, and other platforms.

    Join Telegram Group as a Hacker or a Developer.

    Circles SDK package on npm

    Utilising CirclesQuery Class

    The CirclesQuery class allows you to execute custom queries against the Circles RPC api.

    The previously shown CirclesData class returns CirclesQuery<T> objects for all paged query results. You can execute custom queries against all tables in the Circles index and filter with them.

    Write a query

    First you'll need to define a query. The basic structure of a query is the same as for a basic SQL select. It has the following fields:

    • namespace: Used to distinguish between tables and views as well and to tell the tables of the two Circles version apart from each other.

    • table: The name of the table you want to query.

    • columns: A list of column names you want to select.

    • filter: A list of filter conditions that must be met.

    • sortOrder: Can be 'asc' or 'desc'.

    • limit: How many rows to return (max: 1000).

    Check out the documentation of the for a list of tables.

    Here is a query that reads all avatars with type group. Other avatar types you can try are human and organization.

    If you want to be able to load the next page (queryNextPage()) you must always include the following fields in your query:blockNumber, transactionIndex, logIndex.

    Define a row type

    You can define a type for the rows of your query, or just go with any if the type doesn't matter.

    If you want to specify a custom type, it must extend the EventRow type. The EventRow type contains the blockNumber, transactionIndex and logIndex fields which are required for pagination.

    Execute the query

    To execute the query definition, you'll need a CirclesRpc instance. Create one and pass the Circles rpc url to the constructor.

    Then create a CirclesQuery<MyGroupType> instance.

    Call getNextPage() to retrieve the first page of the result set. You can then access the results through the currentPage property. This property includes the results themselves, along with firstCursor, lastCursor, limit, size, and sortOrder.

    Add computed columns

    You can extend the CirclesQuery with computed columns. Computed columns are defined by a callback that takes in the row and returns a new value. Here we convert the value of the previously queried cidV0Digest field (which is originally a hex-string) to a CID in Qm.. format.

    The new column should be added to the custom type.

    Then you can execute the query just like you did before. The calculated column function will be executed for each row in a page.

    Circles SDK interface

    Sdk class

    Constructor: new Sdk(config?: CirclesConfig, contractRunner?: ContractRunner)

    • config defaults to circlesConfig[100] (Gnosis)

    Pathfinder

    Circles pathfinder calculates “how much and through which tokens can flow from A to B” based on trust and balances (wrapped and unwrapped). You can use it to:

    • Preview a route and achievable amount.

    • Feed TransferBuilder or custom operateFlowMatrix calls.

    Circles Tools

    Circles Tools is a collection of useful tooling for the community. These tools can be used as an extension to the circles core app for niche, specific and advanced usecases.

    Here is a detailed list of tools, you can currently use:

    • Group Checker

      This tool displays detailed information about Circles group memberships and token balances. It can fetch on-chain data like ERC20 supplies, fees, and balances using a smart contract and direct links to relevant explorers and swap platforms.

    • Group Creator

    contractRunner required for any state-changing call (see ContractRunner below)

    Top-level properties:

    • core: low-level contract wrappers (@aboutcircles/sdk-core)

    • rpc: RPC client (@aboutcircles/sdk-rpc)

    • profilesClient: IPFS profile helper

    • senderAddress: present when a runner is provided

    • data: read helpers (see CirclesData)

    Sdk methods

    • getAvatar(address) → HumanAvatar | OrganisationAvatar | BaseGroupAvatar

    Registration (sdk.register.*)

    • asHuman(inviter, profile) → HumanAvatar

    • asOrganization(profile) → OrganisationAvatar

    • asGroup(owner, service, feeCollection, initialConditions, name, symbol, profile) → BaseGroupAvatar

    Profiles (sdk.profiles.*)

    • create(profile) → cid

    • get(cid) → Profile | undefined

    Tokens (sdk.tokens.*)

    • getInflationaryWrapper(address) → wrapper address or zero

    • getDemurragedWrapper(address) → wrapper address or zero

    • getHolders(tokenAddress, limit?, sortOrder?) → PagedQuery<TokenHolderRow>

    Groups (sdk.groups.*)

    • getType(avatar) → group type (currently unsupported)

    • getMembers(groupAddress, limit?, sortOrder?) → PagedQuery<GroupMemberRow>

    • getCollateral(groupAddress) → TokenBalance[] (group treasury balances)

    • getHolders(groupAddress, limit?) → PagedQuery<GroupTokenHolderRow>

    CirclesData (sdk.data)

    Read-only convenience interface:

    • getAvatar(address) → AvatarInfo | undefined

    • getTrustRelations(address) → AggregatedTrustRelation[]

    • getBalances(address) → TokenBalance[]

    ContractRunner (required for writes)

    Minimal contract runner the SDK expects when sending transactions:

    • address (sender)

    • publicClient (viem client for reads)

    • init(): Promise<void>

    • sendTransaction(txs: TransactionRequest[]): Promise<any> Optional: estimateGas, call, resolveName, sendBatchTransaction.

    Avatar interfaces (shared across Human/Organisation/BaseGroup)

    Obtained via sdk.getAvatar(address). All mutate calls require a runner.

    balances

    • getTotal() → total CRC

    • getTokenBalances() → TokenBalanceRow[]

    • getTotalSupply() → BigInt (not implemented for all types)

    trust

    • add(avatar | avatar[], expiry?)

    • remove(avatar | avatar[])

    • isTrusting(address) / isTrustedBy(address)

    • getAll() → AggregatedTrustRelation[]

    profile

    • get() → Profile | undefined

    • update(profile) → cid

    • updateMetadata(cid) → tx receipt

    • registerShortName(nonce) → tx receipt

    history

    • getTransactions(limit?, sortOrder?) → PagedQuery<TransactionRow>

    transfer

    • direct(to, amount, tokenAddress?, txData?) → tx receipt

    • advanced(to, amount, options?) → tx receipt (pathfinding + unwrap/rewrap)

    • getMaxAmount(to) / getMaxAmountAdvanced(to, options?) → bigint

    wrap

    • asDemurraged(avatarAddress, amount) → tx receipt

    • asInflationary(avatarAddress, amount) → tx receipt

    • unwrapDemurraged(wrapperAddress, amount) → tx receipt

    • unwrapInflationary(wrapperAddress, amount) → tx receipt

    events

    • subscribeToEvents() → sets avatar.events observable

    • unsubscribeFromEvents()

    • events → observable stream of Circles events

    Human Avatar specifics

    personalToken

    • getAvailableAmount() → mintable CRC

    • mint() → tx receipt

    • stop() → tx receipt (irreversible)

    groupToken (aliases to group in HumanAvatar)

    • mint(group, amount) → pathfound transfer to mint handler

    • getMaxMintableAmount(group) → bigint

    • redeem(group, amount) → tx receipt (automatic redemption)

    • properties.owner(group) / properties.mintHandler(group)

    • setProperties.owner(group, newOwner)

    • setProperties.mintHandler(group, newHandler)

    Organisation Avatar specifics

    Same groupToken surface as HumanAvatar; lacks personal minting.

    BaseGroup Avatar specifics

    groupToken (group operations)

    • mint(group, amount)

    • getMaxMintableAmount(group)

    • redeem(group, amount)

    • properties.owner(group) / properties.mintHandler(group)

    • setProperties.owner(group, newOwner)

    • setProperties.mintHandler(group, newHandler)

    base group admin (inherited from CommonAvatar group alias)

    • setProperties.memberRequirement(group, newRequirement)

    • setProperties.baseName(group, name, symbol)

    Notes

    • Provide a ContractRunner for any write call; you can use SafeBrowserRunner/SafeContractRunner or your own viem-based runner.

    • Pathfinding options for transfers mirror FindPathParams (useWrappedBalances, token include/exclude lists, maxTransfers, simulatedBalances, etc.).

    Pathfinder

    Finds liquid paths between two accounts in the trust network. These paths are used as input for the contract's transfer methods.

    Learn more about Pathfinder

    Circles Nethermind Plug-in

    Provides access to the Gnosis Chain and indexes Circles events for a seamless experience.

    Learn more about Circles Nethermind Plug-in

    Circles V2 contract source code

    Review the codebase for Circles contracts V2 which follows ERC1155 standard, and manages personal, group and organisation avatars.

    Circles V2 Reference docs

    Explore the latest updates and functionalities of Circles v2.0 Contracts with detailed documentation.

    Circles Profile Service

    This service indexes and persists profile data from/to IPFS.

    https://github.com/aboutcircles/circles-contracts-v2
    https://aboutcircles.github.io/circles-contracts-v2/
    https://github.com/aboutcircles/profile-service
    circles_queryrpc method
    Entry points
    • High-level SDK: const rpc = (sdk as any).rpc; rpc.pathfinder.findPath / findMaxFlow

    • Standalone: const rpc = new CirclesRpc(config.circlesRpcUrl);

    Methods

    • findPath(params: FindPathParams): Promise<PathfindingResult>

    • findMaxFlow(params: Omit<FindPathParams, 'targetFlow'>): Promise<bigint>

    Parameters (FindPathParams)

    • from (Address, required): Source avatar.

    • to (Address, required): Destination avatar.

    • targetFlow (bigint, required for findPath): Desired amount.

    • useWrappedBalances (boolean, optional): Allow ERC20 wrappers in the route. Set true if you hold wrapped CRC.

    • fromTokens / toTokens (Address[], optional): Only allow these token owners from sender/recipient sides.

    • excludeFromTokens / excludeToTokens (Address[], optional): Ban specific token owners.

    • simulatedBalances (array, optional): Inject hypothetical balances { holder, token, amount, isWrapped, isStatic }.

    • maxTransfers (number, optional): Hop cap; lower = faster, but may reduce flow.

    findPath (targeted route)

    Computes a path for a requested amount; returns what is actually achievable plus hop-by-hop legs.

    PathfindingResult:

    • maxFlow – achievable flow for this request.

    • transfers – ordered legs; tokenOwner is the ERC1155 owner or wrapper used in that hop.

    If maxFlow < targetFlow, decide to scale down or show an error.

    findMaxFlow (ceiling)

    Returns the absolute maximum possible flow between two avatars with the same options.

    Use for sliders, validation, or deciding if you must unwrap/wrap first.

    Advanced usage

    • Wrapped tokens: useWrappedBalances: true enables consuming inflationary/demurraged ERC20 wrappers the sender holds.

    • Token pinning/bans: Use fromTokens/toTokens to force specific personal or group tokens; use the exclude* variants to forbid costly or undesired wrappers.

    • Simulations: simulatedBalances let you preview flows after an expected mint/wrap without waiting for indexer updates.

    • Hop limit: maxTransfers curbs route length; helpful for UX responsiveness but may lower maxFlow.

    From path to execution

    • Easiest: call TransferBuilder.constructAdvancedTransfer(from, to, amount, options)—it runs findPath, adds unwrap/rewrap, approvals, and returns an ordered tx array.

    • Manual: convert PathfindingResult to operateFlowMatrix:

    Pathfinder helper functions (when you build flows yourself)

    • createFlowMatrix(from, to, maxFlow, transfers) – turns a PathfindingResult into flowVertices, flowEdges, streams, packedCoordinates for hubV2.operateFlowMatrix.

    • packCoordinates(flowVertices) / transformToFlowVertices(transfers) – lower-level helpers used by createFlowMatrix for packing vertex coordinates.

    • getTokenInfoMapFromPath(from, rpcUrl, path) – fetches token metadata for all tokens/wrappers in a path; feed into other helpers.

    • getWrappedTokensFromPath(path, tokenInfoMap) – list wrapped tokens present in the path.

    • getExpectedUnwrappedTokenTotals(path, tokenInfoMap) – estimate ERC1155 amounts recovered after unwrapping wrappers used in the path.

    • replaceWrappedTokensWithAvatars(path, tokenInfoMap) – rewrite path legs to underlying avatar addresses (for ERC1155 semantics).

    • replaceWrappedTokens(path, unwrappedMap) – replace wrapped token addresses using a precomputed mapping.

    • shrinkPathValues(path, sink, retainBps) – scale down flows proportionally (e.g., execute only 70% of a path).

    • assertNoNettedFlowMismatch(path) – throw if transfers do not net to zero (consistency check).

    • computeNettedFlow(path) – derive net inflow/outflow per address for diagnostics.

    Error handling & tips

    • maxFlow === 0: check mutual trust or unwrap static/demurraged balances back to ERC1155.

    • Paths needing wrapped balances will fail unless useWrappedBalances is true.

    const queryDefinition: PagedQueryParams = {
      namespace: 'V_Crc',
      table: 'Avatars',
      columns: [
        'blockNumber',
        'transactionIndex',
        'logIndex',
        'avatar',
        'name',
        'cidV0Digest'
      ],
      filter: [
        {
          Type: 'FilterPredicate',
          FilterType: 'Equals',
          Column: 'type',
          Value: 'group'
        }
      ],
      sortOrder: 'ASC',
      limit: 100
    };
    interface MyGroupType extends EventRow {
      avatar: string;
      name: string;
      cidV0Digest?: string;
    }
    const circlesRpc = new CirclesRpc('https://chiado-rpc.aboutcircles.com');
    const query = new CirclesQuery<MyGroupType>(circlesRpc, queryDefinition);
    const hasResults = await query.queryNextPage();
    if (!hasResults) {
      console.log("The query yielded no results.");
    } else {
      const rows = query.currentPage.results;
      rows.forEach(row => console.log(row));
    }
    const calculatedColumns = [{
      name: 'cidV0',
      generator: async (row: MyGroupType) => {
        if (!row.cidV0Digest) {
          return undefined;
        }
    
        const dataFromHexString = hexStringToUint8Array(row.cidV0Digest.substring(2));
        return uint8ArrayToCidV0(dataFromHexString);
      }
    }];
    interface MyGroupType extends EventRow {
      avatar: string;
      name: string;
      cidV0Digest?: string;
      cidV0?: string
    }
    const query = new CirclesQuery<MyGroupType>(circlesRpc, queryDefinition, calculatedColumns);
    
    const hasResults = await query.queryNextPage();
    if (!hasResults) {
      console.log("The query yielded no results.");
    } else {
      const rows = query.currentPage.results;
      rows.forEach(row => console.log(row));
    }
    import { CirclesRpc } from '@aboutcircles/sdk-rpc';
    
    const rpc = new CirclesRpc('https://rpc.circlesubi.network/');
    
    const path = await rpc.pathfinder.findPath({
      from: '0xSender',
      to: '0xRecipient',
      targetFlow: BigInt('10000000000000000000'), // 10 CRC
      useWrappedBalances: true,
      fromTokens: ['0xPreferredToken'],  // optional pin
      excludeToTokens: ['0xAvoidToken'], // optional ban
      maxTransfers: 4
    });
    
    console.log(path.maxFlow);    // bigint achievable
    console.log(path.transfers);  // [{ from, to, tokenOwner, value }]
    const max = await rpc.pathfinder.findMaxFlow({
      from: '0xSender',
      to: '0xRecipient',
      useWrappedBalances: true,
      simulatedBalances: [
        { holder: '0xSender', token: '0xToken', amount: 5n * 10n ** 18n, isWrapped: false, isStatic: false }
      ],
    });
    import { createFlowMatrix } from '@aboutcircles/sdk-pathfinder';
    import { Core } from '@aboutcircles/sdk-core';
    
    const core = new Core();
    const rpc = new CirclesRpc(core.config.circlesRpcUrl);
    
    const path = await rpc.pathfinder.findPath({ from, to, targetFlow, useWrappedBalances: true });
    const flow = createFlowMatrix(from, to, path.maxFlow, path.transfers);
    
    const tx = core.hubV2.operateFlowMatrix(
      flow.flowVertices,
      flow.flowEdges,
      flow.streams,
      flow.packedCoordinates
    );
    // send tx via your runner
    This tool allows users to create Circles groups with customizable metadata and membership conditions. It supports input of service address, group name, symbol, and optional initial trust conditions for setup.
  • Group Manager

    This tool enables group owners to manage and update their Circles group contracts directly from the browser. Owners can view contract details, add or remove trusted members, and modify settings like service or fee collection addresses.

  • Profile Checker

    This tool provides a detailed overview of any address’s Circles v1 and v2 profile details like profile data, token balances, trusted relationships, and live ERC20 prices, including bot classification and expiry info.

  • Safe State & Tx Hash Checker

    This tool allows users to inspect the state of a Gnosis Safe (owners, threshold, nonce) and compute a Safe transaction hash with full control over parameters like gas settings, operation type, and calldata. It also includes an interactive function encoder to help generate calldata from ABI inputs, and shows the full execTransaction calldata when approvals meet the threshold.

  • Trust Graph Visualizer

    This tool visualizes the web of trust relationships in the Circles ecosystem using an interactive force-directed graph. It supports avatars, PageRank-based trust scores, dynamic expansion (recursive and top-ranked), and rich filtering (Humans, Groups, Orgs). Users can explore any address, see stats and trust rankings in a sortable table, and view incoming/outgoing trust via tooltips and double-click expansion.

  • Trust Path Visualizer

    This advanced visualization tool helps users explore trust flow paths between two Circles addresses using an interactive Sankey diagram.

    It calculates on-chain trust paths for a given Circles token transfer amount, retrieves real-time token supply capacities, and visualizes flow bottlenecks. It’s ideal for understanding how CRC tokens route across trust relationships and for debugging why a transfer may or may not succeed.

  • Personal CRC Replenisher

    This tool lets Circles users replenish their own CRC token balance by routing it through the trust network back to themselves.

    It calculates the maximum amount you can send to yourself using existing trust paths, and builds a transaction to call operateFlowMatrix on the Circles Hub.

  • Circles Backing dApp

    This tool allows users to back their Circles tokens (CRC) with real-world assets like WBTC, WETH, GNO, or sDAI through a Safe-based flow on Gnosis Chain. It supports both Safe Apps and MetaMask wallet connections, computes a deterministic CirclesBacking contract address for any given Safe, registers appData with CowSwap to enable post-swap hooks, and guides the user through submitting two required transactions: approve() USDC.e to the factory and safeTransferFrom() CRC tokens via Hub V2.

  • LBP Starter

    This tool helps users easily create and manage Liquidity Bootstrapping Pools (LBPs) using their Circles tokens. It allows local groups or organizations to launch an LBP by selecting a group token (CRC), choosing a paired asset (like sDAI or GNO), specifying the amount of each token, and optionally configuring advanced parameters such as weight curves, swap fees, and duration. It also supports reloading or continuing with existing LBP contracts for inspection or finalization.

  • Token Distribution Checker

    This is an analytics tool for exploring the distribution of ERC-1155 Circles tokens. It allows users to input any Circles-compatible token address and fetch a full breakdown of its holders using the Circles Hub RPC API. The tool visually presents token holdings using a pie chart (with custom legends), paginated data tables, and balance percentages per holder. Each address is enriched with profile names (if available), explorer links, quick copy buttons, and the ability to recursively check other token distributions. Users can export the full dataset as CSV, verify the raw API data via a debug panel, and dynamically navigate token ownership. This tool is ideal for community organizers, protocol designers, or auditors who want transparency and insight into token spread, supply concentration, and holder identity.

  • Legagacy Circles Safe Creator

    This tool is designed to help users deploy a Gnosis Safe (v1.1.1) contract instance with a single owner, tailored for legacy use within the Circles ecosystem. By connecting a MetaMask wallet, users can specify any valid Ethereum address as the owner and generate a new Safe contract using a proxy factory and master copy on Gnosis Chain. The tool handles address validation, transaction submission, and post-deployment verification to confirm that the specified address was correctly set as the owner of the Safe. It is especially useful for onboarding Circles users who need a compatible Safe to manage CRC tokens and interact with legacy dApps.

  • Onboarding Helper

    This tool allows people to easily join a Circles group by scanner a QR code.

  • Equilibrium Analaysis

    This tool provides a dynamic view into market efficiency and arbitrage activity within the Circles economy. It visualizes the moving average of arbitrage profit opportunities over time—expressed in USD—based on configurable time windows, helping identify trends in CRC market imbalances. Simultaneously, it offers a price equilibration analysis by showing the distribution of price spreads between liquid CRC token pairs, flagging deviations from parity using customizable thresholds, liquidity filters, and deviation types (absolute or relative). Together, these insights help traders, analysts, and protocol maintainers detect inefficiencies, monitor arbitrage potential, and assess the overall health of price discovery in the Circles ecosystem.

  • Pathfinder Pro App

    This is an advanced visualization and simulation tool that computes and displays optimal token transfer paths through the Circles network graph. Users can specify a source address, destination address, and CRC value, along with custom token inclusion filters for both sender and receiver. The tool supports optional wrapped token inclusion and lets users configure visual and performance parameters—such as edge curvature, capacity gradients, node/edge labels, and tooltip details—for clarity and analysis. Upon clicking “Find Path,” the graph engine simulates feasible routes, showing flow capacity and rendering performance in real-time. This makes the Pathfinder useful for debugging trust routes, analyzing payment viability, and exploring how CRC tokens can move through the social trust graph, especially in complex multi-hop scenarios.

  • Personal / Human Avatars

    Circles SDK exposes human avatars through the HumanAvatar class, which implements the AvatarInterfaceV2 contract. Each instance wraps the Circles V2 hub contracts, profile service, and pathfinding helpers so apps can manage invitations, minting, trust, and transfers with minimal boilerplate.

    Key Characteristics

    • Invitation-based onboarding – every human avatar must be invited by an existing avatar that escrows 100 CRC via avatar.invite.send.

    • Profile-backed identities – v2 humans always reference an IPFS CID (pinned through the Profiles service) that stores metadata such as name, description, and media URLs.

    • Personal token minting – humans accrue up to 24 CRC per day and can mint accumulated issuance via avatar.personalToken.mint.

    • Trust graph participation – avatar.trust.add/remove/isTrusting manages bilateral trust needed for pathfinding and transfers.

    • Transfer orchestration – avatar.transfer.direct and avatar.transfer.advanced support both direct CRC transfers and multi-hop, wrapped-balance flows.

    AvatarRow Data

    Human avatars retrieved from Sdk#getAvatar or CirclesRpc.avatar.getAvatarInfo surface an AvatarRow payload. V2 humans are tagged with CrcV2_RegisterHuman events and look like:

    • avatar / tokenId – human avatars use their address as the ERC‑1155 token id.

    • cidV0 – IPFS CID pointing to the profile JSON; cidV0Digest is the on-chain metadata digest.

    • hasV1 – indicates whether the address has a legacy v1 identity that can still be migrated.

    Creating V2 Human Avatars

    1. Invite the new human – the inviter uses an existing HumanAvatar (fetched via Sdk#getAvatar) and escrows 100 CRC:

    1. Register / accept invitation – the invitee calls sdk.register.asHuman. Pass either an existing CID or a Profile object; the SDK auto-pins profile objects to the Circles profile service before registering with the hub.

    Working With Profiles

    • avatar.profile.get() – fetches and caches the IPFS JSON referenced by cidV0.

    • avatar.profile.update(profile) – pins new metadata and updates the NameRegistry digest; useful for avatar name/description/image changes.

    • sdk.register.asHuman(inviter, profileOrCid) – automatically uses the Profiles service when a plain object is supplied, so invitees do not need prior CID knowledge.

    Trust & Transfers

    • Establish trust through avatar.trust.add('0xFriend...', expiry?), remove trust via avatar.trust.remove, and inspect relationships using avatar.trust.getAll() or avatar.trust.isTrusting.

    • Transfer CRC with avatar.transfer.direct('0xRecipient...', 25n * 10n ** 18n) for simple paths, or avatar.transfer.advanced to opt into wrapped balances, token filters, or encoded metadata.

    Setting Circles Profiles

    Circles avatars are ERC‑1155 tokens whose metadata field points to an IPFS CID. The JSON stored at that CID contains the human-friendly profile (name, description, images, etc.) that explorers and UIs render. @aboutcircles/sdk exposes helpers on every avatar instance, while @aboutcircles/sdk-profiles provides you a lightweight client for direct pin/get calls.

    Profile Data Model

    The profile payload mirrors the Profile interface from @aboutcircles/sdk-types, and the Circles profile service enforces the same shape when accepting uploads.

    SDK Methods

    1) getAvatar

    Gets an avatar instance by address.

    • Parameters:

    Create a Circles Group

    This page is a comprehensive step-by-step guide on how to create your group on the Circles platform. You'll find all the necessary details and instructions here to set up your group.

    Prerequisites

    Make sure you’ve completed these before continuing.

    Glossary

    This glossary contains terms and definitions used throughout the Circles documentation.

    ERC-1155

    ERC-1155 is an Ethereum token standard that enables the efficient transfer and bundling of multiple fungible and non-fungible tokens in a single transaction. This multi-token standard allows for the creation of complex token systems, such as those used in gaming or supply chain management, where different types of tokens need to be managed simultaneously.

    The standard introduces a new set of functions, including safeTransferFrom, safeBatchTransferFrom, and balanceOfBatch, which allow for the transfer and querying of multiple token balances in a single call. This reduces gas costs and simplifies token management compared to using multiple ERC-20 or ERC-721 contracts.

    Famjam | ETHGlobalethglobal
    Runner Up
    woleth.eth | ETHGlobalethglobal
    Runner Up
    Circles Subscriptz | ETHGlobalethglobal
    Winner of Blockscout Explorer Pool Prize

    ERC-1155 tokens are identified by a unique combination of an address and an ID, allowing for the creation of an unlimited number of token types within a single contract. The standard also includes an optional metadata extension, enabling developers to associate additional information, such as images or descriptions, with each token type.

    See also:

    • ERC-1155 Multi Token Standard on Ethereum.org

    Externally-Owned Account

    An externally-owned account (also known as EOA) is one of the two types of Ethereum accounts. A private key controls it; it has no code, and users can send messages by creating and signing Ethereum transactions.

    See also:

    • Ethereum Accounts on ethereum.org

    • Ethereum Whitepaper on ethereum.org

    Gasless Transaction

    Gasless transactions (also known as meta-transactions) are Ethereum transactions that are executed by a third party called relayer on behalf of a smart account to abstract the use of gas. Users must sign a message (instead of the transaction itself) with information about the transaction they want to execute. A relayer will create the Ethereum transaction, sign and execute it, and pay for the gas costs. The main benefit is that users can interact with the blockchain without holding the native token in their account.

    See also:

    • Relay Kit documentation on docs.safe.global

    Network

    A blockchain network is a collection of interconnected computers that utilize a blockchain protocol for communication. Decentralized networks allow users to send transactions, that are processed on a distributed ledger with a consensus mechanism ensuring the batching, verification, and acceptance of data into blocks. This structure enables the development of applications without the need for a central authority or server.

    See also:

    • Networks on ethereum.org

    Owner

    A Safe owner is one of the accounts that control a given Safe. Only owners can manage the configuration of a Safe and approve transactions. They can be either externally-owned accounts or smart accounts. The threshold of a Safe defines how many owners need to approve a Safe transaction to make it executable.

    See also:

    • OwnerManager.sol on github.com

    Relayer

    A relayer is a third-party service acting as an intermediary between users' accounts and blockchain networks. It executes transactions on behalf of users and covers the associated execution costs, which may or may not be claimed.

    See also:

    • What's Relaying? on docs.gelato.network

    Safe Wallet

    Safe is a smart contract wallet that requires a minimum number of people to approve a transaction before it can occur (M-of-N). If for example you have 3 main stakeholders in your business, you are able to set up the wallet to require approval from 2 out of 3 (2/3) or all 3 people before the transaction is sent. This assures that no single person could compromise the funds.

    See also:

    • What is Safe?

    Smart Account

    A smart account (also known as a smart contract account) leverages the programmability of smart contracts to extend its functionality and improve its security in comparison with externally-owned accounts. Smart accounts are controlled by one or multiple externally-owned accounts or other smart accounts, and all transactions have to be initiated by one of those.

    Some common features that smart accounts offer to their users are:

    • Multi-signature scheme

    • Transaction batching

    • Account recovery

    • Gasless transactions

    Safe is one of the most trusted implementations of a smart account.

    Transaction

    A transaction is an action initiated by an externally-owned account to update the state of the EVM network. Transaction objects must be signed using the sender's private key, require a fee, and be included in a validated block.

    A Safe transaction is a transaction sent to a Safe Proxy contract calling the execTransaction method.

    See also:

    • Transactions on ethereum.org

    Threshold

    The threshold of a Safe account is a crucial configuration element that enables using Safe as a multi-signature smart account. It defines the number of required confirmations from the Safe owners a (Safe) transaction must have to be executable.

    See also:

    • Get the threshold and change the threshold of a Safe with the Safe{Core} SDK on docs.safe.global

    Wallet

    A wallet is an interface or application that gives users control over their blockchain account. Wallets allow users to sign in to applications, read their account balance, send transactions, and verify their identity.

    See also:

    • Ethereum Wallets on ethereum.org

  • timestamp, blockNumber, logIndex – useful for freshness checks or historical queries.

  • Replenish personal (unwrapped) CRC by converting wrapped holdings with avatar.balances.replenish(); the SDK uses the pathfinder to determine the optimal flow back to the avatar’s personal token.

    avatarAddress (required): Avatar wallet address.
  • Returns: HumanAvatar | OrganisationAvatar | BaseGroupAvatar

  • Example:

  • 2) register.asHuman

    Registers a human avatar; handles invitation redemption automatically.

    • Parameters:

      • inviter (required): Address of inviting avatar.

      • profile (required): Profile object or CID string.

    • Returns: HumanAvatar

    • Example:

    3) register.asOrganization

    Registers an organization avatar with profile data.

    • Parameters:

      • profile (required): Profile object or CID string; must include name.

    • Returns: OrganisationAvatar

    • Example:

    4) register.asGroup

    Registers a Base Group with profile data.

    • Parameters:

      • owner, service, feeCollection (required): Addresses.

      • initialConditions (required): Array of condition contract addresses (can be empty).

      • name (required, ≤19 chars), symbol (required).

      • profile (required): Profile object or CID.

    • Returns: BaseGroupAvatar

    • Example:

    5) profiles.create (global)

    Pins profile data and returns CID.

    • Parameters: profile (required): Profile object.

    • Returns: CID string.

    • Example:

    6) avatar.profile.update (avatar-bound)

    Pins profile data and updates on-chain metadata digest for that avatar.

    • Parameters: profile (required): Profile object.

    • Returns: CID string.

    • Example:

    7) tokens.getInflationaryWrapper

    Gets the inflationary ERC20 wrapper address for an avatar’s token (or zero address if undeployed).

    • Parameters: address (required): Avatar address.

    • Returns: Wrapper address or zero.

    • Example:

    8) tokens.getDemurragedWrapper

    Gets the demurraged ERC20 wrapper address for an avatar’s token (or zero address if undeployed).

    • Parameters: address (required): Avatar address.

    • Returns: Wrapper address or zero.

    • Example:

    9) Wrapping (avatar.wrap)

    Wrap/unwrap ERC1155 CRC into ERC20 wrappers.

    • Wrap demurraged:

    • Wrap inflationary:

    • Unwrap demurraged:

    • Unwrap inflationary:

    • Parameters:

      • avatarAddress / wrapperAddress (required)

      • amount (required, bigint, atto‑CRC)

    • Returns: Transaction receipt.

    • Example:

    10) Transfers (avatar.transfer)

    • Advanced (pathfinding + unwrap/rewrap):

    • Direct (no pathfinding):

    • Max flow helpers:

    • Key optional options: useWrappedBalances, fromTokens, toTokens, excludeFromTokens, excludeToTokens, simulatedBalances, maxTransfers, txData.

    • Example:

    11) Trust graph (avatar.trust)

    • expiry optional (defaults to max uint96).

    • Example:

    12) Balances & history

    • Example:

    13) Events

    • Returns: subscribeToEvents resolves when WS subscription is active; events is an observable stream of Circles events.

    {
      "blockNumber": 12345678,
      "logIndex": 42,
      "transactionIndex": 3,
      "timestamp": 1620000000,
      "transactionHash": "0x123abc456def789ghi...",
      "version": 2,
      "type": "CrcV2_RegisterHuman",
      "avatar": "0xabcdef1234567890abcdef1234567890abcdef12",
      "tokenId": "0xabcdef1234567890abcdef1234567890abcdef12",
      "hasV1": false,
      "isHuman": true,
      "cidV0Digest": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
      "cidV0": "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"
    }
    const sdk = new Sdk(circlesConfig[100], runner);
    const inviter = await sdk.getAvatar('0xInviter...');
    await inviter.invite.send('0xInvitee...');
    const sdk = new Sdk(circlesConfig[100], inviteeRunner);
    
    // Accept using an existing CID
    const avatarFromCid = await sdk.register.asHuman('0xInviter...', 'QmCID...');
    console.log(avatarFromCid.avatarInfo);
    
    // Accept using an inline profile (CID is created under the hood)
    const avatarFromProfile = await sdk.register.asHuman('0xInviter...', {
      name: 'My profile name',
      description: 'Coop member',
      avatarUrl: 'https://example.com/me.png'
    });
    console.log(avatarFromProfile.avatarInfo);
    sdk.getAvatar(avatarAddress: string): Promise<Avatar>;
    const avatar = await sdk.getAvatar('0x123...abc');
    sdk.register.asHuman(inviter: string, profile: Profile | string): Promise<HumanAvatar>;
    const human = await sdk.register.asHuman('0xInviter', { name: 'Alice', description: 'Dev' });
    sdk.register.asOrganization(profile: Profile | string): Promise<OrganisationAvatar>;
    const org = await sdk.register.asOrganization({ name: 'Org', description: 'Example org' });
    sdk.register.asGroup(
      owner: string,
      service: string,
      feeCollection: string,
      initialConditions: string[],
      name: string,
      symbol: string,
      profile: Profile | string
    ): Promise<BaseGroupAvatar>;
    const group = await sdk.register.asGroup(
      owner,
      service,
      feeCollector,
      [],
      'GroupName',
      'GRP',
      { name: 'GroupName', description: 'Example group' }
    );
    sdk.profiles.create(profile: Profile): Promise<string>;
    const cid = await sdk.profiles.create({ name: 'Jane', description: 'Developer' });
    avatar.profile.update(profile: Profile): Promise<string>;
    const cid = await avatar.profile.update({ name: 'Jane', description: 'Updated bio' });
    sdk.tokens.getInflationaryWrapper(address: string): Promise<string>;
    const inflWrapper = await sdk.tokens.getInflationaryWrapper('0xAvatar...');
    sdk.tokens.getDemurragedWrapper(address: string): Promise<string>;
    const demWrapper = await sdk.tokens.getDemurragedWrapper('0xAvatar...');
    avatar.wrap.asDemurraged(avatarAddress: string, amount: bigint): Promise<TransactionReceipt>;
    avatar.wrap.asInflationary(avatarAddress: string, amount: bigint): Promise<TransactionReceipt>;
    avatar.wrap.unwrapDemurraged(wrapperAddress: string, amount: bigint): Promise<TransactionReceipt>;
    avatar.wrap.unwrapInflationary(wrapperAddress: string, amount: bigint): Promise<TransactionReceipt>;
    await avatar.wrap.asInflationary(avatar.address, BigInt(5e18));
    avatar.transfer.advanced(
      to: string,
      amount: bigint,
      options?: AdvancedTransferOptions
    ): Promise<TransactionReceipt>;
    avatar.transfer.direct(
      to: string,
      amount: bigint,
      tokenAddress?: string,
      txData?: Uint8Array
    ): Promise<TransactionReceipt>;
    avatar.transfer.getMaxAmount(to: string): Promise<bigint>;
    avatar.transfer.getMaxAmountAdvanced(to: string, options?: PathfindingOptions): Promise<bigint>;
    const receipt = await avatar.transfer.advanced('0xRecipient', BigInt(10e18), { useWrappedBalances: true });
    avatar.trust.add(avatarOrList: string | string[], expiry?: bigint): Promise<TransactionReceipt>;
    avatar.trust.remove(avatarOrList: string | string[]): Promise<TransactionReceipt>;
    avatar.trust.isTrusting(address: string): Promise<boolean>;
    avatar.trust.isTrustedBy(address: string): Promise<boolean>;
    avatar.trust.getAll(): Promise<AggregatedTrustRelation[]>;
    await avatar.trust.add('0xFriend'); // indefinite
    avatar.balances.getTotal(): Promise<bigint>;
    avatar.balances.getTokenBalances(): Promise<TokenBalanceRow[]>;
    avatar.history.getTransactions(limit?: number, sortOrder?: 'ASC' | 'DESC'): PagedQuery<TransactionRow>;
    const tokens = await avatar.balances.getTokenBalances();
    await avatar.subscribeToEvents();
    avatar.events.subscribe((event) => console.log(event.$event, event));
    avatar.unsubscribeFromEvents();
    Field
    Type
    Notes
    Required

    name

    string

    Display name for the avatar or group.

    ✅

    description

    string

    Free-form text for bios or summaries.

    previewImageUrl

    string (uri)

    Must be a base64 data URL that matches the preview requirements below.

    For groups, GroupProfile extends Profile with a required symbol property so downstream tools can display the token ticker alongside other metadata.

    Creating & Pinning Profiles

    The SDK automatically creates a Profiles client using the profileServiceUrl from circlesConfig. You can call it directly when building forms or admin panels:

    If you already know the CID (for example, you pinned it off-chain), you can fetch it later via await sdk.profiles.get(cid) or through any avatar instance using await avatar.profile.get().

    Updating On-Chain Metadata

    For avatars you control:

    1. Pin the JSON: const cid = await avatar.profile.update(profileData);

    2. The helper converts the CIDv0 into the bytes32 digest (cidV0ToHex) and submits nameRegistry.updateMetadataDigest.

    3. Subsequent reads pull the updated profile, and you can register short names via avatar.profile.registerShortName(nonce) if desired.

    You can also skip the pinning step when you already have a CID:

    Group Profiles and Registration

    Group metadata uses the same pinning flow, but the registration step also needs the group symbol and deployment parameters for the BaseGroup factory. The SDK’s register.asGroup helper wraps all of that:

    register.asGroup pins the groupProfile, deploys the BaseGroup via core.baseGroupFactory, extracts the new group address from the BaseGroupCreated event, and returns a BaseGroupAvatar wired to your runner.

    Preview Image Requirements

    When you embed an image as previewImageUrl, the profile service enforces a few constraints to keep responses lightweight:

    • Format: PNG, JPEG, or GIF.

    • Dimensions: exactly 256×256 pixels.

    • File size: ≤ 150 KB after compression.

    • Encoding: Base64-encoded data URL (e.g., data:image/png;base64,...).

    Here’s a browser-friendly helper that crops and compresses user uploads before pinning:

    Profiles that violate those constraints are rejected by the pinning service, so validate on the client and surface helpful errors to your users.

    Reading Profiles Back

    • Via avatars: await avatar.profile.get() pulls and caches the current metadata, while avatar.profile.update handles write flows.

    • Via RPC: CirclesRpc.avatar.getAvatarInfo includes the cidV0 so you can fetch profile blobs without instantiating an avatar class.

    • Via the profiles client: await sdk.profiles.get(cid) is ideal for batch jobs or server-side verification.

    Because the CID is stored on-chain, any tool that knows the avatar address can look up its metadata by combining RPC data with the profile service.

    You’ve received an invite link, and you have an active Gnosis app account
  • Using a Chrome-based desktop browser or Firefox is recommended (the app is not yet optimised for mobile devices)

    Creating a New Group

    1. Connecting

    Visit app.aboutcircles.com and click “Get started”

    On the “Access Circles”, select the “Use Safe” option.

    This will allow you to either:

    1. Create a new Safe which will act as the “owner” of the group you create in the next steps, or

    2. Access a list of existing Safes associated with your connected wallet

    2. Create the owner's Safe

    From this page, we’ll create and deploy a new Safe wallet. This Safe will function as the owner of your new group.

    1. Click the “Create New Safe” button,

    2. Sign the transaction in your web3 wallet (eg Metamask, Rabby).

    3. Create your first group

    Now that your Safe has been deployed, you will be able to see the address (eg 0x2A12…). For now, we can ignore the “register” button.

    • Click on the My Groups + button to start the group creation flow

    4. Your group info

    The “Create Group” page enables you to set the important info for your new group.

    Important:

    • The group “Name” and “Symbol” fields cannot be changed once the group has been created

    • The “Image” and “Description” fields can be updated at any time

    5. Group created

    After a few seconds, your new group will be created.

    • Click “Access the dashboard” to view your new group

    6. Dashboard

    The current dashboard displays a list of transactions and a breakdown of the tokens in your Circles group.

    In an upcoming release, this view will also display an overview of key data metrics for your group, including the number of members, group token transactions, token distribution and more.

    7. Managing members

    To add existing Circles users to your new group, navigate to the “Contacts” page and click the “Manage members” button.

    This will display the member management form, where you can enter the address of the members you want to add.

    You can also use the bulk upload option by importing a CSV file with a list of addresses.

    Once you have added the addresses of all the members you wish to add to the group:

    • Click the “add” button to start the process

    • Sign the transaction in your wallet

    • Once the transaction has completed, close the contact form popup, and you will see the new members in the list

    8. Update the group info

    Non-editable fields:

    • Circles address - this is the address of the group contract

    Editable fields:

    • Group name - this is the name of the group as displayed in the Gnosis app

    • Group description - this is the description for the group that is also displayed in the Gnosis app

    • Location - optionally add a physical location for your group

    • Image - the profile image for your group, also displayed in the Gnosis app

    Access an existing group

    1. Connecting

    Visit app.aboutcircles.com and click “Get started”

    Ensure you have your “owner” EOA (or Safe) selected in Metamask or Rabby.

    On the “Access Circles”, select the “Use Safe” option.

    2. Select your profile

    On this screen, you can access your Circles accounts. Possible account types in Circles are:

    1. Personal

    2. Organisation

    3. Group

    If you have the correct EOA selected in your wallet, then you will see your group in the “My groups” section.

    1. Click on your group name to access your group dashboard (displayed as “My Demo Group” in the screenshot below)

    3. Dashboard

    The current dashboard displays a list of transactions and a breakdown of the tokens in your Circles group.

    In an upcoming release, this view will also display an overview of key data metrics for your group, including the number of members, group token transactions, token distribution and more.

    4. Managing members

    To add existing Circles users to your new group, navigate to the “Contacts” page and click the “Manage members” button.

    This will display the member management form, where you can enter the address of the members you want to add.

    You can also use the bulk upload option by importing a CSV file with a list of addresses.

    Once you have added the addresses of all the members you wish to add to the group:

    • Click the “add” button to start the process

    • Sign the transaction in your wallet

    • Once the transaction has completed, close the contact form popup, and you will see the new members in the list

    5. Update the group info

    Non-editable fields:

    • Circles address - this is the address of the group contract

    Editable fields:

    • Group name - this is the name of the group as displayed in the Gnosis app

    • Group description - this is the description for the group that is also displayed in the Gnosis app

    • Location - optionally add a physical location for your group

    • Image - the profile image for your group, also displayed in the Gnosis app


    We hope you've completed your setup smoothly!

    Now that you've successfully created your Circles Group, you're ready to dive deeper into the technical details that form the foundation of your project. In the next section, we'll explore the protocol-level configurations and advanced features that power your group's operations.

    Rabby
    Metamask

    Query Circles Data

    The CirclesData class provides an easy-to-use selection of common queries that are suitable for most use cases.

    Initialization

    Most of the previously shown avatar methods internally use the CirclesData class with filters for the current avatar address. If you already have a configured Sdk instance, you can use the sdk.data property to access the class:

    Otherwise you can also create an instance like this:

    const circlesRpc = new CirclesRpc("https://rpc.aboutcircles.com/");
    const data = new CirclesData(circlesRpc);

    Get avatar info

    The getAvatarInfo(avatar: string): Promise<AvatarRow | undefined> method finds basic information about an avatar. This includes the signup timestamp, circles version, avatar type (human, organization or group), and token address/id as well as it's profile CID (if any).

    Get token info

    The getTokenInfo(tokenId: string): Promise<TokenInfoRow | undefined> methods finds basic information about a Circles token. This includes the creation timestamp, circles version, token type (human or group) and the address of the avatar that created the token.

    Get total balance (v1, v2)

    The total Circles balance of an avatar is the sum of all it's personalized and group token holdings. It can be queried with the getTotalBalance(avatar:string): Promise<string> method. There is a separate method for each Circles version.

    The methods have a second, optional parameter asTimeCircles?: boolean = true that controls the return value format. The default value (true) returns a floating point number as a string, while false returns a bigint number as a string. If you want to use the value for calculations you need to parse them.

    Get detailed token balances (v1, v2)

    In contrast to the above method, the getTokenBalances(avatar: string): Promise<TokenBalanceRow[]> method gives a detailed overview of an avatar's Circles holdings. As with the method above, this one also exists for both versions of the Circles protocol.

    The result row contains the token, balance and the tokenOwner.

    The methods have a second, optional parameter asTimeCircles?: boolean = true that controls the return value format. The default value (true) returns a floating point number as a string, while false returns a bigint number as a string. If you want to use the value for calculations you need to parse them.

    Get transaction history

    The getTransactionHistory(avatar: string, pageSize: number): CirclesQuery<TransactionHistoryRow> method can be used to query all incoming and outgoing Circles transfers from and to an avatar. This includes minting and transfers of personal and group Circles for v1 and v2.

    The result rows have the following properties:

    • timestamp When the transaction happened

    • transactionHash

    • version If the transaction happened in Circles v1 or v2

    The results are ordered in descending order.

    Get trust relations

    The getTrustRelations(avatar: string, pageSize: number): CirclesQuery<TrustListRow> method can be used to query the effective trust events for an avatar. Already expired or removed trust relations are omitted from the results.

    The results of this method contain one row per incoming or outgoing event. This is useful when you need to know when a relation was established. However, if you just want to display a contact list you should consider using getAggregatedTrustRelations(avatarAddress: string): Promise<TrustRelationRow[]> instead.

    Get aggregated trust relations

    In contrast to the above method, this method queries all relevant trust events and groups mutual trust events into a single row instead of one for each direction.

    The result rows have the following properties:

    • subjectAvatar The acting avatar

    • relation The relation between the acting avatar and the one it's related to

    • objectAvatar The other avatar

    The possible relations are: trusts, trustedBy, mutuallyTrusts, and selfTrusts. The last one (selfTrusts) exists because, in Circles, every avatar trusts itself.

    Find groups

    Circles groups have a name and symbol that's stored on-chain. You can use the findGroups(pageSize: number, params: GroupQueryParams): CirclesQuery<GroupRow> method to find groups by name or symbol.

    The params parameter can be used to filter and order the result set by the name and symbol of a group.

    Use the method as following:

    If an avatar is member at a group (as defined by a trust relation from the group to the avatar), it's usually eligible to mint tokens of that group.

    Get group memberships

    You can query the group memberships of an avatar using the getGroupMemberships(avatar: string, pageSize: number): CirclesQuery<GroupMembershipRow> method to get a list of all groups an avatar is a member of.

    The result rows contain the following properties: group, member, expiryTime.

    If you want to query the details of the returned groups, you can pass the group addresses into the groupAddressIn filter field of the findGroups() method.

    Get invited users

    Avatars can invite others to join Circles. The getInvitations(avatar: string, pageSize: number): CirclesQuery<InvitationRow> method returns a list of invitations sent by the specified avatar.

    Th e result rows contain the follwing properties: timestamp, transactionHash, inviter, invited.

    Get invited by

    You can query who invited an other avatar by calling getInvitedBy(avatar:string): Promise<string|undefined>. If the avatar wasn't invited, the method returns undefined.

    BraceBuddy | ETHGlobalethglobal
    First Prize
    Logo
    Logo
    Logo

    Quickstart Guide for Circles SDK

    This guide explains how to use the latest @aboutcircles packages to build a browser-ready Circles dApp. Follow the steps to move from wallet prerequisites to a fully initialised Sdk instance capable of handling avatar reads, trust writes, transfers, profile updates, and the auxiliary helper surfaces exposed in v2.

    1. Prerequisites

    • Browser wallet such as MetaMask or Rabby.

    import type { Profile, GroupProfile } from '@aboutcircles/sdk-types';
    
    const humanProfile: Profile = {
      name: 'John Doe',
      description: 'Web3 Developer',
      imageUrl: 'https://example.com/image.jpg',
      previewImageUrl: 'data:image/jpeg;base64,...',
      location: 'Berlin, Germany',
      geoLocation: [52.52, 13.4050],
      extensions: { twitter: '@john' },
    };
    
    const groupProfile: GroupProfile = {
      name: 'My Community Group',
      symbol: 'MCG',//REQUIRED
      description: 'A community group for local initiatives',
      imageUrl: 'https://example.com/group-image.jpg',
    };
    import { Sdk } from '@aboutcircles/sdk';
    import { circlesConfig } from '@aboutcircles/sdk-core';
    
    const runner = /* your ContractRunner */;
    await runner.init();
    
    const sdk = new Sdk(circlesConfig[100], runner);
    const profile = {
      name: 'John Doe',
      description: 'Web3 Developer',
      previewImageUrl: 'data:image/jpeg;base64,...',
    };
    
    const profileCid = await sdk.profiles.create(profile);
    console.log('Pinned profile CID:', profileCid);
    await avatar.profile.updateMetadata('QmExampleCidV0');
    import type { GroupProfile } from '@aboutcircles/sdk-types';
    
    const groupProfile: GroupProfile = {
      name: 'My Community Group',
      symbol: 'MCG',
      description: 'A community group for local initiatives',
      imageUrl: 'https://example.com/group-image.jpg',
    };
    
    const owner = runner.address!;
    const service = circlesConfig[100].coreMembersGroupDeployer;
    const feeCollection = circlesConfig[100].standardTreasury;
    const initialConditions: Address[] = [];
    
    const groupAvatar = await sdk.register.asGroup(
      owner,
      service,
      feeCollection,
      initialConditions,
      groupProfile.name,
      groupProfile.symbol,
      groupProfile
    );
    const preparePreview = (file: File) =>
      new Promise<string>((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => {
          const img = new Image();
          img.src = reader.result as string;
          img.onload = () => {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            const size = 256;
    
            if (!ctx) return reject(new Error('Canvas context unavailable'));
            canvas.width = canvas.height = size;
            ctx.drawImage(img, 0, 0, size, size);
    
            const dataUrl = canvas.toDataURL('image/jpeg', 0.5);
            if (dataUrl.length > 150 * 1024) {
              console.warn('Preview exceeds 150 KB after compression');
            }
            resolve(dataUrl);
          };
          img.onerror = reject;
        };
        reader.onerror = reject;
        reader.readAsDataURL(file);
      });
    const data = sdk.data;

    imageUrl

    string (uri)

    Full-size image (external URL or data URL).

    location

    string

    Human-readable location such as "Berlin, Germany".

    geoLocation

    [number, number]

    Tuple of [latitude, longitude].

    extensions

    Record<string, unknown>

    Add your own structured metadata without waiting for schema changes.

    Circles Toolsaboutcircles.github.io
    operator (the operator that facilitated the transaction - v2 only)
  • from the sender address

  • to the receiver address

  • id in v1: the token address, in v2: the token id

  • value the transferred raw value for the given version (bigint)

  • timeCircles a floating point number representation of the value for display purposes

  • tokenAddress an address representation of the numeric tokenid (v2) or the actual erc20 token address of a v1 personal token

  • const circlesRpc = new CirclesRpc("https://static.94.138.251.148.clients.your-server.de/rpc/");
    const data = new CirclesData(circlesRpc);
  • Gnosis Chain (chain ID 100) configured inside the wallet. Double-check the RPC endpoint, currency symbol (xDAI), and block explorer entries via the Gnosis Chain docs.

  • A small amount of xDAI for gas (available from the mainnet faucet).

  • Keep the wallet unlocked in the same browser context that will load your dApp so the runner can interact to window.ethereum.

    2. Install the Circles SDK Packages

    Install whichever modules you plan to import directly. @aboutcircles/sdk already depends on the others, but pinning them in your app keeps the versions aligned across bundlers.

    • @aboutcircles/sdk – high-level surface your UI consumes (avatars, registration, helper namespaces).

    • @aboutcircles/sdk-core – typed contract wrappers plus circlesConfig.

    • @aboutcircles/sdk-types – shared types including ContractRunner, TransactionRequest, and avatar data models.

    • @aboutcircles/sdk-runner – production runners (SafeContractRunner, SafeBrowserRunner) and error helpers.

    • @aboutcircles/sdk-rpc – typed JSON-RPC client for balances, trust graphs, pathfinding, transactions, and events.

    • @aboutcircles/sdk-transfers – TransferBuilder orchestration around pathfinding + wrapper management; it leans on…

    • @aboutcircles/sdk-pathfinder – utilities for flow matrices, wrapper lookups, and path manipulation.

    • @aboutcircles/sdk-profiles – profile pinning client that the SDK uses internally for metadata updates.

    • viem – RPC + wallet tooling shared by the runner implementations.

    3. Import the SDK Building Blocks

    • Sdk is the primary entry point exposing avatar namespaces (balances, trust, transfer, profile, etc.).

    • Core/circlesConfig provide production-ready contract addresses, service URLs, and default RPC endpoints.

    • ContractRunner defines the interface your signing strategy (EOA, Safe, hardware wallet, …) must satisfy.

    • CirclesRpc, TransferBuilder, and Profiles are optional direct imports when you need to bypass the high-level helpers (analytics views, offline tooling, custom flows).

    • viem primitives make the runner portable across browser wallets and Node environments.

    Need multisig support? @aboutcircles/sdk-runner exports SafeContractRunner (backend) and SafeBrowserRunner (front-end Safe Apps). Swap your custom runner with those when you integrate Safe-based flows.

    4. Choose the Right Network Configuration

    Production (Gnosis Chain Mainnet)

    This object already contains:

    • Service URLs: circlesRpcUrl (aggregated Circles RPC), pathfinderUrl, and profileServiceUrl.

    • Canonical contract addresses: v1HubAddress, v2HubAddress, nameRegistryAddress, invitationEscrowAddress, baseGroupFactoryAddress, baseGroupMintPolicy, standardTreasury, coreMembersGroupDeployer, and liftERC20Address.

    • Metadata used internally by TransferBuilder, CirclesRpc, and the avatar helpers (so you rarely need to hard-code addresses).

    5. Build a Browser-Friendly Contract Runner

    An EOA runner is ideal for MetaMask/Rabby-style single-signers. The snippet below shows a minimal implementation that satisfies the ContractRunner interface with the help of viem:

    Key behaviors:

    • init() requests wallet access and enforces the correct chain ID before any on-chain action.

    • sendTransaction() loops through every TransactionRequest emitted by avatar helpers or TransferBuilder, ensuring multi-step flows (pathfinding, wrapped transfers, etc.) complete in order.

    • Gas estimation, read-only calls, and ENS resolution are delegated to viem so you do not need to reinvent RPC plumbing.

    Prefer a Safe-based UX? Replace the custom runner with SafeBrowserRunner.create(rpcUrl, window.ethereum, safeAddress, gnosis) (or the server-side SafeContractRunner) from @aboutcircles/sdk-runner. Both satisfy the same interface.

    6. Tap Into Lower-Level Packages

    When you need extra control—analytics dashboards, dry-run tooling, custom transfer flows—instantiate the underlying modules alongside the Sdk:

    • Core returns raw transaction requests for HubV2, BaseGroups, Lift ERC-20 wrappers, etc.

    • CirclesRpc streams balances, trust graphs, profile search results, and transaction history straight from the Circles indexers.

    • TransferBuilder leverages @aboutcircles/sdk-pathfinder to create the ordered transaction list (unwraps, approvals, operateFlowMatrix) without sending anything.

    • Profiles is handy for batch jobs or when you want to pin metadata outside of the avatar helpers.

    Reuse the same config everywhere to avoid mismatched service URLs.

    7. Initialise the Circles SDK

    • Passing circlesConfig[100] explicitly removes any ambiguity about the targeted environment.

    • Provide your custom (or Safe) runner so avatar helpers can sign and broadcast state-changing transactions. If you leave the runner undefined, the SDK functions remain read-only.

    • sdk.data exposes RPC-backed helpers for avatars, trust, and balances when you do not need a full avatar instance.

    • sdk.tokens contains convenience helpers for wrappers and token-holder pagination.

    • sdk.groups helps explore group treasuries, membership lists, and holder breakdowns without writing direct RPC calls.

    • sdk.profiles offers direct create/get helpers powered by @aboutcircles/sdk-profiles.

    8. End-to-End Setup Example

    This pattern maps cleanly to a React useEffect, a Next.js server action (with a server-side runner), or a plain script.

    9. Choosing SDK Surfaces

    Pick the surfaces that match your UX:

    • HumanAvatar / OrganisationAvatar / BaseGroupAvatar – mint issuance, manage trust, transfer CRC, wrap/unwrap, and maintain profiles with type-specific helpers.

    • sdk.register – onboard new humans, organisations, and base groups while handling invitation escrows and profile pinning.

    • sdk.data – lightweight RPC views for trust graphs, balances, and avatar metadata (great for dashboards).

    • sdk.tokens – wrapper lookups plus cursor-based holder queries for ERC1155/ERC20 representations.

    • sdk.groups – fetch group members, treasury balances, and holder distributions without juggling contracts manually.

    • sdk.profiles – talk to the profile service directly when you need to pin/update metadata outside avatar flows.

    Decide which combos your application needs up front so you can import only the modules you plan to expose.

    const avatarInfo = await data.getAvatarInfo("0x...");
    if (avatarInfo) {
       console.log("Avatar is signed up at Circles");
    } else {
       console.log("Avatar is not signed up at Circles");
    }
    const tokenInfo = await data.getTokenInfo("0x...");
    if (tokenInfo) {
       console.log("Token is a Circles token");
    } else {
       console.log("Token is not a Circles token");
    }
    const totalBalanceV1 = await data.getTotalBalance("0x...");
    const totalBalanceV2 = await data.getTotalBalanceV2("0x...");
    const detailedCirclesBalancesV1 = await data.getTokenBalances("0x...");
    const detailedCirclesBalancesV2 = await data.getTokenBalancesV2("0x...");
    const query = data.getTransactionHistory("0x...", 25);
    const hasResults = await query.queryNextPage();
    if (!hasResults) {
       console.log("No transactions yet");
       return;
    }
    
    const rows = query.currentPage.results;
    rows.forEach(row => console.log(row));
    const trustsQuery = data.getTrustRelations("0x...", 25);
    const hasResults = await query.queryNextPage();
    if (!hasResults) {
       console.log("No trust events yet");
       return;
    }
    
    const rows = query.currentPage.results;
    rows.forEach(row => console.log(row));
    const trustRelations = await data.getAggregatedTrustRelations("0x..");
    trustRelations.forEach(row => console.log(row));
    export interface GroupQueryParams {
      nameStartsWith?: string;
      symbolStartsWith?: string;
      groupAddressIn?: string[];
      sortBy?: 'age_asc' | 'age_desc' | 'name_asc' | 'name_desc' | 'symbol_asc' | 'symbol_desc';
    }
    const query = data.findGroups(25, {
      nameStartsWith: "Test",
    });
    
    const hasResults = await query.queryNextPage();
    if (!hasResults) {
       console.log("No trust events yet");
       return;
    }
    
    const rows = query.currentPage.results;
    rows.forEach(row => console.log(row));
    const query = data.getGroupMemberships("0x...", 25);
    const hasResults = await query.queryNextPage();
    if (!hasResults) {
       console.log("No trust events yet");
       return;
    }
    
    const rows = query.currentPage.results;
    rows.forEach(row => console.log(row));
    const query = data.getInvitations("0x...", 25);
    const hasResults = await query.queryNextPage();
    if (!hasResults) {
       console.log("No trust events yet");
       return;
    }
    
    const rows = query.currentPage.results;
    rows.forEach(row => console.log(row));
    const invitedBy = await data.getInvitedBy("0x...");
    npm i @aboutcircles/sdk \
          @aboutcircles/sdk-core \
          @aboutcircles/sdk-types \
          @aboutcircles/sdk-runner \
          @aboutcircles/sdk-rpc \
          @aboutcircles/sdk-transfers \
          @aboutcircles/sdk-pathfinder \
          @aboutcircles/sdk-profiles \
          viem
    import { Sdk } from '@aboutcircles/sdk';
    import { Core, circlesConfig, type CirclesConfig } from '@aboutcircles/sdk-core';
    import type { ContractRunner } from '@aboutcircles/sdk-types';
    import { createPublicClient, createWalletClient, custom, http } from 'viem';
    import { gnosis } from 'viem/chains';
    
    // Optional modules for lower-level access when you need them
    import { CirclesRpc } from '@aboutcircles/sdk-rpc';
    import { TransferBuilder } from '@aboutcircles/sdk-transfers';
    import { Profiles } from '@aboutcircles/sdk-profiles';
    const gnosisMainnetConfig = circlesConfig[100];
    import { createPublicClient, createWalletClient, custom, http } from 'viem';
    import { gnosis } from 'viem/chains';
    import type { Address, TransactionReceipt, TransactionRequest } from '@aboutcircles/sdk-types';
    
    const circlesRPC = 'https://rpc.aboutcircles.com';
    
    export const createBrowserRunner = () => {
      const publicClient = createPublicClient({
        chain: gnosis,
        transport: http(circlesRPC),
      });
    
      const walletClient = createWalletClient({
        chain: gnosis,
        transport: custom(window.ethereum),
      });
    
      const runner: ContractRunner = {
        publicClient,
        address: walletClient.account?.address,
        async init() {
          const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });
          this.address = account as Address;
    
          const currentChain = await walletClient.getChainId();
          if (currentChain !== gnosis.id) {
            throw new Error('Please switch your wallet to Gnosis Chain (chainId 100).');
          }
        },
        estimateGas: (tx: TransactionRequest) =>
          publicClient.estimateGas({ account: runner.address!, ...tx }),
        call: (tx: TransactionRequest) =>
          publicClient.call({ account: tx.from || runner.address!, ...tx }),
        resolveName: (name: string) => publicClient.getEnsAddress({ name }),
        async sendTransaction(txs: TransactionRequest[]): Promise<TransactionReceipt> {
          if (!runner.address) throw new Error('Runner not initialised. Call init() first.');
    
          let receipt: TransactionReceipt | undefined;
          for (const tx of txs) {
            const hash = await walletClient.sendTransaction({
              account: runner.address,
              ...tx,
            });
            receipt = await publicClient.waitForTransactionReceipt({ hash });
          }
    
          if (!receipt) {
            throw new Error('No transactions submitted.');
          }
          return receipt;
        },
      };
    
      return runner;
    };
    import { Core, circlesConfig } from '@aboutcircles/sdk-core';
    import { CirclesRpc } from '@aboutcircles/sdk-rpc';
    import { TransferBuilder } from '@aboutcircles/sdk-transfers';
    import { Profiles } from '@aboutcircles/sdk-profiles';
    
    const config = circlesConfig[100];
    const core = new Core(config);
    const rpc = new CirclesRpc(config.circlesRpcUrl);
    const transferBuilder = new TransferBuilder(core);
    const profilesClient = new Profiles(config.profileServiceUrl);
    import { Sdk } from '@aboutcircles/sdk';
    import { circlesConfig } from '@aboutcircles/sdk-core';
    
    const runner = createBrowserRunner();
    await runner.init();
    
    const sdk = new Sdk(circlesConfig[100], runner); // config defaults to mainnet if omitted
    
    const avatar = await sdk.getAvatar(runner.address!);
    const balances = await avatar.balances.getTokenBalances();
    const trustGraph = await sdk.data.getTrustRelations(avatar.address);
    const demurragedWrapper = await sdk.tokens.getDemurragedWrapper(avatar.address);
    
    const members = sdk.groups.getMembers('0xGroupAddress...');
    await members.queryNextPage(); // fetch first page when you need it
    import { Sdk } from '@aboutcircles/sdk';
    import { circlesConfig } from '@aboutcircles/sdk-core';
    import { createBrowserRunner } from './runner'; // use the helper from section 5
    
    async function bootstrapCircles() {
      const runner = createBrowserRunner();
      await runner.init();
    
      const sdk = new Sdk(circlesConfig[100], runner);
      const avatar = await sdk.getAvatar(runner.address!);
    
      console.log(await avatar.profile.get());
    
      const trustGraph = await sdk.data.getTrustRelations(avatar.address);
      console.log(`Trustees: ${trustGraph.length}`);
    
      const topHolders = sdk.tokens.getHolders(avatar.address, 3);
      await topHolders.queryNextPage();
      console.log('Top holders', topHolders.currentPage?.results ?? []);
    }
    
    bootstrapCircles().catch(console.error);
    Logo
    Voting with UBI | ETHGlobalethglobal

    Circles Events Types

    Base Event Configuration

    All events listed below extend this CirclesBaseEvent structure.

    • $event (CirclesEventType): Discriminator for the event kind.

  • blockNumber (number): The block number containing the log.

  • transactionIndex (number): The transaction index within the block.

  • logIndex (number): The log index within the transaction.

  • timestamp (number | optional): Timestamp (when provided by the source).

  • transactionHash (string | optional): The transaction hash.


  • 1. Identity & Registration

    Events related to the creation and management of Avatars, Groups, and Organizations.

    CrcV2_RegisterHuman

    Emitted when a new human avatar is registered.

    • $event: 'CrcV2_RegisterHuman'

    • avatar (string): The address of the registered human.

    • inviter (string): The address of the user who invited them.

    CrcV2_RegisterGroup

    Emitted when a new group is registered.

    • $event: 'CrcV2_RegisterGroup'

    • group (string): The address of the group.

    • mint (string): The address of the mint policy.

    • treasury (string): The treasury address.

    • name (string): The name of the group.

    • symbol (string): The token symbol.

    CrcV2_RegisterOrganization

    Emitted when an organization is registered.

    • $event: 'CrcV2_RegisterOrganization'

    • organization (string): The address of the organization.

    • name (string): The name of the organization.

    CrcV2_InviteHuman

    Emitted when an invitation is sent to a potential user.

    • $event: 'CrcV2_InviteHuman'

    • inviter (string): The address sending the invite.

    • invited (string): The address being invited.

    CrcV2_RegisterShortName

    Emitted when a short name (alias) is registered.

    • $event: 'CrcV2_RegisterShortName'

    • avatar (string): The avatar address.

    • shortName (bigint): The numeric representation of the short name.

    • nonce (bigint): The nonce used for registration.

    CrcV2_Stopped

    Emitted when an avatar status is set to stopped.

    • $event: 'CrcV2_Stopped'

    • avatar (string): The address of the avatar that has stopped.


    2. Token Operations (Standard & Batch)

    Events regarding the movement and minting of tokens (ERC-1155 compliant).

    CrcV2_PersonalMint

    Emitted when a user mints personal circles.

    • $event: 'CrcV2_PersonalMint'

    • human (string): The address of the human minting.

    • amount (bigint): The amount minted.

    • startPeriod (bigint): The start of the minting period.

    • endPeriod (bigint): The end of the minting period.

    CrcV2_GroupMint

    Emitted when tokens are minted via a Group context.

    • $event: 'CrcV2_GroupMint'

    • sender (string): The address initiating the mint.

    • receiver (string): The address receiving the tokens.

    • group (string): The group address.

    • collateral (bigint): The collateral amount involved.

    • amount (bigint): The amount minted.

    • batchIndex (number): The index within the batch operation.

    CrcV2_TransferSingle

    Emitted when a single token ID is transferred.

    • $event: 'CrcV2_TransferSingle'

    • operator (string): The address performing the transfer.

    • from (string): The source address.

    • to (string): The destination address.

    • id (bigint): The token ID.

    • value (bigint): The amount transferred.

    CrcV2_TransferBatch

    Emitted when multiple token IDs are transferred simultaneously.

    • $event: 'CrcV2_TransferBatch'

    • batchIndex (number): The index within the batch operation.

    • operator (string): The address performing the transfer.

    • from (string): The source address.

    • to (string): The destination address.

    • id (bigint): The token ID.

    • value (bigint): The amount transferred.

    CrcV2_ApprovalForAll

    Emitted when an operator is approved to manage all assets.

    • $event: 'CrcV2_ApprovalForAll'

    • account (string): The account owner.

    • operator (string): The address being approved.

    • approved (boolean): Status of approval (true/false).

    CrcV2_TransferSummary

    A high-level summary event for transfers.

    • $event: 'CrcV2_TransferSummary'

    • from (string): The source address.

    • to (string): The destination address.

    • amount (bigint): The aggregated amount.

    • events (string): Encoded reference to related sub-events.


    3. ERC20 Wrappers

    Events related to the wrapping of Circles tokens into standard ERC20 format.

    CrcV2_ERC20WrapperDeployed

    Emitted when a new ERC20 wrapper contract is deployed.

    • $event: 'CrcV2_ERC20WrapperDeployed'

    • avatar (string): The avatar associated with the wrapper.

    • erc20Wrapper (string): The address of the deployed wrapper contract.

    • circlesType (number): The type identifier for the circles.

    CrcV2_Erc20WrapperTransfer

    Emitted when tokens are transferred via the wrapper.

    • $event: 'CrcV2_Erc20WrapperTransfer'

    • tokenAddress (string): The address of the token.

    • from (string): The source address.

    • to (string): The destination address.

    • value (bigint): The value transferred.


    4. Inflation & Demurrage

    Events tracking value conversions between inflationary and demurraged states.

    CrcV2_DepositInflationary

    Emitted when inflationary tokens are deposited.

    • $event: 'CrcV2_DepositInflationary'

    • account (string): The user account.

    • amount (bigint): The raw amount deposited.

    • demurragedAmount (bigint): The equivalent demurraged value.

    CrcV2_WithdrawInflationary

    Emitted when inflationary tokens are withdrawn.

    • $event: 'CrcV2_WithdrawInflationary'

    • account (string): The user account.

    • amount (bigint): The raw amount withdrawn.

    • demurragedAmount (bigint): The equivalent demurraged value.

    CrcV2_DepositDemurraged

    Emitted when demurraged tokens are deposited.

    • $event: 'CrcV2_DepositDemurraged'

    • account (string): The user account.

    • amount (bigint): The demurraged amount deposited.

    • inflationaryAmount (bigint): The equivalent inflationary value.

    CrcV2_WithdrawDemurraged

    Emitted when demurraged tokens are withdrawn.

    • $event: 'CrcV2_WithdrawDemurraged'

    • account (string): The user account.

    • amount (bigint): The demurraged amount withdrawn.

    • inflationaryAmount (bigint): The equivalent inflationary value.

    CrcV2_DiscountCost

    Emitted to record the cost associated with discounting.

    • $event: 'CrcV2_DiscountCost'

    • account (string): The user account.

    • id (bigint): The token ID.

    • cost (bigint): The calculated cost.


    5. Trust, Flows & Metadata

    Events regarding social graphs, data streams, and IPFS metadata.

    CrcV2_Trust

    Emitted when a trust relationship is modified.

    • $event: 'CrcV2_Trust'

    • truster (string): The entity giving trust.

    • trustee (string): The entity receiving trust.

    • expiryTime (bigint): Timestamp when the trust expires.

    CrcV2_StreamCompleted

    Emitted when a token stream is finalized.

    • $event: 'CrcV2_StreamCompleted'

    • operator (string): The address finalizing the stream.

    • from (string): The source address.

    • to (string): The destination address.

    • id (bigint): The token ID.

    • amount (bigint): The total amount streamed.

    • batchIndex (number | optional): The index if part of a batch.

    CrcV2_FlowEdgesScopeSingleStarted

    Emitted when a single flow edge scope begins.

    • $event: 'CrcV2_FlowEdgesScopeSingleStarted'

    • flowEdgeId (bigint): The ID of the flow edge.

    • streamId (number): The ID of the stream.

    CrcV2_FlowEdgesScopeLastEnded

    Emitted when the last flow edge scope ends.

    • $event: 'CrcV2_FlowEdgesScopeLastEnded'

    • (No specific payload properties defined beyond base event)

    CrcV2_UpdateMetadataDigest

    Emitted when an avatar's metadata hash is updated.

    • $event: 'CrcV2_UpdateMetadataDigest'

    • avatar (string): The avatar address.

    • metadataDigest (Uint8Array): The new metadata hash.

    CrcV2_CidV0

    Emitted regarding IPFS Content ID (v0) updates.

    • $event: 'CrcV2_CidV0'

    • avatar (string): The avatar address.

    • cidV0Digest (Uint8Array): The IPFS CID v0 digest.

    CrcV2_URI

    Emitted when the URI for a token ID changes.

    • $event: 'CrcV2_URI'

    • value (string): The URI string.

    • id (bigint): The token ID.


    6. System & Vaults

    CrcV2_CreateVault

    Emitted when a new vault is created.

    • $event: 'CrcV2_CreateVault'

    • vault (string): The address of the new vault.

    • token (string): The associated token address.

    Logo