# 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

{% content-ref url="/pages/IMIhhGo4QR2ecqId9vgj" %}
[Broken mention](broken://pages/IMIhhGo4QR2ecqId9vgj)
{% endcontent-ref %}

{% content-ref url="/pages/dmWDPOpm6jExckgag27o" %}
[Broken mention](broken://pages/dmWDPOpm6jExckgag27o)
{% endcontent-ref %}

{% content-ref url="/pages/Pd75UYlSjUvWoGsExyqi" %}
[Broken mention](broken://pages/Pd75UYlSjUvWoGsExyqi)
{% endcontent-ref %}

### 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.&#x20;
* 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.&#x20;
* **Members of a Circles Groups** can mint a shared CRC backed by members’ coins, giving communities their own monetary layer.&#x20;

Circles also uses an invitation system for onboarding: existing users invite newcomers by paying 96 CRC, while the invitee receives 48 CRC as a welcome bonus. This helps protect network integrity and creates a meaningful cost to discourage spam accounts and Sybil style abuse.

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.<br>

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


# 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.

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>Personal Currencies and Trust Path</strong></td><td><p>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.</p><p>This fungibility allows for the transfer of tokens along a trust path, enabling transactions even between people who do not directly trust each other.</p></td></tr><tr><td><strong>Group Currencies</strong></td><td><p>Additionally, Circles v2 introduces support for group currencies, allowing communities to share a currency backed by their members' personal tokens.</p><p>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.</p></td></tr></tbody></table>


# 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.&#x20;

{% hint style="info" %}
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).
{% endhint %}

### **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.

{% hint style="info" %}
Organizations cannot mint tokens. Therefore, their balance is always subject to a demurrage of 7% per year.
{% endhint %}

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

<figure><img src="/files/84BJSryejNyrGCXb8tUR" alt=""><figcaption></figcaption></figure>

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).

<figure><img src="/files/42Q2PcHi4BqQ0Xj0EYIU" alt=""><figcaption></figcaption></figure>

### **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.


# 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.


# 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.

<table data-card-size="large" data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td>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.</td></tr><tr><td>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.</td></tr><tr><td>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.</td></tr><tr><td>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.</td></tr><tr><td>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.</td></tr><tr><td>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.</td></tr></tbody></table>

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


# Circles Architecture

An overview of different components of circles architecture.

<figure><img src="/files/GkU82bk3B3C0rDYRV6OE" alt=""><figcaption><p>A flow of architecture for Circles Protocol</p></figcaption></figure>

## Circles V2 Core Components

<table data-view="cards"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><mark style="color:blue;"><strong>Hub V2 Contract</strong></mark></td><td><p>An ERC-1155 standard contract for registeration of </p><ul><li>human,</li><li>groups and </li><li>organisation avatars. </li></ul><p>Manages trust relations, minting of personal CRC tokens, group currencies and demurrage.</p></td></tr><tr><td><mark style="color:blue;"><strong>Migration Contract</strong></mark></td><td>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.</td></tr><tr><td><mark style="color:blue;"><strong>Name Registry</strong></mark></td><td><p>NameRegistry contract manages names, symbols and metadata for avatars (humans, groups, and organizations).</p><p>The name would be of 12 characters with a base58 encoding and store metadata for avatar profiles.</p></td></tr><tr><td><mark style="color:blue;"><strong>Base Mint Policy</strong></mark></td><td>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.</td></tr><tr><td><mark style="color:blue;"><strong>Vaults</strong></mark></td><td>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.</td></tr><tr><td><mark style="color:blue;"><strong>Standard Treasury</strong></mark></td><td>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.</td></tr></tbody></table>


# 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.

* [Circles SDK package on npm](https://www.npmjs.com/package/@circles-sdk/sdk)
* [Circles SDK source code](https://github.com/aboutcircles/sdk)

## Circles Infrastructure

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td><strong>Pathfinder</strong></td><td><p>Finds liquid paths between two accounts in the trust network. These paths are used as input for the contract's transfer methods.<br></p><p><a href="https://github.com/aboutcircles/circles-nethermind-plugin/tree/dev/Circles.Pathfinder">Learn more about Pathfinder</a></p></td><td></td></tr><tr><td><strong>Circles Nethermind Plug-in</strong></td><td><p>Provides access to the Gnosis Chain and indexes Circles events for a seamless experience.<br></p><p><a href="https://github.com/aboutcircles/circles-nethermind-plugin">Learn more about Circles Nethermind Plug-in</a></p></td><td></td></tr></tbody></table>

## 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:

<table><thead><tr><th width="192.9765625">Contract Name</th><th width="563.39453125">Deployed addresses</th></tr></thead><tbody><tr><td>Hub contract</td><td>0xc12C1E50ABB450d6205Ea2C3Fa861b3B834d13e8</td></tr><tr><td>Name registry</td><td>0xA27566fD89162cC3D40Cb59c87AAaA49B85F3474</td></tr><tr><td>Migration contract</td><td>0xD44B8dcFBaDfC78EA64c55B705BFc68199B56376</td></tr><tr><td>Base mint policy</td><td>0xcCa27c26CF7BAC2a9928f42201d48220F0e3a549</td></tr></tbody></table>

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.

{% content-ref url="/pages/dmWDPOpm6jExckgag27o" %}
[Broken mention](broken://pages/dmWDPOpm6jExckgag27o)
{% endcontent-ref %}

<table data-view="cards"><thead><tr><th></th><th></th><th data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Circles V2 contract source code</strong></td><td>Review the codebase for Circles contracts V2 which follows ERC1155 standard, and manages personal, group and organisation avatars.</td><td><a href="https://github.com/aboutcircles/circles-contracts-v2">https://github.com/aboutcircles/circles-contracts-v2</a></td></tr><tr><td><strong>Circles V2 Reference docs</strong></td><td>Explore the latest updates and functionalities of Circles v2.0 Contracts with detailed documentation.</td><td><a href="https://aboutcircles.github.io/circles-contracts-v2/">https://aboutcircles.github.io/circles-contracts-v2/</a></td></tr><tr><td>Circles Profile Service</td><td>This service indexes and persists profile data from/to IPFS.</td><td><a href="https://github.com/aboutcircles/profile-service">https://github.com/aboutcircles/profile-service</a></td></tr></tbody></table>

## 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 ](https://t.me/about_circles/1)as a Hacker or a Developer.


# Circles SDK Overview

The [Circles SDK](https://www.npmjs.com/package/@aboutcircles/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 :&#x20;

To use the Circles sdk, install the primary npm package

```
npm install @aboutcircles/sdk
```

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

<table><thead><tr><th width="247">Package</th><th>Purpose</th></tr></thead><tbody><tr><td>@aboutcircles/sdk-core</td><td>Core contract interactions</td></tr><tr><td>@aboutcircles/sdk-rpc</td><td>RPC client for Circles-specific methods</td></tr><tr><td>@aboutcircles/sdk-profiles</td><td>Profile management</td></tr><tr><td>@aboutcircles/sdk-types</td><td>TypeScript type definitions</td></tr><tr><td>@aboutcircles/sdk-utils</td><td>Utility functions</td></tr><tr><td>@aboutcircles/sdk-runner</td><td>Safe multisig wallet integration for executing blockchain operations with the Circles SDK.</td></tr><tr><td>@aboutcircles/sdk-transfers</td><td>Builds Circles SDK transfer payloads by combining pathfinding results with the data and RPC utilities needed to execute a transfer.</td></tr></tbody></table>


# 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.
* 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](https://docs.gnosischain.com/).
* A small amount of xDAI for gas (available from the [mainnet faucet](https://gnosisfaucet.com/)).

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

```bash
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
```

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

```typescript
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';
```

* `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)

```typescript
const gnosisMainnetConfig = circlesConfig[100];
```

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`:

```typescript
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;
};
```

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`:

```typescript
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);
```

* `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

```typescript
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
```

* 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

```typescript
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);
```

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.


# 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.

| 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. |          |
| `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.   |          |

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

```ts
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',
};
```

### 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:

```ts
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);
```

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:

```ts
await avatar.profile.updateMetadata('QmExampleCidV0');
```

### 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:

```ts
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
);
```

`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:

```ts
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);
  });
```

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.


# 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

{% hint style="info" %}
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 Gnosis chain before you continue.
{% endhint %}

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Personal / Human Avatars</strong></td><td>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%.</td><td></td><td><a href="/pages/1bAQ5kQiKjWDMQ49NM64">/pages/1bAQ5kQiKjWDMQ49NM64</a></td></tr><tr><td><strong>Group Avatars</strong></td><td>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.</td><td></td><td><a href="/pages/If95GwuVnPnE9rmxNpmO">/pages/If95GwuVnPnE9rmxNpmO</a></td></tr><tr><td><strong>Organization Avatars</strong></td><td>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.</td><td></td><td><a href="/pages/JkacJwc47BllqmpjvNYz">/pages/JkacJwc47BllqmpjvNYz</a></td></tr></tbody></table>


# 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:

```json
{
  "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"
}
```

* `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.
* `timestamp`, `blockNumber`, `logIndex` – useful for freshness checks or historical queries.

### Creating V2 Human Avatars

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

```ts
const sdk = new Sdk(circlesConfig[100], runner);
const inviter = await sdk.getAvatar('0xInviter...');
await inviter.invite.send('0xInvitee...');
```

2. **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.

```ts
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);
```

### 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.
* 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.


# 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**

```typescript
// 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:

{% code overflow="wrap" %}

```typescript
//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');
```

{% endcode %}

### Get an existing avatar

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

```typescript
const profile = await inviteeAvatar.profile.get();
if (profile) {
  console.log(profile.name, profile.description);
}
```


# Mint personal tokens

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

```ts
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);
}
```

* `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`.

```typescript
const profile = await inviteeAvatar.profile.get();
if (profile) {
  console.log(profile.name, profile.description);
}
```

## Update profile of the avatar

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

```typescript
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);
}
```

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

```ts
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);
}
```


# 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

```ts
// 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']);
```

* 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.
* Passing an array batches all trust updates into one Safe transaction. EOA runners only support single addresses per call.

### Remove Trust

```ts
const receipt = await avatar.trust.remove('0xOtherAvatar');
console.log(receipt.hash);

// Batch revoke (Safe runner only)
await avatar.trust.remove(['0xA', '0xB']);
```

### Check Trust Direction

```ts
const youTrustThem = await avatar.trust.isTrusting('0xAvatar'); // you -> them
const theyTrustYou = await avatar.trust.isTrustedBy('0xAvatar'); // them -> you
console.log({ youTrustThem, theyTrustYou });
```

### Inspect All Trust Relations

```ts
const relations = await avatar.trust.getAll();

relations.forEach((relation) => {
  console.log(`${relation.subjectAvatar} ${relation.relation} ${relation.objectAvatar}`);
});
```

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

* `trusts` – avatars you trust.
* `trustedBy` – avatars that trust you.
* `mutuallyTrusts` – avatars where trust is bidirectional.


# 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

```ts
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

```ts
const tokenBalances = await avatar.balances.getTokenBalances();

tokenBalances.forEach((balance) => {
  console.log(`Token: ${balance.token}, Balance: ${balance.amount}`);
});
```

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


# Transfer personal Circles tokens to different avatar

You can use `avatar.transfer` utilities to reason about and send Circles with or without pathfinding.&#x20;

### Get Maximum Transferable Amount

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

```ts
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

```ts
const amount = BigInt(10e18); // 10 CRC
const receipt = await avatar.transfer.advanced('0xRecipient', amount);
console.log(`Transfer successful! Tx: ${receipt.hash}`);
```

* 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)

```ts
// 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');
```

* 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.


# Group Avatars

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

### Group Structure

Each group in Circles has:

* A unique blockchain address (similar to individual avatars)
* 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.&#x20;
4. **Collective Governance**: Groups implement membership systems that can include conditions and expiry times.


# 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
* 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.

```ts
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);
```

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

### Membership Management

```ts
// List current conditions
const conditions = await groupAvatar.properties.getMembershipConditions();

// Enable/disable a condition
await groupAvatar.setProperties.membershipCondition('0xCondition1', true);
```

### Trust Management

```ts
// 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);
```

### Group Administration

```ts
// 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);
```

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


# 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

```ts
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)

```ts
const maxMintable = await avatar.groupToken.getMaxMintableAmount(groupAddress);
console.log('Maximum mintable amount:', maxMintable.toString());
```

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


# 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)

```ts
const trustReceipt = await groupAvatar.trust.add('0xMemberAvatar');
console.log('Invitation sent (trust added):', trustReceipt.hash);
```

* Trusting a member signals that the group will accept their personal token as collateral.
* Safe runners can batch invites: `groupAvatar.trust.add(['0xA', '0xB']);`.

### Member Accepts (Trust the Group)

```ts
const memberAvatar = await sdk.getAvatar('0xMemberAvatar');
await memberAvatar.trust.add(groupAvatar.address);
```

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

### Revoke a Member (Untrust Them)

```ts
const revokeReceipt = await groupAvatar.trust.remove('0xMemberAvatar');
console.log('Revoked member (trust removed):', revokeReceipt.hash);
```

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


# 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

```ts
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:

```ts
const groups = await rpc.group.findGroups(10, {
  nameStartsWith: 'Community', // optional filters
  // ownerIn: ['0xOwner...'],
  // typeIn: ['BaseGroup'],
});

console.log('Retrieved groups:', groups);
```

* `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.

```ts
const membershipsQuery = rpc.group.getGroupMemberships('0xAvatar', 5);

await membershipsQuery.queryNextPage(); // first page
console.log('Memberships:', membershipsQuery.currentPage?.results);
```

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

### Full Example

```ts
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);
```


# Getting total supply of group tokens available

Use `balances.getTotalSupply()` on avatars that expose a minted token (currently Base Groups).&#x20;

### Get Total Supply for a Base Group

```ts
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.

{% hint style="info" %}
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.
{% endhint %}


# 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.


# Creation of Organizations

Organizations join Circles without invitations and don’t mint personal tokens.&#x20;

### Register the Organization

```ts
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

```ts
const orgFromCid = await sdk.register.asOrganization('QmExistingProfileCid');
```

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


# 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

```ts
const orgAvatar = await sdk.getAvatar('0xOrgAvatar');

const receipt = await orgAvatar.trust.add('0xCollaborator');
console.log('Trust added:', receipt.hash);
```

* Default expiry is max uint96 (indefinite). Optionally pass an expiry timestamp (seconds since epoch).
* Safe runners can batch: `orgAvatar.trust.add(['0xA', '0xB'], expiry)`.

### Remove Trust

```ts
const revoke = await orgAvatar.trust.remove('0xCollaborator');
console.log('Trust removed:', revoke.hash);
```

### Inspect Trust Direction

```ts
const trusts = await orgAvatar.trust.isTrusting('0xPeer');   // org -> peer
const trustedBy = await orgAvatar.trust.isTrustedBy('0xPeer'); // peer -> org
console.log({ trusts, trustedBy });
```

### List All Trust Relations

```ts
const relations = await orgAvatar.trust.getAll();
relations.forEach((rel) => {
  console.log(`${rel.subjectAvatar} ${rel.relation} ${rel.objectAvatar}`);
});
```

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


# 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.

```ts
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
);
```

#### Inflationary/static ERC‑20

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

```ts
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
);
```

### 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.

```ts
const demurragedWrapper = '0xYourDemurragedWrapper';
const unwrapAmount = BigInt('800000000000000000'); // 0.8 CRC

const receipt = await avatar.wrap.unwrapDemurraged(demurragedWrapper, unwrapAmount);
```

#### Static wrapper unwrapping

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

```ts
const staticWrapper = '0xYourStaticWrapper';
const unwrapAmount = BigInt('2000000000000000000'); // 2 CRC

const receipt = await avatar.wrap.unwrapInflationary(staticWrapper, unwrapAmount);
```

### 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.


# 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.

### 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.

```ts
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 }]
```

`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.

```ts
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 }
  ],
});
```

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`:

```ts
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
```

### 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.


# Invitations and Referrals

Use the Circles SDK to spend invite quota that an inviter already holds and register new humans on Circles.

This guide covers two onboarding flows:

* **Existing-wallet users:** invite people who already have a Safe wallet.
* **New users:** create referral links for people who do not have a wallet yet.

The SDK prepares the required transaction batch. A runner executes that batch atomically through the inviter's Safe. You do not need to call the underlying contracts directly.

> **Scope** This guide assumes that the inviter already has invite quota. For getting quota for your community, please reach out to our team on [Telegram](https://t.me/about_circles/499).

### Overview

The SDK exposes two levels of abstraction:

| API                                               | Use it when                                                                                             | Method                                                                      | Execution behavior                                                |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `InviteFarm` from `@aboutcircles/sdk-invitations` | You manage transaction execution yourself, use a server relayer, or need to inspect or extend the batch | `generateInvites(inviter, invitees)` or `generateReferrals(inviter, count)` | Returns unsigned transactions. You pass them to a runner.         |
| `HumanAvatar.invitation` from the top-level SDK   | You already have a runner-backed `HumanAvatar` and want a build-and-send flow                           | `avatar.invitation.generateReferrals(count)`                                | Builds and executes the transactions through the avatar's runner. |

```mermaid
flowchart LR
    APP["Your application"] -->|"generateInvites or generateReferrals"| SDK["InviteFarm"]
    SDK -->|"simulate claim and encode transfer"| TXS["transactions: [claimTx, transferTx]"]
    TXS -->|"runner.sendTransaction(transactions)"| RUNNER["Safe runner"]
    RUNNER -->|"one atomic Safe batch"| CHAIN["Gnosis Chain"]
```

### Prerequisites

Before using `InviteFarm`, make sure that:

1. The inviter already has enough quota for the number of invites.
2. Your configuration includes `referralsServiceUrl`.
3. The inviter's Safe has completed the one-time invitation setup.
4. You have a runner capable of executing the returned transaction array as one atomic Safe batch.

#### One-time inviter setup

Before the inviter sends their first invitation, their Safe must have the `InvitationModule` enabled and the required module-organization trust configured.

Use `Invitations.ensureInviterSetup(inviter)` to generate any missing setup transactions, then execute those transactions once.

### Initialize `InviteFarm`

Create an `InviteFarm` instance from a `CirclesConfig`. The config must include `referralsServiceUrl`, even when you only use farm methods, because `InviteFarm` internally creates an `Invitations` instance.

```ts
import { InviteFarm } from '@aboutcircles/sdk-invitations';
import { circlesConfig } from '@aboutcircles/sdk-utils';

const config = {
  ...circlesConfig[100], // Gnosis Chain defaults: addresses and RPC configuration
  referralsServiceUrl: 'https://example.com/referrals',
};

const farm = new InviteFarm(config);
```

### Check available quota

Each quota unit represents one invitation funded with `96 CRC`.

```ts
const quota = await farm.getQuota(inviterAddress);

if (quota < 1n) {
  throw new Error('No invite quota available');
}
```

Useful helpers:

```ts
const invitationFee = await farm.getInvitationFee(); // 96 CRC as bigint
const invitationModule = await farm.getInvitationModule(); // module address
```

For batch invitations, validate that the inviter has enough quota for the full batch:

```ts
const count = invitees.length;
const quota = await farm.getQuota(inviterAddress);

if (quota < BigInt(count)) {
  throw new Error('Insufficient invite quota');
}
```

> **Note** A quota check is best-effort. The final debit occurs on-chain when `claimInvite()` or `claimInvites(count)` executes. The transaction reverts if the inviter no longer has enough quota.

### Invite users who already have a wallet

Use `generateInvites` for users who already have a Safe wallet but are not yet registered as Circles humans.

The SDK encodes the invitee wallet addresses directly in the transfer data. It does not create accounts for these invitees.

```ts
const { invitees, transactions } = await farm.generateInvites(inviterAddress, [
  '0xAAA...',
  '0xBBB...',
  '0xCCC...',
]);

await runner.sendTransaction(transactions);
```

#### Requirements

* The `invitees` array must not be empty.
* Each invitee must already have a Safe wallet.
* Each invitee's Safe must already have the `InvitationModule` enabled. Use the referral flow for brand-new users instead.

#### Returned transactions

For one invitee, the SDK returns:

```
[claimInvite(), safeTransferFrom(...)]
```

For multiple invitees, the SDK returns:

```
[claimInvites(count), safeBatchTransferFrom(...)]
```

### Create referrals for brand-new users

Use `generateReferrals` for users who do not have a wallet yet.

The SDK creates a temporary keypair for each referral and encodes a `ReferralsModule.createAccount()` or `createAccounts()` call. The on-chain flow pre-deploys a claimable Safe for each new user.

```ts
const { referrals, transactions } = await farm.generateReferrals(inviterAddress, 25);

// Execute the complete batch atomically.
await runner.sendTransaction(transactions);

// Persist each referral secret so it can be included in a referral link or QR code.
for (const { secret } of referrals) {
  await sdk.referrals.store(secret, inviterAddress);
}
```

`generateReferrals` returns one referral object per new user:

```ts
type Referral = {
  secret: Hex;
  signer: Address;
};
```

#### Store referral secrets securely

Each `secret` is the only way to claim its corresponding pre-deployed account. Persist every secret and deliver it to the intended user through a referral link or QR code.

> **Warning** If a referral secret is lost, the pre-deployed account cannot be claimed.

#### How a new user claims a referral

A new user opens a referral link containing the secret and completes the claim flow:

1. Create a device WebAuthn passkey.
2. Sign the module's EIP-712 passkey digest with the referral secret.
3. Relay `ReferralsModule.claimAccount(...)`.
4. Bind the passkey as the Safe signer.
5. Receive the `48 CRC` welcome bonus.
6. Complete registration as a self-custodial Circles human with the original inviter recorded as the permanent inviter.

The claim call has the following shape:

```ts
ReferralsModule.claimAccount(
  x,
  y,
  verifier,
  signature,
  metadataDigest,
  affiliateGroup?,
);
```

### Use the high-level referral API

When you already have a runner-backed `HumanAvatar`, use the high-level API to generate and execute referrals in one call:

```ts
const { secrets, signers, transactionReceipt } =
  await avatar.invitation.generateReferrals(25);

for (const secret of secrets) {
  await sdk.referrals.store(secret, avatar.address);
}
```

The high-level facade also exposes:

```ts
await avatar.invitation.getQuota();
await avatar.invitation.getInvitationFee();
```

For single-invite flows, it also provides:

```ts
await avatar.invitation.getReferralCode();
await avatar.invitation.invite(inviteeAddress);
```

### Execute invitation transactions atomically

Both `generateInvites` and `generateReferrals` return exactly two transactions:

```
[claimTx, transferTx]
```

Always pass the complete array to a runner in a single call:

```ts
await runner.sendTransaction(transactions);
```

Use a runner from `@aboutcircles/sdk-runner`:

* `SafeContractRunner` for server-side execution with a private key.
* `SafeBrowserRunner` for browser-wallet execution.

> **Important** Do not send `claimTx` and `transferTx` separately. During the claim, the funding bot grants the inviter temporary trust that is valid only for the current block. The `InvitationModule` checks that trust when the borrowed CRC arrives. If the transactions are split across blocks, the transfer reverts with `TrustRequired`.

### How `InviteFarm` builds the batch

For every `generateInvites` or `generateReferrals` call, `InviteFarm` performs the following steps:

#### 1. Simulate the claim

The SDK calls `simulateClaim(inviter, count)`, which runs an `eth_call` for `claimInvite()` or `claimInvites(count)` with `from: inviter`.

This simulation determines which farm-bot token IDs will be allocated. The IDs must be known before execution because a Safe batch cannot pass the runtime return value of `claimInvites()` into the next transfer call.

Allocation is deterministic from the current chain state, so the simulated IDs match the real claim unless a competing claim changes the state first.

#### 2. Generate referral secrets when needed

For referral flows, the SDK generates one `{ secret, signer }` pair per new user.

#### 3. Build the two transactions

The SDK creates:

```
claimTx = invitationFarm.claimInvite()
       or invitationFarm.claimInvites(count)

transferTx = hubV2.safeTransferFrom(...)
          or hubV2.safeBatchTransferFrom(...)
```

Each transfer sends `96 CRC` per invitation from the inviter to the `InvitationModule`.

The transfer `data` depends on the onboarding flow:

| Flow                 | Encoded transfer data                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------- |
| Existing-wallet user | `encodeAbiParameters(['address'], [invitee])` or `encodeAbiParameters(['address[]'], [invitees])` |
| New-user referral    | `encodeAbiParameters(['address', 'bytes'], [referralsModule, createAccount(s)(signers).data])`    |

#### 4. Return the batch

The SDK returns:

```ts
{
  // flow-specific values
  transactions: [claimTx, transferTx],
}
```

#### On-chain result

For each successful invitation:

* The inviter's quota decreases by one.
* The farm bot's `96 CRC` funds registration and is returned to the bot.
* The new human is registered with the original inviter recorded as the permanent inviter.

### API reference

#### `InviteFarm`

Package: `@aboutcircles/sdk-invitations`

```ts
new InviteFarm(config: CirclesConfig)
```

`config` must include `referralsServiceUrl`.

```ts
getQuota(inviter: Address): Promise<bigint>

getInvitationFee(): Promise<bigint>

getInvitationModule(): Promise<Address>

listReferrals(
  inviter: Address,
  limit?: number,
  offset?: number,
): Promise<ReferralPreviewList>
```

**`generateInvites`**

```ts
generateInvites(
  inviter: Address,
  invitees: Address[],
): Promise<{
  invitees: Address[];
  transactions: TransactionRequest[];
}>
```

**`generateReferrals`**

```ts
generateReferrals(
  inviter: Address,
  count: number,
): Promise<{
  referrals: {
    secret: Hex;
    signer: Address;
  }[];
  transactions: TransactionRequest[];
}>
```

#### `HumanAvatar.invitation`

High-level, runner-backed facade:

```ts
generateReferrals(count: number): Promise<{
  secrets: Hex[];
  signers: Address[];
  transactionReceipt: unknown;
}>

getQuota(): Promise<bigint>

getInvitationFee(): Promise<bigint>

getReferralCode(): Promise<{
  transactions: TransactionRequest[];
  privateKey: Hex;
}>

invite(invitee: Address): Promise<TransactionRequest[]>
```

#### Runner execution

Package: `@aboutcircles/sdk-runner`

```ts
runner.sendTransaction(
  transactions: TransactionRequest[],
): Promise<TransactionReceipt>
```

Available Safe runners:

```ts
SafeContractRunner
SafeBrowserRunner
```

#### Referral storage

Persist and retrieve referral secrets through the top-level SDK:

```ts
await sdk.referrals.store(privateKey, inviter);
await sdk.referrals.storeBatch(/* ... */);
await sdk.referrals.retrieve(/* ... */);
await sdk.referrals.listMine(/* ... */);
```

### Errors and troubleshooting

| Error or symptom                   | Cause                                                          | Resolution                                                                                                    |
| ---------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `INVITATION_INVALID_COUNT`         | `count <= 0` or the invitee array is empty                     | Pass at least one referral or invitee.                                                                        |
| `TrustRequired`                    | `claimTx` and `transferTx` were sent separately                | Execute the complete `transactions` array through one `runner.sendTransaction(transactions)` call.            |
| `ExceedsInviteQuota`               | The inviter does not have enough quota when the batch executes | Check `getQuota(inviter)` before generating the batch and retry with a smaller batch or after quota is added. |
| `FarmIsDrained`                    | The invitation farm does not have enough capacity              | Retry after the farm has capacity.                                                                            |
| `InviteFarm` cannot be constructed | `referralsServiceUrl` is missing from the config               | Add `referralsServiceUrl` to the `CirclesConfig`.                                                             |
| Referral account cannot be claimed | The referral secret was not stored or was lost                 | Persist every secret immediately after generating referrals.                                                  |

### Implementation checklist

Before shipping an invitation flow, verify that:

* The inviter has completed one-time setup.
* The inviter has enough quota for the requested batch.
* Existing-wallet users go through `generateInvites`.
* New users go through `generateReferrals`.
* Referral secrets are persisted immediately.
* The full `[claimTx, transferTx]` array is executed in one runner call.
* Errors such as `ExceedsInviteQuota`, `FarmIsDrained`, and `TrustRequired` are surfaced clearly to users.


# 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.

```typescript
  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>
    );
  };
```


# Using CrcV2\_TransferData for verifying annotated data

Annotating Circles transactions with arbitrary data and utilizing CrcV2\_TransferData rpc method to build Gnosis App compatible payment links and QR codes with annotation.

{% embed url="<https://drive.google.com/file/d/1WyOV-bWKTlXYHHWVBQLBLFVPrhXY51rF/view?usp=sharing>" %}

{% embed url="<https://github.com/aboutcircles/circles-gnosisApp-starter-kit>" %}

This starter kit showcases a simple method for Circles transaction based data transfer: execute a transfer via Gnosis, then verify on-chain intent by polling Circles events for `CrcV2_TransferData` and matching `recipient + data`.

{% hint style="success" %}
Using this any arbitrary data can now be annotated with Circles transactions.
{% endhint %}

The core idea is simple:

* Treat transfer `data` as an application-level idempotency key.
* Filter event ingestion to only `CrcV2_TransferData`.
* Compare values .

This guide explains the implementation in `src/lib/circles.ts` and `src/hooks/use-payment-watcher.ts`.

### Architecture

The app pipeline is:

1. Generate a deep link: `https://app.gnosis.io/transfer/<recipient>/crc?data=<payload>&amount=<amount>`
2. User executes transfer in Gnosis.
3. Poll Circles JSON-RPC (`circles_events`) for `CrcV2_TransferData`.
4. Match event by recipient and transfer data payload.
5. Transition watcher state: `idle -> waiting -> confirmed | error`.

### Runtime and Environment

Fork and clone the repository locally.

Install and run:

```bash
npm install
npm run dev
```

{% hint style="info" %}
Environment variables:

* `NEXT_PUBLIC_CIRCLES_RPC_URL`: browser-facing RPC endpoint.
* `NEXT_PUBLIC_DEFAULT_RECIPIENT_ADDRESS`: prefilled receiver.
* `NEXT_PUBLIC_GATEWAY_ADDRESS`: fallback receiver value.

\
If no env values are supplied, `src/lib/circles.ts` defaults to staging RPC and a hardcoded recipient.
{% endhint %}

### Event Ingestion: `circles_events` with `CrcV2_TransferData`

The ingestion query is intentionally narrow:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "circles_events",
  "params": [
    "<recipient-or-cursor-or-null>",//We can also add other params like fromBlock, toBlock, etc
    null,
    null,
    ["CrcV2_TransferData"]
  ]
}
```

Implementation details:

* Recipient-scoped query is preferred when recipient is known.
* Cursor pagination is used when querying globally.
* Response events are mapped into `CirclesTransferEvent` with stable string coercion to avoid runtime type drift from RPC payloads.

### Matching Semantics (Critical Path)

`checkPaymentReceived(dataValue, minAmountCRC, recipientAddress)` returns the first matching `CirclesTransferEvent` or `null`.

Matching gates:

1. `event.to` equals  recipient address.
2. `event.data` semantically equals requested `dataValue`.

#### Data normalization

It computes candidate representations for the target string and attempts reverse decode (`hex -> utf8`) on event payloads. This reduces false negatives when wallet/indexer pipelines emit different encodings for semantically identical content.

{% hint style="info" %}
Proper encoding and decoding standards will also be introduced soon for different data types.
{% endhint %}

### Polling Loop and State Machine

`usePaymentWatcher(...)` executes recursive polling with timeout scheduling:

* Starts only when `enabled && dataValue && minAmountCRC`.
* Sets state to `waiting` before each probe.
* Calls `checkPaymentReceived(...)`.
* On hit: stores event, sets `confirmed`, stops polling.
* On exception: stores message, sets `error`, keeps control explicit.
* On cleanup: cancels loop and clears timeout.

Default interval is `5000ms`; this is configurable via `intervalMs`.

### Deep Link Construction and Correlation Key

`generatePaymentLink(recipientAddress, amountCRC, data)` emits:

```
https://app.gnosis.io/transfer/<recipient>/crc?data=<encoded-data>&amount=<amount>
```

The important field is `data`. For production, use structured unique values (`order:<uuid>`, `invoice:<tenant>:<id>`) to avoid ambiguous confirmations.\
\
This way we can implement Gnosis App integrated payments utilizing Circles.\
\
Here is a simple fun game utilizing the above discussed features:

{% embed url="<https://github.com/aboutcircles/circles-gnosisApp-starter-kit/tree/king-of-hill-game>" %}


# 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:

```typescript
const data = sdk.data;
```

Otherwise you can also create an instance like this:

{% code overflow="wrap" %}

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

{% endcode %}

### 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).

```typescript
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");
}
```

### 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.

```typescript
const tokenInfo = await data.getTokenInfo("0x...");
if (tokenInfo) {
   console.log("Token is a Circles token");
} else {
   console.log("Token is not a Circles 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.

```typescript
const totalBalanceV1 = await data.getTotalBalance("0x...");
const totalBalanceV2 = await data.getTotalBalanceV2("0x...");
```

{% hint style="info" %}
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.
{% endhint %}

### 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`.

```typescript
const detailedCirclesBalancesV1 = await data.getTokenBalances("0x...");
const detailedCirclesBalancesV2 = await data.getTokenBalancesV2("0x...");
```

{% hint style="info" %}
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.
{% endhint %}

### 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
* `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

```typescript
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));
```

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.

```typescript
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));
```

{% hint style="info" %}
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.
{% endhint %}

### 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.

```typescript
const trustRelations = await data.getAggregatedTrustRelations("0x..");
trustRelations.forEach(row => console.log(row));
```

### 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.

```typescript
export interface GroupQueryParams {
  nameStartsWith?: string;
  symbolStartsWith?: string;
  groupAddressIn?: string[];
  sortBy?: 'age_asc' | 'age_desc' | 'name_asc' | 'name_desc' | 'symbol_asc' | 'symbol_desc';
}
```

Use the method as following:

```typescript
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));
```

{% hint style="info" %}
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.
{% endhint %}

### 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`.

```typescript
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));
```

{% hint style="info" %}
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.
{% endhint %}

### 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`.

```typescript
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));
```

### 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.

```typescript
const invitedBy = await data.getInvitedBy("0x...");
```


# 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.

{% tabs %}
{% tab title="Gnosis Chain" %}

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

{% endtab %}

{% tab title="Second Tab" %}

```typescript
const circlesRpc = new CirclesRpc("https://static.94.138.251.148.clients.your-server.de/rpc/");
const data = new CirclesData(circlesRpc);
```

{% endtab %}
{% endtabs %}

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

```typescript
const avatarEvents = await data.subscribeToEvents("0x...");
avatarEvents.subscribe(event => {
    console.log(event);
});
```

If you want to subscribe to all events, call it without parameter:

```typescript
const avatarEvents = await data.subscribeToEvents("0x...");
avatarEvents.subscribe(event => {
    console.log(event);
});
```

### Query past events

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

```typescript
const avatarEvents = await data.getEvents("0x..", 9000000, 10000000);
```

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

```typescript
const avatarEvents = await data.getEvents("0x..", 10000000);
```

### Event types

The above methods yield `CirclesEvent`s. 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](https://github.com/aboutcircles/circles-sdk/blob/34160858798e3ec197e405a06d6a199a0bf73412/packages/data/src/events/events.ts) for the event properties.

<pre class="language-typescript"><code class="lang-typescript">export type CirclesEvent =

// CrcV1 Events

<strong>| CrcV1_HubTransfer
</strong>| CrcV1_Signup
| CrcV1_OrganizationSignup
| CrcV1_Trust
| CrcV1_Transfer

<strong>// CrcV2 Events
</strong>
 | CrcV2_InviteHuman
<strong> | CrcV2_PersonalMint
</strong><strong> | CrcV2_RegisterGroup
</strong><strong> | CrcV2_RegisterHuman
</strong><strong> | CrcV2_RegisterOrganization
</strong> | CrcV2_Stopped
<strong> | CrcV2_Trust
</strong> | 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
<strong> | CrcV2_WithdrawInflationary
</strong>
</code></pre>


# 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).

{% hint style="info" %}
Check out the documentation of the [`circles_query`rpc method](https://github.com/aboutcircles/circles-nethermind-plugin/tree/dev?tab=readme-ov-file#circles-nethermind-plug-in) for a list of tables.
{% endhint %}

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

```typescript
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
};
```

{% hint style="warning" %}
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.`
{% endhint %}

### 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.

```typescript
interface MyGroupType extends EventRow {
  avatar: string;
  name: string;
  cidV0Digest?: string;
}
```

### 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.

```typescript
const circlesRpc = new CirclesRpc('https://rpc.aboutcircles.com/');
```

Then create a `CirclesQuery<MyGroupType>` instance.

```typescript
const query = new CirclesQuery<MyGroupType>(circlesRpc, queryDefinition);
```

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`.

```typescript
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));
}
```

### 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.

```typescript
const calculatedColumns = [{
  name: 'cidV0',
  generator: async (row: MyGroupType) => {
    if (!row.cidV0Digest) {
      return undefined;
    }

    const dataFromHexString = hexStringToUint8Array(row.cidV0Digest.substring(2));
    return uint8ArrayToCidV0(dataFromHexString);
  }
}];
```

The new column should be added to the custom type.

```typescript
interface MyGroupType extends EventRow {
  avatar: string;
  name: string;
  cidV0Digest?: string;
  cidV0?: string
}
```

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

```typescript
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));
}
```


# Query Circles profiles

## :bulb: 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)

```bash
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"
           }
         }'
```

### 2. Get a Profile by CID (GET request)

```bash
curl -X GET "https://rpc.aboutcircles.com/profiles/get?cid=Qm12345abcdef"
```

### 3. Get Multiple Profiles by CIDs (GET request)

{% code fullWidth="false" %}

```bash
curl -X GET "https://rpc.aboutcircles.com/profiles/getBatch?cids=Qm12345abcdef,Qm678bbdj
```

{% endcode %}

### 4. Search Profiles by Name (GET request)

```bash
curl -X GET "https://rpc.aboutcircles.com/profiles/search?name=John"
```

### 5. Search Profiles by Description (GET request)

```bash
curl -X GET "https://rpc.aboutcircles.com/profiles/search?description=Circles"
```

### 6. Search Profiles by Address (GET request)

```bash
curl -X GET "https://rpc.aboutcircles.com/profiles/search?address=0x1234567890abcdef"
```

### 7. Search Profiles by CID (GET request)

```bash
curl -X GET "https://rpc.aboutcircles.com/profiles/search?CID=Qm12345abcdef"
```

### 8. Search Profiles with Multiple Criteria (GET request)

```bash
curl -X GET "https://rpc.aboutcircles.com/profiles/search?name=John&description=blockchain&address=0x1234567890abcdef&CID=Qm12345abcdef"
```


# Circles SDK interface

### Sdk class

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

* `config` defaults to `circlesConfig[100]` (Gnosis)
* `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&#x20;
* `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.).


# SDK Methods

### 1) getAvatar

Gets an avatar instance by address.

```ts
sdk.getAvatar(avatarAddress: string): Promise<Avatar>;
```

* **Parameters:**
  * `avatarAddress` (required): Avatar wallet address.
* **Returns:** `HumanAvatar | OrganisationAvatar | BaseGroupAvatar`
* **Example:**

```ts
const avatar = await sdk.getAvatar('0x123...abc');
```

### 2) register.asHuman

Registers a human avatar; handles invitation redemption automatically.

```ts
sdk.register.asHuman(inviter: string, profile: Profile | string): Promise<HumanAvatar>;
```

* **Parameters:**
  * `inviter` (required): Address of inviting avatar.
  * `profile` (required): Profile object or CID string.
* **Returns:** `HumanAvatar`
* **Example:**

```ts
const human = await sdk.register.asHuman('0xInviter', { name: 'Alice', description: 'Dev' });
```

### 3) register.asOrganization

Registers an organization avatar with profile data.

```ts
sdk.register.asOrganization(profile: Profile | string): Promise<OrganisationAvatar>;
```

* **Parameters:**
  * `profile` (required): Profile object or CID string; must include `name`.
* **Returns:** `OrganisationAvatar`
* **Example:**

```ts
const org = await sdk.register.asOrganization({ name: 'Org', description: 'Example org' });
```

### 4) register.asGroup

Registers a Base Group with profile data.

```ts
sdk.register.asGroup(
  owner: string,
  service: string,
  feeCollection: string,
  initialConditions: string[],
  name: string,
  symbol: string,
  profile: Profile | string
): Promise<BaseGroupAvatar>;
```

* **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:**

```ts
const group = await sdk.register.asGroup(
  owner,
  service,
  feeCollector,
  [],
  'GroupName',
  'GRP',
  { name: 'GroupName', description: 'Example group' }
);
```

### 5) profiles.create (global)

Pins profile data and returns CID.

```ts
sdk.profiles.create(profile: Profile): Promise<string>;
```

* **Parameters:** `profile` (required): Profile object.
* **Returns:** CID string.
* **Example:**

```ts
const cid = await sdk.profiles.create({ name: 'Jane', description: 'Developer' });
```

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

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

```ts
avatar.profile.update(profile: Profile): Promise<string>;
```

* **Parameters:** `profile` (required): Profile object.
* **Returns:** CID string.
* **Example:**

```ts
const cid = await avatar.profile.update({ name: 'Jane', description: 'Updated bio' });
```

### 7) tokens.getInflationaryWrapper

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

```ts
sdk.tokens.getInflationaryWrapper(address: string): Promise<string>;
```

* **Parameters:** `address` (required): Avatar address.
* **Returns:** Wrapper address or zero.
* **Example:**

```ts
const inflWrapper = await sdk.tokens.getInflationaryWrapper('0xAvatar...');
```

### 8) tokens.getDemurragedWrapper

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

```ts
sdk.tokens.getDemurragedWrapper(address: string): Promise<string>;
```

* **Parameters:** `address` (required): Avatar address.
* **Returns:** Wrapper address or zero.
* **Example:**

```ts
const demWrapper = await sdk.tokens.getDemurragedWrapper('0xAvatar...');
```

### 9) Wrapping (avatar.wrap)

Wrap/unwrap ERC1155 CRC into ERC20 wrappers.

* Wrap demurraged:

```ts
avatar.wrap.asDemurraged(avatarAddress: string, amount: bigint): Promise<TransactionReceipt>;
```

* Wrap inflationary:

```ts
avatar.wrap.asInflationary(avatarAddress: string, amount: bigint): Promise<TransactionReceipt>;
```

* Unwrap demurraged:

```ts
avatar.wrap.unwrapDemurraged(wrapperAddress: string, amount: bigint): Promise<TransactionReceipt>;
```

* Unwrap inflationary:

```ts
avatar.wrap.unwrapInflationary(wrapperAddress: string, amount: bigint): Promise<TransactionReceipt>;
```

* **Parameters:**
  * `avatarAddress` / `wrapperAddress` (required)
  * `amount` (required, bigint, atto‑CRC)
* **Returns:** Transaction receipt.
* **Example:**

```ts
await avatar.wrap.asInflationary(avatar.address, BigInt(5e18));
```

### 10) Transfers (avatar.transfer)

* Advanced (pathfinding + unwrap/rewrap):

```ts
avatar.transfer.advanced(
  to: string,
  amount: bigint,
  options?: AdvancedTransferOptions
): Promise<TransactionReceipt>;
```

* Direct (no pathfinding):

```ts
avatar.transfer.direct(
  to: string,
  amount: bigint,
  tokenAddress?: string,
  txData?: Uint8Array
): Promise<TransactionReceipt>;
```

* Max flow helpers:

```ts
avatar.transfer.getMaxAmount(to: string): Promise<bigint>;
avatar.transfer.getMaxAmountAdvanced(to: string, options?: PathfindingOptions): Promise<bigint>;
```

* **Key optional `options`:** `useWrappedBalances`, `fromTokens`, `toTokens`, `excludeFromTokens`, `excludeToTokens`, `simulatedBalances`, `maxTransfers`, `txData`.
* **Example:**

```ts
const receipt = await avatar.transfer.advanced('0xRecipient', BigInt(10e18), { useWrappedBalances: true });
```

### 11) Trust graph (avatar.trust)

```ts
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[]>;
```

* **expiry** optional (defaults to max uint96).
* **Example:**

```ts
await avatar.trust.add('0xFriend'); // indefinite
```

### 12) Balances & history

```ts
avatar.balances.getTotal(): Promise<bigint>;
avatar.balances.getTokenBalances(): Promise<TokenBalanceRow[]>;
avatar.history.getTransactions(limit?: number, sortOrder?: 'ASC' | 'DESC'): PagedQuery<TransactionRow>;
```

* **Example:**

```ts
const tokens = await avatar.balances.getTokenBalances();
```

### 13) Events

```ts
await avatar.subscribeToEvents();
avatar.events.subscribe((event) => console.log(event.$event, event));
avatar.unsubscribeFromEvents();
```

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


# 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.


# 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.

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:&#x20;

* [ERC-1155 Multi Token Standard on Ethereum.org](https://ethereum.org/en/developers/docs/standards/tokens/erc-1155/)

### Externally-Owned Account <a href="#externally-owned-account" id="externally-owned-account"></a>

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](https://ethereum.org/en/developers/docs/accounts) on ethereum.org
* [Ethereum Whitepaper](https://ethereum.org/en/whitepaper/#ethereum-accounts) on ethereum.org

### Gasless Transaction <a href="#gasless-transaction" id="gasless-transaction"></a>

Gasless transactions (also known as meta-transactions) are Ethereum transactions that are executed by a third party called [relayer](https://docs.safe.global/glossary#relayer) on behalf of a [smart account](https://docs.safe.global/glossary#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](https://docs.safe.global/sdk/relay-kit) on docs.safe.global

### Network <a href="#network" id="network"></a>

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](https://ethereum.org/en/developers/docs/networks) on ethereum.org

### Owner <a href="#owner" id="owner"></a>

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](https://docs.safe.global/glossary#externally-owned-account) or [smart accounts](https://docs.safe.global/glossary#smart-account). The [threshold](https://docs.safe.global/glossary#threshold) of a Safe defines how many owners need to approve a Safe transaction to make it executable.

See also:

* [OwnerManager.sol](https://github.com/safe-global/safe-smart-account/blob/main/contracts/base/OwnerManager.sol) on github.com

### Relayer <a href="#relayer" id="relayer"></a>

A relayer is a third-party service acting as an intermediary between users' accounts and [blockchain networks](https://docs.safe.global/glossary#network). 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?](https://docs.gelato.network/developer-services/relay/what-is-relaying) on docs.gelato.network

### Safe Wallet <a href="#safe-apps" id="safe-apps"></a>

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?](https://help.safe.global/en/articles/40869-what-is-safe)

### Smart Account <a href="#smart-account" id="smart-account"></a>

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](https://docs.safe.global/glossary#externally-owned-account). 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](https://docs.safe.global/glossary#gasless-transaction)

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

### Transaction <a href="#transaction" id="transaction"></a>

A transaction is an action initiated by an [externally-owned account](https://docs.safe.global/glossary#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](https://github.com/safe-global/safe-smart-account/blob/main/contracts/Safe.sol#L104) method.

See also:

* [Transactions](https://ethereum.org/developers/docs/transactions) on ethereum.org

### Threshold <a href="#threshold" id="threshold"></a>

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](https://docs.safe.global/sdk/protocol-kit/reference#getthreshold) and [change the threshold](https://docs.safe.global/sdk/protocol-kit/reference#createchangethresholdtx) of a Safe with the Safe{Core} SDK on docs.safe.global

### Wallet <a href="#wallet" id="wallet"></a>

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](https://ethereum.org/wallets) on ethereum.org


# 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.

{% embed url="<https://aboutcircles.github.io/CirclesTools/>" %}

{% embed url="<https://pathfinder.app.aboutcircles.com/flow-visualization/>" %}

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.<br>
* **Group Creator**

  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.<br>
* **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.<br>
* **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.<br>
* **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.<br>
* **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.<br>
* **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.<br>
* **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. <br>
* **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.<br>
* **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.<br>
* **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.<br>
* **Onboarding Helper**

  This tool allows people to easily join a Circles group by scanner a QR code.<br>
* **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.<br>
* [**Pathfinder App**](https://pathfinder.app.aboutcircles.com/flow-visualization/)

  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.

> **Purpose limitation:** The Trust Score is provided only for Circles-related trust, anti-abuse, spam and Sybil-resistance use cases (for example, filtering obvious Sybil accounts, protecting community airdrops, or shaping access to Circles-native groups and offers in a proportional way). You must not use it to make or automate unrelated, high-impact decisions about individuals – for example, decisions about employment, housing, off-chain financial products or credit, insurance, access to public services, or other real-world eligibility and access decisions.


# 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!

{% embed url="<https://ethglobal.com/showcase/bracebuddy-ph10y>" %}
First Prize
{% endembed %}

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

{% embed url="<https://ethglobal.com/showcase/woleth-eth-3ukpz>" %}
Runner Up
{% endembed %}

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

{% embed url="<https://ethglobal.com/showcase/famjam-99inm>" %}
Runner Up
{% endembed %}

### 2. EthGlobal Singapore 2024

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

{% embed url="<https://ethglobal.com/showcase/voting-with-ubi-k56uu>" %}

### 3. EthGlobal Prague 2025

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

{% embed url="<https://ethglobal.com/showcase/circles-subscriptz-tgfhj>" %}
Winner of Blockscout Explorer Pool Prize
{% endembed %}


# Circles API

## Get all token balances with metadata

> Returns all token balances of an address with full metadata. Each row includes flags\
> (\`isWrapped\`, \`isGroup\`, \`isInflationary\`, \`isErc20\`, \`isErc1155\`, \`tokenType\`) for\
> client-side filtering, plus balances in several denominations\
> (\`attoCircles\`/\`circles\`, \`staticAttoCircles\`/\`staticCircles\`, \`attoCrc\`/\`crc\`).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Address to get balances for.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Balance & Tokens","description":"Total balances and per-token holdings & metadata."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTokenBalances":{"post":{"tags":["Balance & Tokens"],"operationId":"circles_getTokenBalances","summary":"Get all token balances with metadata","description":"Returns all token balances of an address with full metadata. Each row includes flags\n(`isWrapped`, `isGroup`, `isInflationary`, `isErc20`, `isErc1155`, `tokenType`) for\nclient-side filtering, plus balances in several denominations\n(`attoCircles`/`circles`, `staticAttoCircles`/`staticCircles`, `attoCrc`/`crc`).\n\n**Positional `params`:**\n1. `address` (string, **required**) — Address to get balances for.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of token balance rows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get metadata for a token

> Returns metadata for a single Circles token.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`tokenAddress\` (string, \*\*required\*\*) — Token contract address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Balance & Tokens","description":"Total balances and per-token holdings & metadata."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTokenInfo":{"post":{"tags":["Balance & Tokens"],"operationId":"circles_getTokenInfo","summary":"Get metadata for a token","description":"Returns metadata for a single Circles token.\n\n**Positional `params`:**\n1. `tokenAddress` (string, **required**) — Token contract address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Token metadata object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get metadata for multiple tokens

> Batch variant of \[\`circles\_getTokenInfo\`]\(#operation/circles\_getTokenInfo).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`tokenAddresses\` (string\[], \*\*required\*\*) — Array of token contract addresses.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Balance & Tokens","description":"Total balances and per-token holdings & metadata."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTokenInfoBatch":{"post":{"tags":["Balance & Tokens"],"operationId":"circles_getTokenInfoBatch","summary":"Get metadata for multiple tokens","description":"Batch variant of [`circles_getTokenInfo`](#operation/circles_getTokenInfo).\n\n**Positional `params`:**\n1. `tokenAddresses` (string[], **required**) — Array of token contract addresses.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of token metadata objects (one per input address).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get avatar info

> Returns avatar information for an address (V1 and V2 merged): version, type, token id,\
> whether it has a V1 token, and its profile CID (if any).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getAvatarInfo":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_getAvatarInfo","summary":"Get avatar info","description":"Returns avatar information for an address (V1 and V2 merged): version, type, token id,\nwhether it has a V1 token, and its profile CID (if any).\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Avatar info object (or `null` if not signed up).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get avatar info for multiple addresses

> Batch variant of \[\`circles\_getAvatarInfo\`]\(#operation/circles\_getAvatarInfo).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`addresses\` (string\[], \*\*required\*\*) — Array of avatar addresses.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getAvatarInfoBatch":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_getAvatarInfoBatch","summary":"Get avatar info for multiple addresses","description":"Batch variant of [`circles_getAvatarInfo`](#operation/circles_getAvatarInfo).\n\n**Positional `params`:**\n1. `addresses` (string[], **required**) — Array of avatar addresses.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of avatar info objects.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get profile by address

> Returns the profile for an avatar address. May also include extended group-profile\
> fields when set: \`externalLinks.website\`,\
> \`membershipCriteria.{minRepScore, membershipFee, additionalCriteria}\`, \`groupType\`,\
> \`contactInfo.{email, website}\`.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getProfileByAddress":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_getProfileByAddress","summary":"Get profile by address","description":"Returns the profile for an avatar address. May also include extended group-profile\nfields when set: `externalLinks.website`,\n`membershipCriteria.{minRepScore, membershipFee, additionalCriteria}`, `groupType`,\n`contactInfo.{email, website}`.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Profile object (or `null` if none).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get profiles for multiple addresses

> Batch variant of \[\`circles\_getProfileByAddress\`]\(#operation/circles\_getProfileByAddress).\
> Extended group-profile fields may be present per item.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`addresses\` (string\[], \*\*required\*\*) — Array of avatar addresses.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getProfileByAddressBatch":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_getProfileByAddressBatch","summary":"Get profiles for multiple addresses","description":"Batch variant of [`circles_getProfileByAddress`](#operation/circles_getProfileByAddress).\nExtended group-profile fields may be present per item.\n\n**Positional `params`:**\n1. `addresses` (string[], **required**) — Array of avatar addresses.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of profile objects (one per input address; `null` when none).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get profile IPFS CID

> Returns the IPFS CIDv0 of an avatar's profile.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getProfileCid":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_getProfileCid","summary":"Get profile IPFS CID","description":"Returns the IPFS CIDv0 of an avatar's profile.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"CIDv0 string (or `null`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get profile IPFS CIDs (batch)

> Batch variant of \[\`circles\_getProfileCid\`]\(#operation/circles\_getProfileCid).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`addresses\` (string\[], \*\*required\*\*) — Array of avatar addresses.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getProfileCidBatch":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_getProfileCidBatch","summary":"Get profile IPFS CIDs (batch)","description":"Batch variant of [`circles_getProfileCid`](#operation/circles_getProfileCid).\n\n**Positional `params`:**\n1. `addresses` (string[], **required**) — Array of avatar addresses.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of CIDv0 strings (one per input address; `null` when none).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get profile by IPFS CID

> Retrieves a profile from IPFS by its CID.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`cid\` (string, \*\*required\*\*) — CIDv0 string.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getProfileByCid":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_getProfileByCid","summary":"Get profile by IPFS CID","description":"Retrieves a profile from IPFS by its CID.\n\n**Positional `params`:**\n1. `cid` (string, **required**) — CIDv0 string.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Profile object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get profiles by IPFS CIDs (batch)

> Batch variant of \[\`circles\_getProfileByCid\`]\(#operation/circles\_getProfileByCid).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`cids\` (string\[], \*\*required\*\*) — Array of IPFS CIDs.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getProfileByCidBatch":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_getProfileByCidBatch","summary":"Get profiles by IPFS CIDs (batch)","description":"Batch variant of [`circles_getProfileByCid`](#operation/circles_getProfileByCid).\n\n**Positional `params`:**\n1. `cids` (string[], **required**) — Array of IPFS CIDs.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of profile objects.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Full-text profile search

> Full-text search for profiles. Each result's \`Profile\` sub-object may include extended\
> group fields (flat shape): \`externalWebsite\`, \`minRepScore\`, \`membershipFee\`,\
> \`additionalCriteria\`, \`groupType\`, \`contactEmail\`, \`contactWebsite\`.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`text\` (string, \*\*required\*\*) — Search query (max 3 tokens).\
> 2\. \`limit\` (integer, optional, default \`20\`) — Max results (max 100).\
> 3\. \`offset\` (integer, optional, default \`0\`) — Pagination offset.\
> 4\. \`types\` (string\[], optional) — Filter by avatar types.\
> 5\. \`groupType\` (string, optional) — Filter by group type: \`open\` or \`closed\`.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_searchProfiles":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_searchProfiles","summary":"Full-text profile search","description":"Full-text search for profiles. Each result's `Profile` sub-object may include extended\ngroup fields (flat shape): `externalWebsite`, `minRepScore`, `membershipFee`,\n`additionalCriteria`, `groupType`, `contactEmail`, `contactWebsite`.\n\n**Positional `params`:**\n1. `text` (string, **required**) — Search query (max 3 tokens).\n2. `limit` (integer, optional, default `20`) — Max results (max 100).\n3. `offset` (integer, optional, default `0`) — Pagination offset.\n4. `types` (string[], optional) — Filter by avatar types.\n5. `groupType` (string, optional) — Filter by group type: `open` or `closed`.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of matching profiles.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Unified search by address or name

> Unified search by address prefix \*\*or\*\* name/text. A \`0x\` prefix triggers address search.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`query\` (string, \*\*required\*\*) — Search query (\`0x…\` ⇒ address search).\
> 2\. \`limit\` (integer, optional, default \`20\`) — Max results.\
> 3\. \`cursor\` (string, optional) — Pagination cursor.\
> 4\. \`types\` (string\[], optional) — Filter by avatar types.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Avatars & Profiles","description":"Avatar registration info and IPFS-backed profiles."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_searchProfileByAddressOrName":{"post":{"tags":["Avatars & Profiles"],"operationId":"circles_searchProfileByAddressOrName","summary":"Unified search by address or name","description":"Unified search by address prefix **or** name/text. A `0x` prefix triggers address search.\n\n**Positional `params`:**\n1. `query` (string, **required**) — Search query (`0x…` ⇒ address search).\n2. `limit` (integer, optional, default `20`) — Max results.\n3. `cursor` (string, optional) — Pagination cursor.\n4. `types` (string[], optional) — Filter by avatar types.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Search result with detected `searchType` and matched profiles.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get trust relations

> Returns the trust relationships for an address as a list of outgoing trusts with limits.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Trust & Groups","description":"Trust relationships, groups and group membership."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTrustRelations":{"post":{"tags":["Trust & Groups"],"operationId":"circles_getTrustRelations","summary":"Get trust relations","description":"Returns the trust relationships for an address as a list of outgoing trusts with limits.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Trust relations for the user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get aggregated trust relations

> Returns aggregated trust relations in SDK-compatible format. \`relation\` is one of\
> \`trusts\`, \`trustedBy\`, \`mutuallyTrusts\`, \`selfTrusts\`.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`avatar\` (string, \*\*required\*\*) — Avatar address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Trust & Groups","description":"Trust relationships, groups and group membership."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getAggregatedTrustRelations":{"post":{"tags":["Trust & Groups"],"operationId":"circles_getAggregatedTrustRelations","summary":"Get aggregated trust relations","description":"Returns aggregated trust relations in SDK-compatible format. `relation` is one of\n`trusts`, `trustedBy`, `mutuallyTrusts`, `selfTrusts`.\n\n**Positional `params`:**\n1. `avatar` (string, **required**) — Avatar address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of aggregated trust relation rows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get common trust between two addresses

> Finds addresses that two users both trust.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address1\` (string, \*\*required\*\*) — First address.\
> 2\. \`address2\` (string, \*\*required\*\*) — Second address.\
> 3\. \`version\` (integer, optional) — Filter by version (\`1\`, \`2\`, or \`null\` for both).<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Trust & Groups","description":"Trust relationships, groups and group membership."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getCommonTrust":{"post":{"tags":["Trust & Groups"],"operationId":"circles_getCommonTrust","summary":"Get common trust between two addresses","description":"Finds addresses that two users both trust.\n\n**Positional `params`:**\n1. `address1` (string, **required**) — First address.\n2. `address2` (string, **required**) — Second address.\n3. `version` (integer, optional) — Filter by version (`1`, `2`, or `null` for both).\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of commonly-trusted addresses (empty if none).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get group members

> Returns members of a specific group, paginated.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`groupAddress\` (string, \*\*required\*\*) — Group address.\
> 2\. \`limit\` (integer, optional, default \`100\`) — Max results.\
> 3\. \`cursor\` (string, optional) — Pagination cursor.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Trust & Groups","description":"Trust relationships, groups and group membership."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getGroupMembers":{"post":{"tags":["Trust & Groups"],"operationId":"circles_getGroupMembers","summary":"Get group members","description":"Returns members of a specific group, paginated.\n\n**Positional `params`:**\n1. `groupAddress` (string, **required**) — Group address.\n2. `limit` (integer, optional, default `100`) — Max results.\n3. `cursor` (string, optional) — Pagination cursor.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Paginated list of group memberships.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get group memberships of an avatar

> Returns the groups that an avatar is a member of.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`memberAddress\` (string, \*\*required\*\*) — Member address.\
> 2\. \`limit\` (integer, optional, default \`50\`) — Max results.\
> 3\. \`cursor\` (string, optional) — Pagination cursor.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Trust & Groups","description":"Trust relationships, groups and group membership."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getGroupMemberships":{"post":{"tags":["Trust & Groups"],"operationId":"circles_getGroupMemberships","summary":"Get group memberships of an avatar","description":"Returns the groups that an avatar is a member of.\n\n**Positional `params`:**\n1. `memberAddress` (string, **required**) — Member address.\n2. `limit` (integer, optional, default `50`) — Max results.\n3. `cursor` (string, optional) — Pagination cursor.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Paginated list of group memberships.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get score-group mint limits

> Returns per-(group, collateral) headroom for score-group mints. Passing a\
> \`collateralToken\` returns a single row.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`group\` (string, \*\*required\*\*) — Score-group address.\
> 2\. \`collateralToken\` (string, optional) — Collateral token filter.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Trust & Groups","description":"Trust relationships, groups and group membership."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getScoreGroupMintLimits":{"post":{"tags":["Trust & Groups"],"operationId":"circles_getScoreGroupMintLimits","summary":"Get score-group mint limits","description":"Returns per-(group, collateral) headroom for score-group mints. Passing a\n`collateralToken` returns a single row.\n\n**Positional `params`:**\n1. `group` (string, **required**) — Score-group address.\n2. `collateralToken` (string, optional) — Collateral token filter.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Mint-limit rows for the group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get a full trust-network snapshot

> Returns a complete snapshot of the Circles trust network. \*\*This response is large.\*\*\
> \
> \*\*Positional \`params\`:\*\* \_none.\_<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Trust & Groups","description":"Trust relationships, groups and group membership."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getNetworkSnapshot":{"post":{"tags":["Trust & Groups"],"operationId":"circles_getNetworkSnapshot","summary":"Get a full trust-network snapshot","description":"Returns a complete snapshot of the Circles trust network. **This response is large.**\n\n**Positional `params`:** _none._\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Full network snapshot (shape illustrative — fields may vary).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get transaction history

> Returns incoming and outgoing Circles transfers for an avatar (mints and transfers,\
> V1 and V2). Results are ordered descending.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`avatarAddress\` (string, \*\*required\*\*) — Avatar address.\
> 2\. \`limit\` (integer, optional, default \`50\`) — Max transactions.\
> 3\. \`cursor\` (string, optional) — Pagination cursor.\
> 4\. \`version\` (integer, optional) — Filter by version (\`null\` = both).\
> 5\. \`excludeIntermediary\` (boolean, optional, default \`true\`) — Exclude intermediary hops.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Transactions","description":"Transfer history, token holders and ERC-1155 transfer data."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTransactionHistory":{"post":{"tags":["Transactions"],"operationId":"circles_getTransactionHistory","summary":"Get transaction history","description":"Returns incoming and outgoing Circles transfers for an avatar (mints and transfers,\nV1 and V2). Results are ordered descending.\n\n**Positional `params`:**\n1. `avatarAddress` (string, **required**) — Avatar address.\n2. `limit` (integer, optional, default `50`) — Max transactions.\n3. `cursor` (string, optional) — Pagination cursor.\n4. `version` (integer, optional) — Filter by version (`null` = both).\n5. `excludeIntermediary` (boolean, optional, default `true`) — Exclude intermediary hops.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Transaction rows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get enriched transaction history

> Transaction history enriched with participant profiles, over a block range.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.\
> 2\. \`fromBlock\` (integer, \*\*required\*\*) — Starting block number.\
> 3\. \`toBlock\` (integer, optional) — Ending block number.\
> 4\. \`limit\` (integer, optional, default \`20\`) — Max transactions.\
> 5\. \`cursor\` (string, optional) — Pagination cursor.\
> 6\. \`version\` (integer, optional) — Filter by version.\
> 7\. \`excludeIntermediary\` (boolean, optional, default \`true\`) — Exclude intermediary hops.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Transactions","description":"Transfer history, token holders and ERC-1155 transfer data."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTransactionHistoryEnriched":{"post":{"tags":["Transactions"],"operationId":"circles_getTransactionHistoryEnriched","summary":"Get enriched transaction history","description":"Transaction history enriched with participant profiles, over a block range.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n2. `fromBlock` (integer, **required**) — Starting block number.\n3. `toBlock` (integer, optional) — Ending block number.\n4. `limit` (integer, optional, default `20`) — Max transactions.\n5. `cursor` (string, optional) — Pagination cursor.\n6. `version` (integer, optional) — Filter by version.\n7. `excludeIntermediary` (boolean, optional, default `true`) — Exclude intermediary hops.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Enriched transaction rows (shape illustrative; like `circles_getTransactionHistory` rows plus `fromProfile`/`toProfile`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get token holders

> Returns all holders of a specific token, paginated.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`tokenAddress\` (string, \*\*required\*\*) — Token address.\
> 2\. \`limit\` (integer, optional, default \`100\`) — Max holders.\
> 3\. \`cursor\` (string, optional) — Pagination cursor.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Transactions","description":"Transfer history, token holders and ERC-1155 transfer data."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTokenHolders":{"post":{"tags":["Transactions"],"operationId":"circles_getTokenHolders","summary":"Get token holders","description":"Returns all holders of a specific token, paginated.\n\n**Positional `params`:**\n1. `tokenAddress` (string, **required**) — Token address.\n2. `limit` (integer, optional, default `100`) — Max holders.\n3. `cursor` (string, optional) — Pagination cursor.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Paginated list of token holders.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get ERC-1155 transfer data bytes

> Returns the \`data\` bytes parameter from ERC-1155 transfer calls. See the\
> \`CrcV2\_TransferData\` tutorial for decoding annotated payloads.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Primary address to filter.\
> 2\. \`direction\` (string, optional) — \`"sent"\`, \`"received"\`, or \`null\` (both).\
> 3\. \`counterparty\` (string, optional) — Counterparty address filter.\
> 4\. \`fromBlock\` (integer, optional) — Start block (inclusive).\
> 5\. \`toBlock\` (integer, optional) — End block (inclusive).\
> 6\. \`limit\` (integer, optional, default \`50\`) — Max results (max 1000).\
> 7\. \`cursor\` (string, optional) — Pagination cursor.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Transactions","description":"Transfer history, token holders and ERC-1155 transfer data."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTransferData":{"post":{"tags":["Transactions"],"operationId":"circles_getTransferData","summary":"Get ERC-1155 transfer data bytes","description":"Returns the `data` bytes parameter from ERC-1155 transfer calls. See the\n`CrcV2_TransferData` tutorial for decoding annotated payloads.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Primary address to filter.\n2. `direction` (string, optional) — `\"sent\"`, `\"received\"`, or `null` (both).\n3. `counterparty` (string, optional) — Counterparty address filter.\n4. `fromBlock` (integer, optional) — Start block (inclusive).\n5. `toBlock` (integer, optional) — End block (inclusive).\n6. `limit` (integer, optional, default `50`) — Max results (max 1000).\n7. `cursor` (string, optional) — Pagination cursor.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Transfers carrying a non-empty `data` field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Query indexed blockchain events

> Queries indexed blockchain events with advanced filtering.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, optional) — Filter by address.\
> 2\. \`fromBlock\` (integer, optional) — Starting block (inclusive).\
> 3\. \`toBlock\` (integer, optional) — Ending block (inclusive).\
> 4\. \`eventTypes\` (string\[], optional) — Filter by event types (e.g. \`CrcV2\_TransferSingle\`).\
> 5\. \`filterPredicates\` (object\[], optional) — Advanced filter predicates.\
> 6\. \`sortAscending\` (boolean, optional, default \`false\`) — Sort order.\
> 7\. \`limit\` (integer, optional, default \`100\`) — Max events (max 1000).\
> 8\. \`cursor\` (string, optional) — Pagination cursor.\
> \
> Event types include the \`CrcV1\_\*\`, \`CrcV2\_\*\` and \`CrcV2\_ScoreGroup\_\*\` families — see\
> \`circles\_tables\` for the full catalog.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Events & Query","description":"Indexed blockchain events and generic table queries."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_events":{"post":{"tags":["Events & Query"],"operationId":"circles_events","summary":"Query indexed blockchain events","description":"Queries indexed blockchain events with advanced filtering.\n\n**Positional `params`:**\n1. `address` (string, optional) — Filter by address.\n2. `fromBlock` (integer, optional) — Starting block (inclusive).\n3. `toBlock` (integer, optional) — Ending block (inclusive).\n4. `eventTypes` (string[], optional) — Filter by event types (e.g. `CrcV2_TransferSingle`).\n5. `filterPredicates` (object[], optional) — Advanced filter predicates.\n6. `sortAscending` (boolean, optional, default `false`) — Sort order.\n7. `limit` (integer, optional, default `100`) — Max events (max 1000).\n8. `cursor` (string, optional) — Pagination cursor.\n\nEvent types include the `CrcV1_*`, `CrcV2_*` and `CrcV2_ScoreGroup_*` families — see\n`circles_tables` for the full catalog.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of events with `event` name and `values` payload (hex-encoded numerics).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Generic table query

> Runs a generic database query against an indexed namespace/table. Use \`circles\_tables\`\
> to discover available namespaces, tables and columns.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`query\` (object, \*\*required\*\*) — A \`SelectDto\`:\
> &#x20;  \- \`Namespace\` (string) — e.g. \`V\_Crc\`, \`CrcV2\`, \`CrcV2\_ScoreGroup\`.\
> &#x20;  \- \`Table\` (string) — e.g. \`Avatars\`.\
> &#x20;  \- \`Columns\` (string\[], optional) — Columns to return (empty = all).\
> &#x20;  \- \`Filter\` (object\[], optional) — \`FilterPredicate\` objects\
> &#x20;    (\`{ type, column, filterType, value }\`; \`filterType\` ∈ \`Equals\`, \`NotEquals\`,\
> &#x20;    \`GreaterThan\`, \`LessThan\`, \`Like\`, \`In\`, \`IsNull\`, …).\
> &#x20;  \- \`Order\` (object\[], optional) — \`\[{ Column, SortOrder }]\`, \`SortOrder\` ∈ \`ASC\`/\`DESC\`.\
> &#x20;  \- \`Limit\` (integer, optional), \`Distinct\` (boolean, optional).\
> 2\. \`cursor\` (string, optional) — Pagination cursor.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Events & Query","description":"Indexed blockchain events and generic table queries."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_query":{"post":{"tags":["Events & Query"],"operationId":"circles_query","summary":"Generic table query","description":"Runs a generic database query against an indexed namespace/table. Use `circles_tables`\nto discover available namespaces, tables and columns.\n\n**Positional `params`:**\n1. `query` (object, **required**) — A `SelectDto`:\n   - `Namespace` (string) — e.g. `V_Crc`, `CrcV2`, `CrcV2_ScoreGroup`.\n   - `Table` (string) — e.g. `Avatars`.\n   - `Columns` (string[], optional) — Columns to return (empty = all).\n   - `Filter` (object[], optional) — `FilterPredicate` objects\n     (`{ type, column, filterType, value }`; `filterType` ∈ `Equals`, `NotEquals`,\n     `GreaterThan`, `LessThan`, `Like`, `In`, `IsNull`, …).\n   - `Order` (object[], optional) — `[{ Column, SortOrder }]`, `SortOrder` ∈ `ASC`/`DESC`.\n   - `Limit` (integer, optional), `Distinct` (boolean, optional).\n2. `cursor` (string, optional) — Pagination cursor.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Result with `columns` and `rows` (rows are positional arrays aligned to `columns`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Paginated table query

> Like \[\`circles\_query\`]\(#operation/circles\_query) but returns a\
> \`{ columns, rows, hasMore, nextCursor }\` wrapper for pagination.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`query\` (object, \*\*required\*\*) — \`SelectDto\` (see \`circles\_query\`).\
> 2\. \`cursor\` (string, optional) — Cursor from a previous response.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Events & Query","description":"Indexed blockchain events and generic table queries."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_paginated_query":{"post":{"tags":["Events & Query"],"operationId":"circles_paginated_query","summary":"Paginated table query","description":"Like [`circles_query`](#operation/circles_query) but returns a\n`{ columns, rows, hasMore, nextCursor }` wrapper for pagination.\n\n**Positional `params`:**\n1. `query` (object, **required**) — `SelectDto` (see `circles_query`).\n2. `cursor` (string, optional) — Cursor from a previous response.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Paginated query result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## List available tables and schemas

> Returns the catalog of available namespaces, tables, their event topics and column schemas.\
> \
> \*\*Positional \`params\`:\*\* \_none.\_<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Events & Query","description":"Indexed blockchain events and generic table queries."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_tables":{"post":{"tags":["Events & Query"],"operationId":"circles_tables","summary":"List available tables and schemas","description":"Returns the catalog of available namespaces, tables, their event topics and column schemas.\n\n**Positional `params`:** _none._\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of namespaces with their tables and column definitions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get complete profile view

> Returns a complete profile view combining avatar info, profile, trust stats and balances.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getProfileView":{"post":{"tags":["SDK Methods"],"operationId":"circles_getProfileView","summary":"Get complete profile view","description":"Returns a complete profile view combining avatar info, profile, trust stats and balances.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Combined profile view.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get trust network summary

> Returns aggregated trust-network statistics for an address.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.\
> 2\. \`maxDepth\` (integer, optional) — Max depth for network traversal.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTrustNetworkSummary":{"post":{"tags":["SDK Methods"],"operationId":"circles_getTrustNetworkSummary","summary":"Get trust network summary","description":"Returns aggregated trust-network statistics for an address.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n2. `maxDepth` (integer, optional) — Max depth for network traversal.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Trust network summary statistics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get enriched aggregated trust relations

> Trust relations categorized by type with enriched avatar info, paginated.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.\
> 2\. \`limit\` (integer, optional, default \`50\`) — Max results per page.\
> 3\. \`cursor\` (string, optional) — Pagination cursor.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getAggregatedTrustRelationsEnriched":{"post":{"tags":["SDK Methods"],"operationId":"circles_getAggregatedTrustRelationsEnriched","summary":"Get enriched aggregated trust relations","description":"Trust relations categorized by type with enriched avatar info, paginated.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n2. `limit` (integer, optional, default `50`) — Max results per page.\n3. `cursor` (string, optional) — Pagination cursor.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Enriched trust relations grouped under `results`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get valid inviters

> Returns addresses that trust the given address \*\*and\*\* have sufficient balance to invite.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.\
> 2\. \`minimumBalance\` (string, optional) — Minimum balance required (in CRC).\
> 3\. \`limit\` (integer, optional, default \`50\`) — Max results.\
> 4\. \`cursor\` (string, optional) — Pagination cursor.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getValidInviters":{"post":{"tags":["SDK Methods"],"operationId":"circles_getValidInviters","summary":"Get valid inviters","description":"Returns addresses that trust the given address **and** have sufficient balance to invite.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n2. `minimumBalance` (string, optional) — Minimum balance required (in CRC).\n3. `limit` (integer, optional, default `50`) — Max results.\n4. `cursor` (string, optional) — Pagination cursor.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Valid inviters with balances and enriched avatar info.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get invitation origin

> Returns how a user joined Circles (the origin of their invitation).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getInvitationOrigin":{"post":{"tags":["SDK Methods"],"operationId":"circles_getInvitationOrigin","summary":"Get invitation origin","description":"Returns how a user joined Circles (the origin of their invitation).\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Invitation origin record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get all invitations for an address

> Returns all available invitations for an address from all sources (trust, escrow,\
> at-scale).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.\
> 2\. \`minimumBalance\` (string, optional) — Minimum balance required (in CRC).<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getAllInvitations":{"post":{"tags":["SDK Methods"],"operationId":"circles_getAllInvitations","summary":"Get all invitations for an address","description":"Returns all available invitations for an address from all sources (trust, escrow,\nat-scale).\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n2. `minimumBalance` (string, optional) — Minimum balance required (in CRC).\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Invitations grouped by source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get trust-based invitations

> Returns trust-based invitations (addresses that trust the target with sufficient balance).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.\
> 2\. \`minimumBalance\` (string, optional) — Minimum CRC balance required (in wei).<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getTrustInvitations":{"post":{"tags":["SDK Methods"],"operationId":"circles_getTrustInvitations","summary":"Get trust-based invitations","description":"Returns trust-based invitations (addresses that trust the target with sufficient balance).\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n2. `minimumBalance` (string, optional) — Minimum CRC balance required (in wei).\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of trust-based inviters (shape illustrative).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get escrow-based invitations

> Returns escrow-based invitations (CRC escrowed for the target address).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getEscrowInvitations":{"post":{"tags":["SDK Methods"],"operationId":"circles_getEscrowInvitations","summary":"Get escrow-based invitations","description":"Returns escrow-based invitations (CRC escrowed for the target address).\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of escrow invitations (shape illustrative).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get at-scale invitations

> Returns at-scale invitations (pre-created unclaimed accounts) for an address.\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Avatar address.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getAtScaleInvitations":{"post":{"tags":["SDK Methods"],"operationId":"circles_getAtScaleInvitations","summary":"Get at-scale invitations","description":"Returns at-scale invitations (pre-created unclaimed accounts) for an address.\n\n**Positional `params`:**\n1. `address` (string, **required**) — Avatar address.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Array of at-scale invitations (shape illustrative).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Get accounts invited by an avatar

> Returns accounts invited by a specific avatar (accepted or pending).\
> \
> \*\*Positional \`params\`:\*\*\
> 1\. \`address\` (string, \*\*required\*\*) — Inviter avatar address.\
> 2\. \`accepted\` (boolean, optional, default \`false\`) — \`true\` = registered accounts,\
> &#x20;  \`false\` = pending invitations.<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"SDK Methods","description":"Higher-level aggregated views used by the Circles SDK."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circles_getInvitationsFrom":{"post":{"tags":["SDK Methods"],"operationId":"circles_getInvitationsFrom","summary":"Get accounts invited by an avatar","description":"Returns accounts invited by a specific avatar (accepted or pending).\n\n**Positional `params`:**\n1. `address` (string, **required**) — Inviter avatar address.\n2. `accepted` (boolean, optional, default `false`) — `true` = registered accounts,\n   `false` = pending invitations.\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Invited accounts with status and enriched avatar info.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```

## Find a transitive payment path

> Calculates a transitive payment path through the trust network. Unlike most Circles\
> methods, this takes a single \*\*object\*\* as the only \`params\` element.\
> \
> \*\*\`params\[0]\` object fields:\*\*\
> \- \`source\` (string, \*\*required\*\*) — Source address.\
> \- \`sink\` (string, \*\*required\*\*) — Destination address.\
> \- \`targetFlow\` (string, \*\*required\*\*) — Target amount as a \`uint256\` string (in wei).\
> &#x20; Use the max \`uint256\`\
> &#x20; (\`115792089237316195423570985008687907853269984665640564039457584007913129639935\`)\
> &#x20; for a max-flow query.\
> \- \`withWrap\` (boolean, optional) — Enable ERC20 wrapping.\
> \- \`fromTokens\` / \`toTokens\` (string\[], optional) — Whitelist source/destination tokens.\
> \- \`excludedFromTokens\` / \`excludedToTokens\` (string\[], optional) — Blacklist tokens.\
> \- \`simulatedBalances\` (object\[], optional) — Override balances for testing.\
> \- \`simulatedTrusts\` (object\[], optional) — Override trusts for testing.\
> \- \`simulatedConsentedAvatars\` (string\[], optional) — Pre-consented avatars.\
> \- \`maxTransfers\` (integer, optional) — Max transfer hops.\
> \- \`quantizedMode\` (boolean, optional) — 96 CRC quantization for invitations.\
> \
> \> Routed to the Pathfinder, which shares the RPC root URL (\`/\`).<br>

```json
{"openapi":"3.0.3","info":{"title":"Circles RPC API","version":"1.0.0"},"tags":[{"name":"Pathfinder","description":"Transitive payment path finding through the trust graph."}],"servers":[{"url":"https://rpc.aboutcircles.com","description":"Production"}],"paths":{"/#circlesV2_findPath":{"post":{"tags":["Pathfinder"],"operationId":"circlesV2_findPath","summary":"Find a transitive payment path","description":"Calculates a transitive payment path through the trust network. Unlike most Circles\nmethods, this takes a single **object** as the only `params` element.\n\n**`params[0]` object fields:**\n- `source` (string, **required**) — Source address.\n- `sink` (string, **required**) — Destination address.\n- `targetFlow` (string, **required**) — Target amount as a `uint256` string (in wei).\n  Use the max `uint256`\n  (`115792089237316195423570985008687907853269984665640564039457584007913129639935`)\n  for a max-flow query.\n- `withWrap` (boolean, optional) — Enable ERC20 wrapping.\n- `fromTokens` / `toTokens` (string[], optional) — Whitelist source/destination tokens.\n- `excludedFromTokens` / `excludedToTokens` (string[], optional) — Blacklist tokens.\n- `simulatedBalances` (object[], optional) — Override balances for testing.\n- `simulatedTrusts` (object[], optional) — Override trusts for testing.\n- `simulatedConsentedAvatars` (string[], optional) — Pre-consented avatars.\n- `maxTransfers` (integer, optional) — Max transfer hops.\n- `quantizedMode` (boolean, optional) — 96 CRC quantization for invitations.\n\n> Routed to the Pathfinder, which shares the RPC root URL (`/`).\n","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/JsonRpcRequestBase"}]}}},"required":true},"responses":{"200":{"description":"Computed flow with the ordered list of transfers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"JsonRpcRequestBase":{"type":"object","required":["jsonrpc","method","params"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"],"default":"2.0","description":"JSON-RPC protocol version. Always `\"2.0\"`."},"id":{"oneOf":[{"type":"integer"},{"type":"string"}],"default":1,"description":"Request identifier echoed back in the response."},"method":{"type":"string","description":"The RPC method name (also selects routing on the single root endpoint)."},"params":{"type":"array","description":"Positional parameters for the method (see each operation for the order and types). Pathfinder and query methods take a single object as the first element.","items":{}}},"description":"JSON-RPC 2.0 request envelope. Sent via `POST` to the server root (`/`)."},"JsonRpcResponse":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"result":{"description":"Method-specific result. Present on success; see the example for each operation. (Type varies: string, object, array, boolean, …)"},"error":{"$ref":"#/components/schemas/JsonRpcError"}},"description":"JSON-RPC 2.0 response envelope. On success `result` is present; on failure `error` is present instead (still returned with HTTP `200`)."},"JsonRpcError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Optional additional error detail."}},"description":"JSON-RPC 2.0 error object."}}}}
```


# User Guides

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td></td><td><h4><i class="fa-people-group">:people-group:</i></h4></td><td><strong>Circles Groups</strong></td><td>Create and manage Circles Groups for your community</td><td></td><td></td><td><a href="/pages/cfvpxXFzNBW1GPl4SUyS">/pages/cfvpxXFzNBW1GPl4SUyS</a></td></tr><tr><td></td><td><h4><i class="fa-rotate">:rotate:</i></h4></td><td><strong>v1 Account Migration</strong></td><td>Migrate your v1 Circles account to v2</td><td></td><td></td><td><a href="/pages/KW0KXONH1MqItA74ETui">/pages/KW0KXONH1MqItA74ETui</a></td></tr><tr><td><h4><i class="fa-right-left">:right-left:</i></h4></td><td><strong>Circles Transfer and Rule of Trust</strong></td><td>Understand Circles balances, send limits, and how the Pathfinder finds a route</td><td></td><td></td><td></td><td><a href="/pages/ycGlqtGzpwQpiZydjwDN">/pages/ycGlqtGzpwQpiZydjwDN</a></td></tr><tr><td><h4><i class="fa-fire">:fire:</i></h4></td><td><strong>Daily Burn (Demurrage)</strong></td><td>Understand what daily burn means and how demurrage plays a role in Circles</td><td></td><td></td><td></td><td><a href="/pages/iCy2LmucdRJ3B8cvSOVq">/pages/iCy2LmucdRJ3B8cvSOVq</a></td></tr><tr><td><h4><i class="fa-handshake">:handshake:</i></h4></td><td>Trust Score</td><td>Understand what it means, how it’s calculated, and how to use it safely</td><td></td><td></td><td></td><td><a href="/pages/l18jM5dxoLPdI2HVxHKh">/pages/l18jM5dxoLPdI2HVxHKh</a></td></tr></tbody></table>


# Invitation Link Manager Mini App Guide

The [**Invitation Manager**](https://circles.gnosis.io/miniapps/invitations-manager) is a miniapp that lets you create shareable Circles invitation links, single-use codes that anyone can redeem to sign up with an invited Circles account, optionally also adding them to a particular group. You can group those codes into **Magic Links** (one shareable URL backed by many codes), pause and resume a campaign at any time, attach UTM tags, and manage the leftover codes in your personal pool.

This guide walks through every screen from the perspective of someone who just opened the app for the first time.

***

### Before you start

You will need:

* **A Gnosis App account**
* **Quota on the InvitationFarm contract** (If you are a community/group manager, please reach out to us on TG)

***

### 1. Signing in

When you open the app you land on a card titled **Invitation Manager** with a short summary of what the tool does and a status line at the bottom.

1. The status reads **Waiting for account connection…** until the parent Circles app reports a wallet. Once it does, it changes to **Connected: 0x1234…5678** and a **Sign in** button appears.
2. Click **Sign in**. The app fetches a one-time challenge from the Circles auth server and asks your wallet to sign it.
3. Approve the **signature request** in your wallet.&#x20;
4. On success you land on the **Magic Links** tab.

***

### 2. The two tabs

Once signed in, you see two tabs across the top of every screen:

* **Magic Links** — your shareable distribution links. Each magic link is a campaign that bundles many invite codes behind one URL.
* **Individual Links** — your personal pool of raw invite codes that aren't currently attached to any magic link. Useful as a holding area, for codes you generated outside the app, or for codes that got pulled out of a deleted session.

Above the Magic Links list there is also a green banner reading **Overall quota available: N invites**. This is your remaining quota on the InvitationFarm contract, refreshed every time you load the tab. If it shows 0, generate buttons are hidden, but everything else still works.

***

### 3. Magic Links tab

Creating a new magic link

1. Click **+ New** in the top-right of the Magic Links tab. The **New Magic Link** modal appears.
2. Enter a **Label** (optional, max 80 chars) — e.g. `ETHDenver booth #3`. This is purely for your own reference and is never shown to invitees.
3. Pick an **Expires** option: **Never**, **1 day**, **7 days**, or **30 days**. After the chosen interval, the link stops accepting new claims (existing claims are unaffected). You can also pause and resume manually at any time, regardless of expiry.
4. Click **Create Magic Link**. The new link appears at the top of your list and the detail view opens automatically.

Click **🔗 Copy Link** on any card. The button briefly changes to **✓ Copied** for two seconds. The URL on your clipboard looks like:

```
https://circles.gnosis.io/invitation/<slug>
```

Anyone with this URL can claim *one* invite code from the session, in the order they were added. You can hand the same URL to many people — each visitor gets a different code under the hood.

#### Adding URL parameters (UTM tags etc.)

Click **＋ Add parameters** on a card to open the **Add Parameters** modal.

Enter a query string in `key=value&key=value` format — do **not** include the leading `?`. Example:

```
utm_source=booth&utm_medium=qr&utm_campaign=ethdenver
```

These params are saved to your browser's local storage *for that magic link* and automatically appended to whatever URL the **🔗 Copy Link** button copies next. The params are never sent to the server, so they will not appear if someone else views the same link from a different browser, and clearing your browser data wipes them.

The **＋ Add parameters** button changes its style once params are present, so you can tell at a glance which links already have tracking attached.

***

### 4. Magic link detail view

Click any card in the Magic Links list (or finish creating a new one) to open the detail view.

#### 4.1 The header bar

The header shows the label, the **Active** / **Paused** status, and the expiry summary (e.g. *Expires in 5d* or no expiry). On the right are four icon buttons:

* **⏸ / ▶** — pause or resume the session. While paused, the link is dead for new claimants; existing claims are not affected. Click again to resume.
* **↻** — refresh the session data and codes list from the server.
* **↪** — reassign the entire session to another wallet address (see 4.8).
* **🗑** — delete the session (see 4.9). This button has a red tint as a warning.

#### 4.2 Generating new invite codes (quota required)

If your overall quota is greater than 0, a green bar appears just below the header reading **Quota: N**, with a number input (1–10) and a green **＋** button.

1. Set the number of codes you want to generate. The maximum per click is 10 or your remaining quota, whichever is lower.
2. Click **＋**. The status line walks through several stages: generating keypairs, building transactions, waiting for wallet confirmation, adding to session, and (if a group is assigned) trusting in the group.
3. Your wallet asks you to approve **two transactions**:
   * **Tx 1** — claim invite slots from the InvitationFarm contract. This consumes your quota.
   * **Tx 2** — transfer the resulting invite tokens to the session's holder address.
4. The keys are stored on the referrals server and added to this session.
5. If you have a group assigned, the new accounts are trusted into that group automatically.
6. On success: **Generated and added N invitations to this session.** The quota counter drops accordingly.

#### 4.3 Assigning a group

Below the header is a row reading **Group: No group** (or the group name if one is already set). Click the value to open the **Assign Group** modal.

The modal lists every Circles group you can act on:

* Groups where your wallet is the **owner** or the **service** address, and
* Groups owned by a **Safe multisig** that you are a signer on (your role is shown as the Safe's role; the Safe's address is shown in italics).

#### 4.4 The invites list

Under the **Invites** heading you see one row per invite code attached to this session. Each row carries:

* A **truncated key preview** like `0x1a2b3c…d5e6f7` — that's the start and end of the private key. Codes are *not* shown in full anywhere in the UI.
* The **derived account address** in small grey text (when the address is known), so you can match a code to an on-chain account.
* A **status badge**: *queued* (sitting in the session, ready to be handed out), *dispatched* (the URL has been opened by someone), or *claimed* (an invitee has successfully registered with it).
* Two icon buttons on **queued** rows only: **↔ Reassign** (move the code to another magic link) and **✕** (remove from this session). Dispatched and claimed codes have no actions — once a URL has been visited, the code is in the wild.

Codes are fetched in pages of 50; the app keeps loading until all of them are on screen, so there is no "load more" button. Claimed codes are collapsed under a *N claimed invites* toggle at the bottom; click it to expand them (claimed codes have no actions — they belong to real users now).

The header shows a running summary: *N claimed, M dispatched, K total*.

#### 4.5 Per-code actions

* **↔ Reassign** — opens the **Move to another Magic Link** modal listing your other active (non-paused) sessions. Click one to move just this code. Useful when you need to rebalance between campaigns. If the source session has a group assigned, that trust is left alone (the code is still trusted in the old group); reassign the group on the destination if you need different membership.
* **✕** — pulls the code out of this session entirely. The code is not destroyed — it returns to your pool on the **Individual Links** tab and can be reassigned later.

#### 4.6 Adding codes manually

At the bottom of the codes list is a full-width **+ Add keys manually** button. Click it to open a textarea.

Paste private keys, **one per line**, in `0x` + 64 hex character format. Stray whitespace is fine; anything that doesn't match `0x[0-9a-fA-F]{64}` is silently skipped.

Click **Add Keys**. The result line reports how many were:

* **Added** — newly attached to this session.
* **Skipped (duplicate)** — the same private key was already in your account.
* **Already claimed** — the key has already been used to register a Circles account; it can't be reused.

If a group is assigned, the new accounts are trusted into the group as part of the same flow.

#### 4.7 Pausing and resuming

Click the **⏸** button to pause a session. The status pill flips to *Paused* and the magic link URL becomes inactive and anyone who opens it sees a "this link is not active" message in the Circles app, even if there are still queued codes. Click **▶** to resume.

Pausing is purely a server-side flag; it costs no gas and is instantaneous.

#### 4.8 Reassigning the session to another address

Click the **↪** button in the header. A blue input row appears: enter a `0x` + 40-hex-character wallet address and click **Reassign**.

The address must validate; if it doesn't, the input border turns red. On success, a centred modal appears reading **Session Reassigned** with the new owner's address — click **Done** to return to the Magic Links list. The session no longer appears in your list; it's now owned by the recipient and they will see it when they sign in.

This is a server-side ownership transfer. It does **not** move the codes out from under you — the on-chain accounts behind each code are unchanged — but the new owner gains control over the session record (including its label, expiry, group assignment, and the ability to add or remove codes).

#### 4.9 Deleting a session

Click the red **🗑** button. A confirmation row appears: **Delete this session? This cannot be undone.** with **Delete** and **Cancel** buttons.

Confirming triggers a multi-step cleanup:

1. Fetch every code in the session that is not yet claimed.
2. If a group is assigned, **untrust** those accounts from the group on chain (one or more wallet transactions, batched \~30 per tx).
3. **Migrate the unclaimed codes back to your pool** (best-effort — if this step fails, the codes are not lost on chain, but they may not surface in the **Individual Links** tab; they can still be reattached manually).
4. Delete the session record from the server.
5. Return you to the Magic Links list.

Claimed codes are deliberately left alone — they belong to real Circles accounts and deleting the session does not remove those accounts or their group memberships. If you want to fully revoke a claimed account from a group, you have to do that through the normal Circles group admin tools.

***

### 5. Individual Links tab

Switch to **Individual Links** in the top tab bar.

#### 5.1 What this tab is for

This is your personal pool of invite codes that are *not* currently attached to any magic link. Codes land here when you:

* Generate them externally and store them via the advanced add-keys panel (5.3).
* Click **✕** on a code inside a session to pull it out (4.5).
* Delete a session — its unclaimed codes migrate here (4.9).

Use it as a holding area, or as a way to bulk-attach a stockpile of codes to a magic link in one go.

#### 5.2 What you see

The pool loads in pages of 50; the app keeps fetching automatically until everything is on screen. Each row has:

* A **checkbox** for bulk selection.
* The **truncated key preview**.
* A **Confirmed** pill on codes that the referrals server has confirmed (this is just a freshness signal and both confirmed and unconfirmed codes are usable). Codes that are not yet confirmed show no pill.

Already-redeemed codes appear in a collapsed section at the bottom under a *N claimed invites* toggle, each with a faded **Claimed** pill and the derived account address. There are no per-row actions on this tab — bulk-assign (5.4) is the only operation.

A **+ Add keys manually (advanced)** link sits above the list. The "advanced" tag is intentional: most people will never need to paste raw private keys.

#### 5.3 Storing codes to your pool

1. Click **+ Add keys manually (advanced)**. A text area opens.
2. Paste private keys, one per line, in `0x` + 64 hex character format.
3. Click **Store Keys**. Same parsing rules as the session manual-add (4.6); duplicates and already-claimed keys are reported in the result line.

The codes land in your pool immediately; they can then be attached to any magic link via 5.4.

#### 5.4 Bulk-assigning to a magic link

1. Tick the checkboxes next to the codes you want to attach. To select every code currently rendered, use the **Select all** checkbox in the header row.
2. The right side of the header row shows **Assign to magic link →** with a count.
3. Click it to open the **Assign to Magic Link** modal — a list of your active (non-paused) sessions with their totals.
4. Click a session. The codes are attached to it, your selections clear, and the pool list refreshes.

If the target session has a group assigned, the newly attached codes are *not* automatically trusted — to do that, open the session detail and either re-pick the group (which retriggers trust on every code) or generate/add the codes directly from inside the session.

To move a code in the other direction (out of a magic link and back to this pool), use the **✕** button on the codes list inside the session detail.

***

### 6. What an invitee experiences

When you give someone a magic link, here is what happens on their end:

1. They open `https://circles.gnosis.io/invitation/<slug>` (with whatever UTM tags you appended) in their browser.
2. The Circles app picks the next queued code from your session and walks the visitor through onboarding *as that pre-funded account*. They do **not** need to bring their own wallet — the invite code itself is the private key for a fresh account, and Circles imports it into the user's session.
3. If the session has a **group** assigned, the visitor is automatically a trusted member of that group from day one.
4. The session's counts update: *queued* drops by one, *dispatched* / *claimed* go up.

If the link has been **paused**, has **expired**, or has **no codes left**, the Circles app shows the visitor an inactive-link message and no account is created.

***

### 7. Troubleshooting / FAQ

#### "No quota available to generate invitations."

Your address has no remaining slots on the InvitationFarm contract. Quota is allocated by the Circles team; you can still create magic links, attach codes you generated elsewhere, and reorganise existing codes. The **Overall quota available** banner at the top of the Magic Links tab is the source of truth.

#### Wallet popup never appears, or "Failed: …" during generate

The two-transaction generate flow (claim slot, then transfer tokens) is all-or-nothing. If you reject either popup, run out of xDAI for gas, or the wallet times out, nothing is added to the session and your quota is not consumed. Try again. If only the *trust into group* batches fail, the codes *are* in the session — just unscoped — and you can retry the trust by re-picking the group on the detail screen.

#### "No valid keys found. Keys must be 0x + 64 hex characters."

The textarea parser only accepts strings that match `0x` followed by exactly 64 hex characters. Common causes: a stray `0x` prefix is missing, the key has extra leading/trailing whitespace, or there are line breaks inside a single key. Each line is one key.

#### "Added N key(s). M skipped (duplicate)." / "M already claimed."

* **Skipped (duplicate)** — the same private key is already attached to one of your sessions or sitting in your pool. No action needed.
* **Already claimed** — the key has already been used to onboard a Circles account, so it can't be reused. Discard it.

#### Group assignment fails or only partly applies

Trust calls are batched \~30 accounts per transaction. If your wallet rejects a batch or you run out of xDAI mid-flow, the earlier batches succeeded but later ones did not. Refresh and re-pick the same group — already-trusted accounts will be no-ops, and the missing ones will be filled in. Safe-owned groups may also bounce if you don't have enough cosigner approvals on the underlying Safe.

#### My link shows as inactive to invitees

The session is **paused**, has **expired**, or has **0 queued codes**. Check the status pill on the detail view and the *queued* count under the Invites heading. Resume with **▶**, or generate / attach more codes.

#### "Reassign" address keeps getting rejected

The destination must be a valid Ethereum-style address: `0x` followed by exactly 40 hex characters. The input box turns red on invalid input. Paste the address fresh, with no surrounding whitespace.

#### Delete didn't put my codes back in the pool

The migrate-back step is best-effort. If the network blip happens at exactly that moment, the on-chain accounts and private keys are still safe, but they may not appear in the Individual Links tab. You can re-add them via the advanced **+ Add keys manually (advanced)** panel if you have a copy of the keys; otherwise they're effectively orphaned (the underlying Circles accounts continue to exist).

#### Sign-in fails

Two common cases: the wallet rejected the signature request (try again), or the auth server is unreachable (check `auth.aboutcircles.com` from your network). The signature is gas-free — if your wallet is asking for gas at this step, dismiss the request and retry; the next prompt should be a plain message-signing prompt.

#### Overall quota banner shows 0

You have no inviter slots allocated on the InvitationFarm contract. You can still:

* Create magic links and attach codes you have manually.
* Manage existing magic links (label, expiry, group, pause, copy URL, delete).
* Use the Individual Links pool to stockpile and reassign codes between sessions.

To get quota allocated, reach out to us on TG .

#### The lookup-link panel returns nothing

The slug in the URL doesn't correspond to any current session — either it was mistyped, or the session has been deleted by its owner. Double-check the URL.


# What are Circles groups?

* 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.


# Create a Circles Group using Group manager MiniApp

[Circles Groups Manager](https://circles.gnosis.io/admin/circles-groups) helps you create and manage Circles groups from one place. You can use it to create a new group, update the group's profile, manage admins and members, review important addresses, convert collected fees, and send group tokens.

This guide is written for people using the deployed app inside the Circles MiniApp environment.

### Who This App Is For

Use this app if you need to:

* create a new Circles group
* manage an existing group you control
* add or remove group members
* add another admin to the group
* update ownership or operational addresses
* convert group fees into group tokens
* send group tokens to a Circles user

### Before You Start

#### Requirements

* a Gnosis App account

#### Important Things To Know

* Most actions in this app require an onchain transaction.
* Every important change should be reviewed carefully before you approve it.
* Group image uploads are processed automatically:
  * the original file must be `8 MB` or smaller
  * the image is cropped to a square
  * the preview is compressed to `256x256`
  * the final processed image must be under `150 KB`

### Opening the App

1. Open Circles Groups Manager from the Circles MiniApp.
2. Connect your wallet if prompted.
3. Wait until the status changes from `Not connected` to `Connected`.

If wallet connection fails, reconnect the same wallet and try again.

### What You See First

After connecting, the app opens on the groups list.

This screen shows:

* groups owned directly by your connected wallet
* groups owned by owner Safes you control

Each group card shows:

* the group image if available
* the group name
* the ticker or fallback identifier
* the group address
* an `Open` button

If you have not created or connected any groups yet, the app shows `No groups yet.`

### Main Navigation

From the groups list, you can:

* click `+ Create` to create a new group
* click `Open` to manage an existing group

When a group is open, the app gives you four main areas:

* `Members`
* `Treasury Operations`
* `Group Details`
* `Advanced`

The group header also includes:

* `Edit` to open the profile editor
* `Refresh` to reload group data
* `Switch Groups` to go back to the groups list

### Create a Group

Click `+ Create` from the groups list.

#### Information You Need To Enter

* `Name`
* `Ticker`
* `Description`

You can also add:

* a group image
* an external link with label and URL

#### Rules For Group Creation

* Name is required.
* Name must be `19` characters or fewer.
* Ticker is required.
* Ticker must be `2` to `8` characters long.
* Ticker can only contain uppercase letters and numbers.
* Description is required.

#### How To Create The Group

1. Enter the group name.
2. Enter the ticker.
3. Add a short description explaining what the group is for.
4. Optionally upload an image.
5. Optionally add an external link.
6. Click `Create Group`.
7. Review the transaction flow and approve it.

### Open a Group

Click `Open` on any group in the groups list.

When a group opens, the app loads the main group summary, including:

* group name and ticker
* group description
* group image
* member count
* affiliate count
* fee balance summary
* key group addresses
* total supply

This is the main hub for everything you do after the group is created.

### Edit Group Details

Open a group, then click `Edit`.

This section lets you update:

* description
* image
* external link

#### How To Update The Profile

1. Change the description if needed.
2. Upload a new image if needed.
3. Add or update the external link if needed.
4. Click `Save Profile`.
5. Approve the transaction.

After the update, the group header refreshes with the new content.

#### External Link Behavior

The app supports one external link:

* a link label
* a link URL

Use this for a website, community page, docs page, or any destination that helps members understand the group.

### Members

Open a group, then choose `Members`.

This screen is where you manage who is trusted by the group.

#### What You Can Do Here

* view members
* search for people or groups to add
* remove existing members
* move through pages of members

The app loads members in pages of `50`.

#### Add a Member

You can add a member by searching for:

* name
* registered name
* Circles avatar address

**Steps**

1. Type at least two characters in the search box, or paste a full `0x...` address.
2. If needed, enable `Include v1 users`.
3. Choose the correct result.
4. Click `Add`.
5. Approve the transaction.

Adding a member creates a trust relationship from the group to that avatar.

#### Remove a Member

1. Find the member in the list.
2. Click `Remove`.
3. Approve the transaction.

Removing a member revokes that trust relationship.

#### Useful Member Search Notes

* If you paste a valid address, the app lets you add it directly.
* If a result is already a member, the app marks it as already added.
* If nothing matches, the app shows `No matches found.`

### Group Details

Open a group, then choose `Group Details`.

This screen is mainly for reference. It shows the important onchain addresses and technical details tied to the group:

* group address
* owner Safe
* treasury
* mint handler
* service
* fee collection
* group type
* total supply

Use this page when you want to verify which addresses are currently active before making a change.

### Advanced

Open a group, then choose `Advanced`.

This area is for admin and configuration tasks.

It includes:

* current group admins
* add new admin
* update owner Safe
* update service address
* update fee collection address
* manage membership conditions

### Group Admins

At the top of the `Advanced` screen, the app shows the current owners of the owner Safe. These are effectively the people who can control group-level admin actions.

#### Add a New Admin

You can add a new admin by:

* searching by name
* searching by address
* pasting a wallet address

**Steps**

1. Enter a name or address in `Add new Group Admins`.
2. Select the correct result, or paste the address directly.
3. Click `Add New Admin`.
4. Confirm the action.
5. Approve the transaction.

#### Important Limitation

The app only supports adding Safe owners when the owner Safe threshold is `1`.

If the Safe uses a threshold greater than `1`, the app will not allow this action.

### Update Owner Safe

Use this when you want a different Safe to take control of the group.

#### When To Use It

* you are transferring control to a new Safe
* you are restructuring admin ownership
* the current owner Safe is no longer the correct controller

#### Steps

1. Expand the `Advanced` section inside the admin panel.
2. Paste the new owner Safe address.
3. Click `Update Owner`.
4. Review the warning carefully.
5. Approve the transaction.

This is a high-impact action because it changes who controls owner-level group settings.

### Update Service Address

Use this only if you know exactly which service contract the group should reference.

#### Steps

1. Expand the `Advanced` section.
2. Paste the new service address.
3. Click `Update Service`.
4. Confirm the change.
5. Approve the transaction.

### Update Fee Collection Address

Use this to define where fees for the group should accumulate.

#### Steps

1. Expand the `Advanced` section.
2. Paste the new fee collection address.
3. Click `Update Fee Collection`.
4. Approve the transaction.

If you enter the same address as the currently connected wallet, the app shows a warning because this is usually not the best operational setup.

### Membership Conditions

Membership conditions are contract addresses that the group has enabled.

Use this feature if your group relies on additional rule contracts for membership logic.

#### Enable a Membership Condition

1. Paste the contract address.
2. Click `Enable`.
3. Approve the transaction.

#### Disable a Membership Condition

1. Paste the address of an active condition.
2. Click `Disable`.
3. Approve the transaction.

The app prevents:

* enabling the same condition twice
* disabling a condition that is not currently active

### Treasury Operations

Open a group, then choose `Treasury Operations`.

This area has two main features:

* `Convert Fees`
* `Send Token`

### Convert Fees

This feature converts balances held at the fee collection address into the group token by routing them to the mint handler.

#### What You See

* fee collection balance
* currently convertible amount
* amount input
* `Max` shortcut

#### When To Use It

Use this when fees have accumulated and you want to convert the available balance into the group's token.

#### Steps

1. Review the current fee collection balance.
2. Check how much is currently convertible.
3. Enter an amount, or click `Max`.
4. Click `Convert Fees`.
5. Approve the transaction.

#### Conversion Rules

* the group must have a valid fee collection address
* the group must have a mint handler
* the fee collection address must hold fee tokens
* the amount must be greater than zero
* the amount cannot exceed the convertible balance

If the button is disabled, the app currently does not detect a convertible fee balance.

### Send Token

This feature sends the group token from the fee collection address to another Circles avatar using max-flow routing.

#### What You See

* available group-token balance
* recipient search
* amount input
* `Max` shortcut

#### When To Use It

Use this when you want to distribute group tokens to a person, member, or another Circles participant.

#### Steps

1. Enter a recipient name or `0x...` address.
2. Select the correct result if needed.
3. Enter an amount, or click `Max`.
4. Click `Send Token`.
5. Approve the transaction.

#### Send Rules

* the group must have a valid fee collection address
* the recipient must resolve to a valid Circles avatar
* there must be a routable path through the trust graph
* the amount cannot exceed the current max transferable amount

#### How `Max` Works

* If you have not selected a recipient yet, `Max` fills the currently available group-token amount.
* If a recipient is selected, `Max` calculates the maximum amount that can actually be routed to that recipient.

If no route exists, the app will show that no routable amount is available.

### Typical End-to-End Workflow

For most users, the normal flow looks like this:

1. Open the app in Circles and connect your wallet.
2. Create a new group.
3. Review the generated owner Safe and group details.
4. Update the profile so the group has a clear description and image.
5. Add any additional admins you want managing the group.
6. Add the first members.
7. Verify the fee collection address and other advanced settings if needed.
8. When fees accumulate, convert them into group tokens.
9. Send group tokens to recipients when needed.

### Troubleshooting

#### Wallet Does Not Connect

* Make sure you opened the app inside the Circles MiniApp.
* Reconnect the same wallet and try again.

#### Group Creation Fails

Check that:

* your wallet is connected
* the name is not longer than `19` characters
* the ticker is `2-8` uppercase letters or digits
* the description is filled in
* you approved the full transaction flow

#### Image Upload Fails

Check that:

* the file is an image
* the source file is `8 MB` or smaller
* the image can be processed and compressed

#### No Groups Appear

* you may not own any groups yet
* you may not control any owner Safes that currently own groups
* reconnect and reopen the app if the list seems stale

#### Cannot Add a New Admin

Check that:

* the address is valid
* the address is not already an owner
* the owner Safe threshold is `1`

#### Cannot Convert Fees

Check that:

* the group has a valid fee collection address
* the group has a mint handler
* the fee collection address holds fee tokens
* the amount does not exceed the convertible amount

#### Cannot Send Tokens

Check that:

* the fee collection address is valid
* the recipient is a valid Circles avatar
* a routable trust path exists
* the amount does not exceed the max transferable amount

### Safety Tips

* Always double-check pasted addresses before approving a transaction.
* Treat `Update Owner` as a high-impact action.
* Avoid using a personal wallet as the fee collection address unless that is intentional and you are sure of what you are doing.
* Only update the service address if you understand why the group should point to a different contract.


# Create a Circles Group using Circles Core App

### Prerequisites

Make sure you’ve completed these before continuing.

* [x] You’ve installed a wallet, [Rabby](https://rabby.io/) or [Metamask](https://metamask.io/)
* [x] You’ve received an invite link, and you have an active [Gnosis app account](https://app.gnosis.io/circles)
* [x] Open the [Circles App](https://app.aboutcircles.com/) and deploy a new [Safe](https://safe.global/)

{% hint style="info" %}
Using a Chrome-based desktop browser or Firefox is recommended (the app is not yet optimised for mobile devices)
{% endhint %}

### Creating a New Group

#### **1. Connecting**

Visit [app.aboutcircles.com](http://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

![](https://hackmd.io/_uploads/SyLLzqGUxg.png)

#### **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).

![](https://hackmd.io/_uploads/ByruM5f8gx.png)

#### **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

![](https://hackmd.io/_uploads/ByxZQcM8ex.png)

#### **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

![](https://hackmd.io/_uploads/BJlY7cMUgg.png)

#### **5. Group created**

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

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

![](https://hackmd.io/_uploads/HJO4Q9fIel.png)

#### **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.

![](https://hackmd.io/_uploads/rJ2wXcfLex.png)

#### **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

![](https://hackmd.io/_uploads/SyNwmcG8ll.png)

#### **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

![](https://hackmd.io/_uploads/SJhq7cGIel.png)

#### Access an existing group

**1. Connecting**

Visit [app.aboutcircles.com](http://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.

![](https://hackmd.io/_uploads/r1OnQ5zUel.png)

**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)

![](https://hackmd.io/_uploads/ryTpXczIgg.png)

**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.

![](https://hackmd.io/_uploads/SJnm4czLgl.png)

**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

![](https://hackmd.io/_uploads/H1xmVqMIxg.png)

**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

![](https://hackmd.io/_uploads/r17fV9zUxx.png)

***

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.


# Migrate your group and update its details

How to migrate your group so you can edit it, and how to fill in the new group contract properties as an admin.

New properties have been added to the group contract, and group admins need to fill them in. This guide walks you through making your Circles profile an admin of the group and then updating the new fields in the admin app.

The process is straightforward: you add your Circles profile as a signer of the group's owner Safe, then use that connection to edit the group in the admin app.

#### Assumptions

Before you start, this guide assumes the following:

* You set up your account using [app.aboutcircles.com](https://app.aboutcircles.com). This means you have a signer and a wallet you can connect to that app to modify your account.
* Your group is owned by a Safe (multisig) that you control.
* You have a Circles profile and are willing to use it as an admin of the group.

#### Which steps apply to you?

* If you created your group using [app.aboutcircles.com](https://app.aboutcircles.com), start at **Step 1**.
* If you created your group using [circles.gnosis.io/admin/circles-groups](https://circles.gnosis.io/admin/circles-groups), skip to **Step 6**.

### Part 1 — Make your Circles profile an admin of the group

#### Step 1: Find your group

Open the Group Checker and locate your group:

{% embed url="<https://aboutcircles.github.io/CirclesTools/groupChecker.html>" %}

#### Step 2: Open the owner Safe

In the **Owner** column, find the owner Safe of your group and click the wallet symbol next to it to open it in the Safe UI.

<figure><img src="/files/k7V6gZtw1nXZf6NRbttF" alt="" width="375"><figcaption></figcaption></figure>

*Group Checker showing the Owner column and the wallet symbol used to open the owner Safe in the Safe UI*

#### Step 3: Connect to the Safe UI

Connect to the Safe UI using your owner EOA — the wallet that owns the Safe.

#### Step 4: Find your Circles profile address

Locate the address of your personal Circles profile (for example, via the Gnosis App).

<figure><img src="/files/zzrB1JyDjmhBrZxwKp21" alt="" width="375"><figcaption></figcaption></figure>

#### Step 5: Add your Circles address as a signer

In the Safe UI, go to **Settings → Manage Signers** and add your Circles address as a signer. Then sign the transaction.

{% hint style="info" %}
Once the transaction is confirmed, your Circles profile is an authorised admin of the group's owner Safe and you can use it to edit the group.
{% endhint %}

### Part 2 — Update the group details

#### Step 6: Sign in to the admin app

Go to [circles.gnosis.io/admin/circles-groups](https://circles.gnosis.io/admin/circles-groups) and sign in using your passkey for the Gnosis App.

#### Step 7: Open your group

Open your group and select **Edit Details**.

#### Step 8: Edit the details

Fill in the new fields:

* **Description** — Explain the purpose of the group and the benefits of joining.
* **Group type** — Set this to **closed**.
* **Contact details** — Provide a way for prospective members to reach you.
* **Membership Fee** and **Minimum Rep Score** — Optional.
* **Additional Criteria** — The requirement(s) that users must satisfy before they will be considered for group membership (this field

#### Step 9: Save

Click **Save** to apply your changes.

{% hint style="success" %}
That's it — your group details are now updated.
{% endhint %}


# 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.

![](https://hackmd.io/_uploads/By5lRFGIlx.png)

#### **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.

![](https://hackmd.io/_uploads/r1_mRFfUle.png)

#### **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.

![](https://hackmd.io/_uploads/ryyFCFzIee.png)

#### **Redemption Policy**

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

![](https://hackmd.io/_uploads/rJf9AYMIex.png)

#### **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](https://hackmd.io/_uploads/B1NblcM8lg.png)

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

![](https://hackmd.io/_uploads/B1xMLxcMLeg.png)

#### 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.

> <img src="https://2619591868-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fs76GmMOXUUWWi7YK8Ugp%2Fuploads%2F58oDoEmFTMWdwiLxZI3K%2FScreenshot%202025-07-15%20at%2010.44.25.png?alt=media&#x26;token=d5f17b36-d79c-43b4-967b-c97242617847" alt="" data-size="original">

#### 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.


# 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](https://t.me/about_circles/1) ord [Discord](https://discord.gg/pxtBuJJPKJ) #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                                                                                                                                                                                                                                                                      |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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.                   |

#### Helpful Resources

* [Why Build on Circles](https://docs.aboutcircles.com/overview/why-build-on-circles)
* [Marketing Assets](https://github.com/aboutcircles/media-kit/archive/refs/heads/main.zip)
* [Circles FAQ](https://aboutcircles.com/faqs)


# Migrate Circles v1 account to v2

{% embed url="<https://circles.garden/>" %}

Use this app to migrate your v1 Circles to the latest Circles v2.

### Pre-requisites

You will need:

* Your Circles v1 account (from Circles Garden).
* Your 24-word Circles Garden seed phrase.
* An inviter in Circles v2 (the app will show your available invites).

{% hint style="info" %}
Your seed phrase is never stored by this app (it is used locally to derive your account).
{% endhint %}

### Step-by-step migration guide

<figure><img src="/files/hx1E7m70rIYOfKvgne3O" alt=""><figcaption></figcaption></figure>

* Open the Circles Migration Tool in your browser.
* Click **Start migration** to open the import dialog.

<figure><img src="/files/yHXGwRQBPhjotkSWm54Z" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/tgZk7zYNEuACTmnVu2Vb" alt=""><figcaption></figcaption></figure>

* Paste your 24-word Circles Garden seed phrase.
  * The word counter should show **24/24** before you continue.
  * You can toggle visibility to double-check your phrase.
* Click **Continue** to import the account.
* Wait while the app checks your Circles status and loads your data.
* Review the status shown at the top:
  * **Already on V2** or **Migration Complete**: you are done; click **Visit Gnosis app**.
  * **Migrate to V2**: proceed to the next step.
* **Choose an inviter**:
  * Select one inviter from the list.
  * If no inviters are listed, use the **Get invited to Circles** link and come back after you are invited.
* **Create your profile**:
  * Add a name (required, max 36 characters).
  * Add a description (optional, max 500 characters).
  * Upload an avatar image (optional). The image must be small (max 150 KB).
* Click **Review** to confirm your profile details.
* **Complete migration**:
* Review the summary (your profile + inviter).
* Read the on-chain warning. This action is irreversible.
* Click **Complete profile migration** and wait for the confirmation message.

<figure><img src="/files/I5IWGQVM5TfZ31d7B62y" alt=""><figcaption></figcaption></figure>

* On success, click **Visit Gnosis app** to continue using Circles v2.

### Troubleshooting

* **Invalid seed phrase**: ensure it is exactly 24 words from Circles Garden.
* **No inviters available**: you must be invited to Circles v2 before migrating. The inviter also must hold 100 Personal CRC tokens to send you an invitation.
* **Profile validation errors**: shorten the name/description or use a smaller image.
* **Migration failed**: try again later and contact support in discord if the problem persists.

### Support

Need help with migration? Join the [Circles Discord](https://discord.com/invite/aboutcircles).


# Circles Transfer and Rule of Trust

This guide will help you understand balances, send limits, and how the Pathfinder finds a route

{% embed url="<https://www.loom.com/embed/fe3961c5ffa74942b9bf4468fa486c09>" %}

When you open the Circles tab in the [Gnosis](https://app.gnosis.io/) app, you’ll typically see:

* Total Circles balance (e.g., 8,602 CRC)
* A Send flow that shows a Send limit (e.g., only 6,800 CRC available to send to a specific person)

That difference is expected. Your total balance is not the same as what’s routable to a specific recipient at this moment.

### Everyone creates their own Circles

In Circles, every person mints their own unique CRC (their “personal currency”).

So your wallet’s total “CRC balance” is usually a mix of CRC issued by many different people, not only the CRC you personally minted.

For example, your total might include:

* some Gnosis Group Circles (gCRC)
* your own minted CRC
* and other people’s CRC

### The Rule of Trust&#x20;

The most important rule is the Rule of Trust:

> If Alice trusts Bob, then *anyone* holding Bob’s CRC can swap it 1:1 for *any CRC currently held by Alice* (whatever Alice holds at that moment).

#### What does this imply?

* Trust is not just “permission to receive.”
* It creates swap capacity that others can use to move value across the network.
* Transfers can become transitive (they can hop through trust relationships).

### Why you can’t always send your full balance to someone?

Your ability to send CRC to a specific recipient depends on two things together:

1. Trust relationships across the network (who trusts whom)
2. Current distribution of CRC holdings (who is holding which people’s CRC)

So even if you have 8,602 CRC total, the network might only be able to route 6,800 CRC from you to that particular recipient *right now*.

{% hint style="info" %}
Total balance = what you have

Send limit = what the network can currently route to that person through trust + available intermediate balances
{% endhint %}

***

### How Circles finds the sendable amount (Max Flow routing)?

Under the hood, Circles models the routing problem as a maximum flow problem (a well-known graph algorithm problem).

* The network is a graph:
  * nodes = people/accounts
  * edges = trust relationships + swap possibilities created by who holds whose CRC
* The solver searches through many possible combinations to compute:
  * the maximum amount that can be delivered to a chosen recipient

### Checking routes and limits with Pathfinder Pro App (Advanced Users Only)

If you want to see the “power tool” version of the routing calculation, use Pathfinder Pro:

1. Navigate *to* [pathfinder.app.aboutcircles.com](https://pathfinder.app.aboutcircles.com/flow-visualization/)
2. Paste your address in the sender field
3. Paste the recipient’s address in the recipient field
4. Set Amount to a high number (to probe the maximum)
5. Click Find a path
6. Pathfinder returns the maximum sendable amount — this should match what the Gnosis app shows as the send limit

***

### Practical tips

* Your total balance being higher than your send limit is normal.
* Send limits vary by recipient. You may be able to send more to one person than another.
* Send limits change over time as:
  * trust links change
  * people’s holdings change
  * routing opportunities appear/disappear


# Daily Burn (Demurrage)

This guide will help you understand what the “burn” events mean, how they work, and why they exist

{% embed url="<https://www.loom.com/embed/af3cf8c16d324ecca5aed7d5a7f78954>" %}

In Circles-enabled apps (for example, the [Gnosis app](https://app.gnosis.io/)), you can spot CRC burn events in your Activity feed. These appear regularly and reduce your displayed CRC balance over time.

***

### What is Daily Burn?

Daily Burn is Circles’ built-in demurrage: a small, automatic reduction of the CRC balance you hold.

* Roughly \~0.02% of your current balance per day is removed (“burned”).
* Over a year, that adds up to 7% annual demurrage.&#x20;
* This is not a fee and is not paid to anyone—the burned CRC simply cease to exist.&#x20;

### Why Circles has Daily Burn?

Daily Burn is one half of Circles’ monetary design:

1. Creation force: each user can create 1 CRC per hour.&#x20;
2. Destruction force: existing CRC depreciate at 7% per year via demurrage.&#x20;

{% hint style="info" %}
The Circles whitepaper explains that these two forces are tuned to support intergenerational fairness so the system stays attractive to new joiners even decades later.&#x20;
{% endhint %}

#### The “fair access” idea&#x20;

Circles aims for a property like:

* People who join later shouldn’t be permanently “too late.”
* Over long time horizons, the gap between early joiners and new joiners shrinks, because early-created money gradually loses weight relative to ongoing creation.&#x20;

### What does “demurrage” mean?

“Demurrage” is a monetary concept often associated with the economist [Silvio Gesell](https://en.wikipedia.org/wiki/Silvio_Gesell), historically discussed as a way to discourage hoarding and encourage circulation (sometimes via “stamp scrip” systems).&#x20;

Circles uses demurrage a bit differently: it’s paired with universal, ongoing issuance (everyone creates), which changes the goal from “just circulation” to a blend that supports long-term fairness and accessibility.&#x20;

### What Daily Burn implies for you?

* Holding CRC long-term has a carrying cost (like a gentle “melting”).
* Spending/using CRC sooner avoids some future burn (because you’re not holding it as long).
* Burn is automatic and time based, you don’t need to claim, approve, or trigger it.

### How the “maximum created” amount works?

Over a lifetime-scale horizon, demurrage plus 1 CRC/hour issuance creates a saturation effect:

* The [Circles whitepaper](https://whitepaper.aboutcircles.com/) notes that accounts approximately reach a maximum level of \~120,804 CRC after creating for about 80 years (life-expectancy scale).&#x20;
* After creation stops, the remaining nominal amount trends downward with an exponential “half-life” on the order of \~10 years.&#x20;

{% hint style="info" %}
Important nuance: this “maximum” framing is about what you can create in your own name over time, not a hard cap on what you can hold (you can still hold more via transfers).&#x20;
{% endhint %}

***

### FAQ

#### Is Daily Burn the same as a transaction fee?

No. It’s not paid to validators, app developers, or any treasury. It’s a protocol-level removal of value from existing balances.&#x20;

#### Does burn apply to “my CRC” only or all CRC I hold?

It applies to the CRC balance you hold (regardless of whose CRC they originated from), because it’s defined as depreciation of existing CRC holdings.&#x20;


# Trust Score

This guide will help you understand what it means, how it’s calculated, and how to use it safely

{% embed url="<https://www.loom.com/share/70900131b5fc42bc982085bbc22d1862>" %}

### What is Trust Score?

The Trust Score is a number shown on your profile that helps indicate how “established” an account is in the Circles network.

Use it as a signal when deciding who to trust, especially if you’re unsure and would otherwise “trust back” by default.

{% hint style="warning" %}
Important: It’s a helper, not a guarantee. Don’t use it as the only criterion.
{% endhint %}

### Where to find it?

1. Open the Gnosis app
2. Go to your Profile page
3. Look for the Trust Score number (You can also tap it to open the explainer modal, a short in-app explanation)

### What should you use it for?

#### 1) Safer trust decisions

The main purpose is to help you decide whether it’s sensible to trust a new or unfamiliar account.

* Low Trust Score → higher caution. It *may* be a bad idea to trust them.
* High Trust Score → generally a better signal, but still verify the person.

#### 2) Unlocking access to offers&#x20;

The score is also used to unlock or expand access to certain offers and experiences.

For example:

* Some marketplace offers may accept only Gnosis Group Circles as payment (not all CRC).
* To convert what you create daily into Gnosis Group Circles, you need to be part of the Gnosis group.
* One possible way to gain entry / eligibility is reaching a high Trust Score threshold (alongside other routes like holding a Gnosis Pay Card or being a “backer,” other criterias.

### How the Trust Score is calculated?

The Trust Score combines two components:

#### A) Risk Score (0–100, where 100 is worst)

* A higher Risk Score means the system considers the account riskier to trust.
* It’s based on many signals like:
  * activity patterns
  * trust connections
  * holdings
  * who holds your circles
  * minting behavior

#### B) Community Score (0–100, where 100 is best)

* A higher Community Score means the network has higher confidence it’s fine to trust you.
* This one is described as having a simpler approach than the Risk Score, and is based heavily on trust connectivity.

\
So, the final trust score uses the below formula:\
$$Trust Score = min( Community Score, 100 − Risk Score )$$

#### Why “minimum”?

It’s a “safety-first” design:

* If either your Community Score is low or your Risk Score is high, your final Trust Score will be low.
* A high Trust Score requires both:
  * low risk (so 100 − Risk Score is high)
  * high community confidence (Community Score is high)

### How to improve your Trust Score?

Because the final score is the *minimum* of two values, you want to improve both sides:

#### Improve Community Score

Community score relates to how well-connected you are to backers.

Circles Backers are accounts that have put up collateral to back their own currency. So they have “skin in the game” and tend to trust more carefully. Being trusted by well-connected / high-score participants helps.

Practical steps:

* Build genuine relationships in the community (don’t spam trust requests).
* Get trusted by established participants (especially those who are active and reputable).
* Avoid “trust circles” formed purely to inflate scores as these may backfire if flagged as suspicious behavior.

#### Keep Risk Score low

Because the Risk Score model is intentionally not transparent, your best bet is to behave in ways that are broadly “normal and trustworthy”:

* Avoid suspicious patterns (mass trust/untrust waves, bot-like activity, etc.).
* Keep your account behavior consistent and human.
* If you’re new, don’t rush: let trust build organically.

Use the Trust Score as a starting point, then also:

* confirm identity via a known channel (message them, check community presence)
* look at their activity history (if available)
* avoid trusting accounts you don’t recognize *just because they “trusted you”*

### FAQ

#### Is a high Trust Score a guarantee someone is safe?

No. It’s a helpful signal, not a guarantee.

#### Can I “farm” my score quickly?

The system is designed to resist gaming—especially via the Risk Score. Focus on real relationships and normal participation.

#### What does being trusted by high-score people do?

It can improve your Community Score and may help unlock access/eligibility thresholds.

> **Important limitation of use**
>
> The Trust Score is intended and designed only as a context-specific signal within the Circles ecosystem, for example to support trust, safety and anti-abuse decisions in Circles-related interactions. It is not a measure of a person’s general trustworthiness or social value. It must not be used outside this context, including, in particular, for employment, housing, education, public services, insurance or consumer credit decisions, or other real-world eligibility or access decisions. The Trust Score is only one signal and should not be used as the sole basis for decisions with significant impact on a person’s rights, opportunities or access to essential real-world services.


# What are Circles Mini Apps?

<div align="left" data-full-width="false"><figure><img src="https://2619591868-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fs76GmMOXUUWWi7YK8Ugp%2Fuploads%2FV4Kma3pkDEl221EUroL7%2Fcirclesminiapps.png?alt=media&#x26;token=9db5f4b9-0248-4feb-ad83-262b884d0984" alt=""><figcaption></figcaption></figure></div>

### What are Circles Mini Apps?

Circles Mini Apps are focused web applications built around Circles.

They are designed to deliver a narrow, task-specific experience rather than a full wallet or a broad product surface.

The core idea is simple: a Mini App should help a user complete one or a few meaningful actions quickly within a Circles-based product experience. Instead of recreating an entire wallet or ecosystem interface, a Mini App focuses on doing one thing well, such as making a payment, joining a group, claiming something, or completing a specific workflow.

### Embedded Mini Apps

In the embedded model, the user opens the Mini App inside the Mini App environment and signs actions directly there using their Gnosis App passkey.

This creates the smoothest user experience because the user stays within a single flow, without switching devices or scanning QR codes. It feels closer to a native Circles experience and allows tighter integration with the Circles host and SDK.

Embedded Mini Apps are typically the right choice when you want stronger integration with the Circles ecosystem, direct wallet interaction inside the app, and better discoverability through store-based distribution.

Example:

{% embed url="<https://circles.gnosis.io/miniapps/coinflip-game>" %}

#### Advantages

* Smooth, native-feeling user experience for existing Gnosis App users
* No extra QR scan step for wallet approval
* Stronger integration with the Circles host and SDK
* Better fit for repeat usage and polished product flows
* Can benefit from Mini App store discovery and distribution

#### Disadvantages

* Requires integration with the Circles host environment
* Usually needs more work to support SDK-based wallet flows
* May require store listing or ecosystem-specific distribution
* Less flexibility, especially if you require third-party integrations such as a database, compared to a fully standalone web app
* More dependent on platform-specific constraints and conventions

#### Best for

* Native Circles experiences
* Direct in-app wallet interactions
* Consumer-facing apps that benefit from low-friction UX
* Apps intended for ecosystem discovery and repeat use

### Standalone Mini Apps

In the standalone model, the Mini App runs as a regular web application on a shared screen, kiosk, event booth, merchant checkout page, or standard website. When wallet approval is required, the app can display a QR code that the user scans with Gnosis App to complete the action. Alternatively, it can provide a button that opens the appropriate Gnosis App transaction URL, allowing the user to complete the transaction in Gnosis App and then return to the Mini App flow.

This approach is often easier to deploy because it does not require embedded Mini App integration or store listing. It also gives builders more freedom to use their own backend, infrastructure, and application setup. The tradeoff is that it introduces an extra approval step, which makes the flow less seamless than an embedded Mini App.

Example:

{% embed url="<https://miniapps.aboutcircles.com/>" %}

#### Advantages

* Easier and faster to deploy
* Works as a normal web app without full host integration
* No need for Mini App store listing
* Greater flexibility in backend, architecture, and product design
* Well suited for physical-world setups like booths, kiosks, and merchant flows
* Easier to experiment with custom infrastructure or non-standard app logic

#### Disadvantages

* Less seamless user experience
* Requires an external approval step through Gnosis App
* Adds friction compared to direct in-app signing
* Can feel less native to the Circles ecosystem
* Multi-step flows may slow down fast or repeated transactions

#### Best for

* CRC transfer-related flows
* Event booth experiences
* Merchant checkout flows
* Apps that need custom backend logic or more deployment freedom


# Embedded Mini Apps

This is a practical, end-to-end technical guide for building miniapps that run inside the Circles miniapp host and communicate through [`@aboutcircles/miniapp-sdk`](https://www.npmjs.com/package/@aboutcircles/miniapp-sdk).

{% hint style="info" %}
If you are a vibecode developer, you can simply copy the entire page using the button above and paste in your coding environment
{% endhint %}

#### Mental Model: How Embedded Miniapps Run?

A embedded miniapp is a web app rendered in an iframe inside a host application.

* Your app owns UI, local state, validation, and business flow.
* The host owns wallet session UX and transaction approval.
* `@aboutcircles/miniapp-sdk` bridges both sides using `postMessage`.

This means:

* Local browser testing is useful for UI and logic.
* End-to-end wallet/transaction testing must happen in the host.

#### SDK Capabilities

The SDK currently exposes:

* `isMiniappMode(): boolean`
* `onAppData(fn: (data: string) => void): void`
* `onWalletChange(fn: (address: string | null) => void): () => void`
* `sendTransactions(transactions: Transaction[]): Promise<string[]>`
* `signMessage(message: string, signatureType?: 'erc1271' | 'raw'): Promise<{ signature: string; verified: boolean }>`

Where `Transaction` is:

```ts
type Transaction = {
  to: string;
  data?: string;
  value?: string; // hex string, e.g. "0x0"
};
```

#### Project Setup

**Create the app**

```bash
npm create vite@latest my-miniapp -- --template vanilla
cd my-miniapp
npm i
npm i @aboutcircles/miniapp-sdk viem
```

Add additional Circles SDK packages only if your use case needs them:

```bash
npm i @aboutcircles/sdk-core @aboutcircles/sdk-rpc @aboutcircles/sdk-profiles @aboutcircles/sdk-transfers @aboutcircles/sdk-utils
```

**Suggested structure**

```
src/
  app/
    state.js
    actions.js
    ui.js
  host/
    bridge.js
  chain/
    client.js
  utils/
    format.js
    validation.js
main.js
```

Keep host bridge logic isolated from domain logic.

#### Bootstrapping the Host Bridge

Create a dedicated bridge module:

```js
// src/host/bridge.js
import {
  isMiniappMode,
  onAppData,
  onWalletChange,
  sendTransactions,
  signMessage,
} from '@aboutcircles/miniapp-sdk';

export { isMiniappMode, onAppData, onWalletChange, sendTransactions, signMessage };
```

Use `onWalletChange` as the app’s wallet truth source.

#### Wallet Lifecycle Pattern

Implement wallet lifecycle as a finite-state flow:

1. `disconnected`
2. `connecting` (optional UI state)
3. `connected`
4. `error` (recoverable or fatal)

Minimal pattern:

```js
import { getAddress } from 'viem';
import { onWalletChange } from './host/bridge.js';

let connectedAddress = null;

onWalletChange(async (address) => {
  try {
    connectedAddress = address ? getAddress(address) : null;
  } catch {
    connectedAddress = null;
  }

  if (!connectedAddress) {
    resetAccountScopedState();
    renderDisconnected();
    return;
  }

  resetAccountScopedState();
  await loadInitialDataFor(connectedAddress);
  renderConnected(connectedAddress);
});
```

Rules:

* Always reset account-scoped caches on wallet changes.
* Never assume prior in-memory state is still valid after reconnects.

#### Transaction Submission Pattern

Always format and submit transactions through one adapter.

```js
import { sendTransactions } from './host/bridge.js';

function toHexValue(value) {
  return value ? `0x${BigInt(value).toString(16)}` : '0x0';
}

export function formatTxForHost(tx) {
  return {
    to: tx.to,
    data: tx.data || '0x',
    value: toHexValue(tx.value || 0n),
  };
}

export async function submitTransactions(txs) {
  return sendTransactions(txs.map(formatTxForHost));
}
```

Why this is critical:

* Prevents value-encoding inconsistencies.
* Keeps write paths auditable.
* Makes retries and logging straightforward.

#### Standard Action Pipeline

For every write action (any state-changing on-chain operation), use this pipeline:

1. Validate input
2. Normalize addresses/amounts
3. Optional preflight simulation
4. Build one or more tx payloads
5. Show pending UI
6. Submit via host bridge
7. Wait for confirmations (if required by UX)
8. Show success/failure
9. Refresh affected data

Example shell:

```js
export async function performAction(input) {
  validateInput(input);

  const txs = buildTransactions(input);
  setStatus('pending', 'Submitting transaction...');

  try {
    const hashes = await submitTransactions(txs);
    await maybeWaitForReceipts(hashes);
    setStatus('success', `Submitted ${hashes.length} transaction(s).`);
    await reloadRelevantState();
  } catch (err) {
    setStatus('error', normalizeError(err));
  }
}
```

#### Signing Flows (`signMessage`)

Use `signMessage` when your backend or protocol needs host-backed signatures.

```js
import { signMessage } from './host/bridge.js';

const result = await signMessage('Example message', 'erc1271');
// result: { signature, verified }
```

Guidance:

* Prefer `'erc1271'` unless you explicitly require raw bytes semantics.
* Persist signature type with the signature payload for verifier correctness.

#### App Data Injection (`onAppData`)

The host can pass app-specific context via query data.

Use cases:

* entry context (resource id, mode, source)
* feature flags
* campaign or referral metadata

Pattern:

```js
import { onAppData } from './host/bridge.js';

onAppData((raw) => {
  const data = safelyParseHostData(raw); // your parser/validator
  applyHostContext(data);
});
```

Never trust raw host data without validation.

#### Validation and Type Safety

Minimum validation set:

* `address`: checksum normalization, strict format checks
* `amount`: decimal precision + non-negative + max bounds
* `url`: protocol allowlist (`https://` by default)
* `text`: length limits and rendering-safe escaping

Keep pure validation helpers in a dedicated module and test them directly.

#### Error Handling and Recovery

Design a predictable error model:

* `validation_error`
* `user_rejected`
* `network_error`
* `host_bridge_error`
* `unexpected_error`

Normalize unknown errors:

```js
export function normalizeError(err) {
  if (!err) return 'Unknown error';
  if (typeof err === 'string') return err;
  return err.shortMessage || err.message || String(err);
}
```

For recoverable host issues:

* show actionable guidance
* preserve user input where safe
* do not silently retry state-changing actions

#### Concurrency and Race Control

Use request IDs for async operations that may race:

```js
let reqId = 0;

export async function runLatest(task) {
  const id = ++reqId;
  const result = await task();
  if (id !== reqId) return null; // stale
  return result;
}
```

Apply to:

* debounced search
* paginated loads
* wallet-dependent initialization

#### Generic Starter Skeleton

```js
import { getAddress, isAddress } from 'viem';
import {
  isMiniappMode,
  onWalletChange,
  sendTransactions,
} from '@aboutcircles/miniapp-sdk';

let connected = null;

function toHexValue(value) {
  return value ? `0x${BigInt(value).toString(16)}` : '0x0';
}

function status(msg) {
  document.getElementById('status').textContent = msg;
}

async function sendExample() {
  if (!connected || !isAddress(connected)) throw new Error('Wallet not connected');
  const txs = [{ to: connected, data: '0x', value: toHexValue(0n) }];
  const hashes = await sendTransactions(txs);
  status(`Submitted: ${hashes.join(', ')}`);
}

onWalletChange((address) => {
  try {
    connected = address ? getAddress(address) : null;
  } catch {
    connected = null;
  }
  status(connected ? `Connected: ${connected}` : 'Disconnected');
});

status(isMiniappMode() ? 'Host mode' : 'Standalone mode');
document.getElementById('send').addEventListener('click', () => {
  sendExample().catch((err) => status(`Error: ${err.message}`));
});
```


# Intermediate Embedded Mini App Guide

This guide is takes building Embedded Mini Apps one step further and we are going to demonstrate it with a practical example mini app linked below.

{% embed url="<https://github.com/aboutcircles/intermediate-miniapp-tutorial>" %}

Compared to simple embedded mini apps, where the user signs a transaction and an action happens directly, in this example the app performs a couple of extra backend steps (which you can vary based upon your mini app's logic).\
An intermediate mini app lets the host wallet execute the payment, embed a signed payment intent into the transfer, match it later through the Circles RPC, then issue a signed receipt or sign on chain transfers.

{% hint style="info" %}
You can also setup Circles Org using this [mini app](https://circles.gnosis.io/admin/miniapps-org-manager) if you want to process CRC payments from your backend. For example, a game which rewards people in CRC for winning , can setup an Org using that miniapp, fund it, add an eoa as a signer and confgure direct payments in the backend.
{% endhint %}

***

### Get Started

Clone the repository:

```
git clone https://github.com/aboutcircles/intermediate-miniapp-tutorial
cd intermediate-miniapp-tutorial
```

Install Dependencies & Start the dev server:

```
pnpm install
pnpm dev
```

Navigate to <https://circles.gnosis.io/playground> and paste your localhost dev url.

### Architecture

The app has four parts:

```
Circles Host
  └─ MiniApp iframe
       └─ React UI + MiniApp SDK

MiniApp Backend
  ├─ Builds CRC payment calldata
  ├─ HMAC-signs payment intents
  ├─ Matches transfers through the Circles indexer
  └─ Signs EIP-712 ticket receipts

Circles RPC / Indexer
  └─ Exposes indexed transfer events

User Wallet
  └─ Safe + ERC-4337 via the host
```

The key principle is simple:

The frontend is not trusted.

It can ask the backend to build transactions. It can ask the host to submit them. But it never verifies payments, signs receipts, or holds secrets.

***

### Handling Wallets Through the MiniApp SDK

Inside the Circles host, the MiniApp does not connect to a wallet directly.

Instead, it uses [`@aboutcircles/miniapp-sdk`](https://www.npmjs.com/package/@aboutcircles/miniapp-sdk):

```ts
import {
  isMiniappMode,
  onWalletChange,
  sendTransactions,
  signMessage,
} from "@aboutcircles/miniapp-sdk";
```

The app first checks whether it is running inside the host:

```ts
const isEmbedded =
  typeof window === "undefined" ? false : isMiniappMode();
```

Then it subscribes to wallet updates:

```ts
onWalletChange((address) => {
  setWalletAddress(address ?? null);
});
```

This keeps the MiniApp reactive without owning the wallet session.

The most important primitive is `sendTransactions()`:

```ts
await sendTransactions([
  {
    to: tx.to,
    data: tx.data,
    value: tx.value,
  },
]);
```

This opens the host-controlled approval flow. The user approves through the host wallet, and the host submits the transaction through ERC-4337.

One important detail: `sendTransactions()` does not mean the payment has landed on-chain. It usually gives you a UserOperation submission result, not a normal transaction hash you can immediately match against a transfer.

So the MiniApp treats transaction submission as only the beginning of the verification flow.

***

### Building a Payment Intent&#x20;

When the user clicks “Buy ticket,” the frontend calls the backend:

```
POST /api/build-payment
```

The backend creates a signed payment intent:

```ts
type PaymentPayload = {
  v: 1;
  e: string;              // eventId
  t: string;              // ticketTypeId
  b: `0x${string}`;        // buyer address
  x: number;              // expiry timestamp
  n: string;              // random nonce
};
```

It serializes that payload and signs it with HMAC:

```
crc-ticket.<base64url(payload)>.<hmac>
```

This token is the payment intent.

It answers:

Who is buying what, for which event, before what expiry, with which unique nonce?

Because the backend can verify the HMAC later, it does not need to store this intent in a database.

***

### Embedding the Intent Into the CRC Transfer

The backend then builds the CRC transfer calldata.

The important trick is that the signed payment intent is embedded into the transfer metadata:

```ts
const txs = await builder.constructAdvancedTransfer(
  buyerAddress,
  organizerAddress,
  amount,
  {
    txData: paymentDataToBytes(paymentData),
  },
);
```

The backend returns these transactions to the frontend:

```ts
return txs.map((tx) => ({
  to: tx.to,
  data: tx.data,
  value: tx.value.toString(),
}));
```

Notice the `value.toString()`.

Transaction objects cross a JSON boundary, so BigInts must become strings.

The frontend then passes these transactions to the host:

```ts
await sendTransactions(txs);
```

At this point:

1. the host asks the user to approve,
2. the wallet submits the transaction,
3. the CRC transfer eventually lands on-chain,
4. the Circles indexer eventually sees the transfer metadata.

***

### Matching the Transaction Later

After submission, the frontend navigates to a ticket page:

```
/ticket/<paymentData>
```

That page polls:

```
POST /api/issue-receipt
```

The backend receives the `paymentData`, verifies the HMAC, and then searches the Circles indexer for a matching transfer.

The match is based on:

* recipient equals the organizer address
* embedded metadata contains the same `crc-ticket...` token
* token HMAC is valid
* token buyer/event/ticket fields are valid

Simplified flow:

```
paymentData from URL
        │
        ▼
verify HMAC
        │
        ▼
query Circles transfer events
        │
        ▼
find transfer containing same paymentData
        │
        ▼
issue receipt
```

This is the core database-less trick.

The on-chain transfer carries the intent, and the backend later confirms that the intent actually landed.

***

### &#x20;Handling Eventual Consistency

The app does not assume instant confirmation.

The receipt endpoint returns one of three states:

```ts
{ status: "pending" }
```

```ts
{ status: "ready", signedTicket }
```

```ts
{ status: "expired" }
```

The frontend polls with backoff.

This is important because there are multiple async layers:

```
host approval
  → ERC-4337 submission
  → inclusion
  → indexing
  → backend verification
```

A successful `sendTransactions()` call only proves that the transaction request was submitted. It does not prove that payment was indexed.

***

### Signing the Ticket Receipt

Once the backend finds the matching transfer, it signs a ticket receipt using EIP-712.

The receipt looks like this:

```ts
type TicketReceipt = {
  ticketId: string;
  eventId: string;
  ticketTypeId: string;
  buyerAddress: `0x${string}`;
  recipientAddress: `0x${string}`;
  amountCrc: string;
  paymentData: string;
  issuedAt: string;
  expiresAt: string;
};
```

The backend signs it with a dedicated App Operator EOA:

```ts
const signature = await operator.signTypedData({
  domain,
  types,
  primaryType: "TicketReceipt",
  message,
});
```

This signed receipt becomes the actual ticket.

The user can store it, show it as a QR code, or present it to a scanner.

***

### Why Use a Separate Operator Key?

The App Operator key only signs receipts.

It does not:

* hold CRC
* receive payments
* send transactions
* control organizer funds

The organizer address receives the actual payment. The operator key only attests:

“This payment was verified, and this ticket is valid.”

But you can also add a transactional backend, which for example maybe processes payouts, etc.

***

### QR Code as Portable Proof

When the ticket is ready, the frontend encodes the signed receipt:

```ts
base64url(JSON.stringify(signedTicket))
```

That encoded payload becomes the QR code.

A scanner can verify the ticket offline by checking the EIP-712 signature against the operator address.

No backend call is required at the door.

That makes the ticket portable:

```
signed receipt + operator public address = verifiable ticket
```

***

### Ideal User Journey (Example)

```
User clicks Buy
      │
      ▼
Frontend calls /api/build-payment
      │
      ▼
Backend creates HMAC-signed paymentData
      │
      ▼
Backend embeds paymentData into CRC transfer txData
      │
      ▼
Frontend calls sendTransactions()
      │
      ▼
Host wallet submits the transaction
      │
      ▼
Frontend opens /ticket/<paymentData>
      │
      ▼
Frontend polls /api/issue-receipt
      │
      ▼
Backend finds matching transfer in Circles indexer
      │
      ▼
Backend signs EIP-712 ticket receipt
      │
      ▼
Frontend renders QR ticket
```

***

For CRC Tickets, the payment is the on-chain action. The ticket is an off-chain signed receipt. The bridge between them is the signed payment intent embedded inside the transfer.


# Standalone Mini Apps

This guide provides a generic implementation pattern for Standalone Mini Apps. It focuses on the core technical building blocks required to make these flows reliable and production-safe.

{% hint style="info" %}
If you are a vibecode developer, you can simply copy the entire page using the button above and paste in your coding environment
{% endhint %}

**Standalone Mini Apps generally have the following flow:**

1. Create a short-lived intent (`intentId`, `reference`, `amount`, `recipient`, `expiresAt`).
2. Encode intent reference into transfer data.
3. Build a Gnosis App deep link URL.
4. Render QR from that URL.
5. Query transfer-data events for the recipient.
6. Decode and match event `data` against your intent.
7. Verify transfer amount from receipt logs.
8. Run your business action.

**Gnosis App Deep Link Format**

For CRC transfer flows:

```
https://app.gnosis.io/transfer/{recipientAddress}/crc?amount={amountCrc}&data={urlEncodedTransferData}
```

* `recipientAddress`: expected receiving address
* `amount`: human-readable CRC amount string
* `data`: URL-encoded transfer-data payload used for deterministic matching

Example:

```
https://app.gnosis.io/transfer/0xabc...123/crc?amount=12.5&data=0x010001000b4352432d414243313233
```

**Use the Circles SDK Transfer Data Encoder**

Use SDK utils instead of custom encoding:

```ts
import { encodeCrcV2TransferData, decodeCrcV2TransferData } from "@aboutcircles/sdk-utils";
```

{% hint style="info" %}
Encoded structure:

1. `version` (1 byte)
2. `type` (2 bytes)
3. `length` (2 bytes)
4. `payload` (N bytes)
   {% endhint %}

This gives consistent decoding and catches malformed data (version/length mismatch).

**Which Data Type to Use**

* `0x0001`: UTF-8 text payload
* `0x1001`: UTF-8 message + metadata payload

Recommended:

* Use `0x0001` when you only need a reference string.
* Use `0x1001` when you want message + structured metadata.

`0x0001` example:

```ts
const encoded = encodeCrcV2TransferData(["CRC-7F3A912B"], 0x0001);
// Example from tests: "Hi" => 0x01000100024869
```

`0x1001` example:

```ts
const encoded = encodeCrcV2TransferData(
  ["Payment for order", "ref=CRC-7F3A912B"],
  0x1001,
);
// Includes metadata separator 480a868a internally
```

**Build QR URL with Encoded Data**

```ts
import { encodeCrcV2TransferData } from "@aboutcircles/sdk-utils";

function buildQrUrl(input: {
  recipientAddress: string;
  amountCrc: string;
  reference: string;
}) {
  const encodedData = encodeCrcV2TransferData([input.reference], 0x0001);
  const dataParam = encodeURIComponent(encodedData);

  return `https://app.gnosis.io/transfer/${input.recipientAddress}/crc?amount=${input.amountCrc}&data=${dataParam}`;
}
```

Richer context example:

```ts
const encodedData = encodeCrcV2TransferData(
  ["Booth payment", `ref=${reference};intent=${intentId}`],
  0x1001,
);
```

**Detect Transactions with Transfer Data RPC Query**

Use `circles_events` scoped to the expected recipient and filtered to transfer-data events.

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "circles_events",
  "params": [
    "<recipientAddress>",
    null,
    null,
    ["CrcV2_TransferData"]
  ]
}
```

Detection:

1. Fetch events via `circles_events`.
2. Filter `to == recipientAddress`.
3. Decode each event `data`.
4. Match decoded reference to expected intent reference.
5. Verify amount and tx success via receipt logs.

Do not match on amount alone.

**Decode and Extract Reference Reliably**

```ts
import { decodeCrcV2TransferData } from "@aboutcircles/sdk-utils";

type MatchResult = { matched: boolean; reason?: string };

function extractReferenceFromDecoded(payload: unknown, type: number): string | null {
  if (type === 0x0001 && typeof payload === "string") {
    return payload.trim();
  }

  if (
    type === 0x1001 &&
    payload &&
    typeof payload === "object" &&
    "metadata" in payload &&
    typeof (payload as { metadata: string }).metadata === "string"
  ) {
    const metadata = (payload as { metadata: string }).metadata;
    const m = metadata.match(/(?:^|[;\s])ref=([^;\s]+)/i);
    return m?.[1] ?? null;
  }

  return null;
}

export function dataMatchesReference(eventData: string, expectedReference: string): MatchResult {
  try {
    const decoded = decodeCrcV2TransferData(eventData);
    const found = extractReferenceFromDecoded(decoded.payload, decoded.type);

    if (!found) return { matched: false, reason: "reference not found in decoded payload" };

    return {
      matched: found.toLowerCase() === expectedReference.trim().toLowerCase(),
      reason: found,
    };
  } catch {
    return { matched: false, reason: "decode failed" };
  }
}
```

**Verify Amount from Receipt Logs**

After reference match, load tx receipt and compute actual transferred amount.

For Circles flows, sum all relevant transfer amounts for matching `from -> to` across receipt logs, then compare:

**Idempotent Post-Confirmation Actions**

```ts
await withIntentLock(intentId, async () => {
  const current = await loadIntent(intentId);
  if (current.actionStatus === "done") return;

  await performBusinessAction(current);
  await markActionDone(intentId);
});
```

**Minimal End-to-End Example**

```ts
import { encodeCrcV2TransferData, decodeCrcV2TransferData } from "@aboutcircles/sdk-utils";

// 1) Build QR URL
const transferData = encodeCrcV2TransferData([`CRC-${intentId.slice(0, 8).toUpperCase()}`], 0x0001);
const url = `https://app.gnosis.io/transfer/${recipient}/crc?amount=${amount}&data=${encodeURIComponent(transferData)}`;

// 2) Query transfer-data events
const rpcBody = {
  jsonrpc: "2.0",
  id: 1,
  method: "circles_events",
  params: [recipient, null, null, ["CrcV2_TransferData"]],
};

// 3) Decode + match
const decoded = decodeCrcV2TransferData(event.data);
if (decoded.type !== 0x0001) return;
if (decoded.payload !== expectedReference) return;

// 4) Verify receipt amounts, then finalize
await finalizeConfirmedIntent(intentId, event.transactionHash);
```

Using `encodeCrcV2TransferData` / `decodeCrcV2TransferData` plus `circles_events` (`CrcV2_TransferData`) makes QR detection deterministic and keeps transactions properly annotated in Gnosis App for better UX.\
\
Now that the payment/user action is successfully decoded, you can proceed with your app's own business logic, be it a payout for winning a game, processing refund for a store, etc.

{% hint style="info" %}
Here are some examples you can use as a base of your own app:

* <https://github.com/aboutcircles/circles-gnosisApp-starter-kit>
* <https://github.com/aboutcircles/merch-shop-miniapp>
  {% endhint %}


# Simple Vibecoding Tutorial

{% embed url="<https://www.loom.com/share/6bcfb38138054053b8dfe3c5607bd451>" %}

<p align="center">Watch this short video on how you can quickly vibecode QR based miniapps.</p>

<br>


# Create or Connect a Circles Account from a Mini App

Use `@aboutcircles/miniapp-sdk` to let users create a new Circles account or connect an existing one without leaving your mini app.

This guide explains the recommended integration pattern for mini apps embedded inside the Circles host. The examples work as a foundation for vanilla JavaScript, React, Vue, Svelte, and other browser-based frameworks.

***

### Overview

A Circles mini app runs inside an `<iframe>` on its own origin. The surrounding **Circles host** owns the wallet experience and handles account creation, passkeys, Safe setup, and on-chain Circles registration.

Your mini app should not attempt to create or manage the account directly. It only needs to:

1. Ask the host to start the account flow with `requestCreateAccount()`.
2. Subscribe to wallet state changes with `onWalletChange()`.
3. Render the correct UI for connected, disconnected, and standalone states.

```
Mini app iframe                         Circles host
      │                                      │
      │  requestCreateAccount()              │
      ├─────────────────────────────────────▶│
      │                                      │  Opens passkey and account flow
      │                                      │
      │  onWalletChange(address)             │
      │◀─────────────────────────────────────┤
      │                                      │
```

The SDK wraps the cross-origin messaging protocol for you. In the normal integration path, your app does not need to call `window.parent.postMessage()` or manage browser message listeners.

When the flow succeeds, the returned `address` represents a registered Circles human account. You can use it immediately to read profile data, request transaction signing, or request message signing.

***

### Prerequisites

Before integrating the SDK, make sure you have:

* A browser-based application that can be served over HTTP or HTTPS.
* A Circles host environment that embeds your application by URL.
* A visible button or another direct user action that can start the sign-up flow.

The account flow opens a WebAuthn passkey prompt. Browsers require this prompt to originate from a real user gesture, such as a button click.

***

### Install the SDK

Choose the command that matches your package manager:

```bash
npm install @aboutcircles/miniapp-sdk
```

```bash
pnpm add @aboutcircles/miniapp-sdk
```

```bash
yarn add @aboutcircles/miniapp-sdk
```

Import the account-related SDK functions:

```ts
import {
  isMiniappMode,
  onWalletChange,
  requestCreateAccount,
} from '@aboutcircles/miniapp-sdk';
```

***

### Core API Concepts

#### `requestCreateAccount()`

```ts
requestCreateAccount(): Promise<{
  authenticated: boolean;
  address: string;
}>
```

Call this function when a user clicks your sign-up or connect button.

The host determines which experience the user needs:

* If the user is already connected, the promise resolves with their address.
* If the user is not connected, the host opens the account creation or login flow.
* If the user cancels the flow or the request fails, the promise rejects.

Always handle the rejection with `try/catch` so you can restore the UI cleanly.

#### `onWalletChange()`

```ts
onWalletChange(
  callback: (address: string | null) => void,
): () => void
```

Use this function as the source of truth for authentication state.

The callback runs immediately with the current state and runs again whenever the wallet state changes. The value is:

* A wallet address when the user is connected.
* `null` when the user is not connected or before the host returns a connected account.

The function returns an unsubscribe callback. Use it during component cleanup in framework-based applications.

#### `isMiniappMode()`

```ts
isMiniappMode(): boolean
```

Use this function to detect whether your app is running inside the Circles host.

If the app is opened as a standalone webpage, there is no host available to answer SDK requests. In that state, show an explanatory message instead of an inactive sign-up button.

***

### Recommended Integration Pattern

Treat wallet state as reactive application state:

1. Subscribe to `onWalletChange()` when the app initializes.
2. Render your connected or disconnected interface from the callback value.
3. Call `requestCreateAccount()` directly from a user click.
4. Use the returned promise only for immediate button state and error handling.
5. Continue to use `onWalletChange()` as the authoritative state source.

This matters because wallet state can change independently of the current click handler, including reconnects and changes initiated elsewhere.

***

### Minimal Vanilla JavaScript Example

The following example contains the complete account integration for a simple browser app.

```html
<div id="status">Checking account status…</div>
<button id="signup" type="button" hidden>
  Create or connect Circles account
</button>

<script type="module">
  import {
    isMiniappMode,
    onWalletChange,
    requestCreateAccount,
  } from '@aboutcircles/miniapp-sdk';

  const statusElement = document.getElementById('status');
  const signupButton = document.getElementById('signup');

  function renderWalletState(address) {
    if (address) {
      statusElement.textContent = `Connected: ${address}`;
      signupButton.hidden = true;
      return;
    }

    statusElement.textContent = 'No Circles account connected.';
    signupButton.hidden = false;
  }

  if (!isMiniappMode()) {
    statusElement.textContent = 'Open this mini app inside the Circles host.';
    signupButton.hidden = true;
  } else {
    const unsubscribe = onWalletChange(renderWalletState);

    signupButton.addEventListener('click', async () => {
      signupButton.disabled = true;

      try {
        const { address } = await requestCreateAccount();
        console.log('Circles account ready:', address);
      } catch (error) {
        const message =
          error instanceof Error ? error.message : 'Unknown account error';
        console.warn('Account flow cancelled or failed:', message);
      } finally {
        signupButton.disabled = false;
      }
    });

    // Call unsubscribe() if your application later removes this view.
  }
</script>
```

#### Why this works

* The button calls `requestCreateAccount()` directly from the click handler.
* `onWalletChange()` controls the rendered wallet state.
* Standalone mode is handled explicitly.
* The button is disabled while the host flow is active, preventing duplicate requests.

***

### React Integration

A reusable hook keeps the host integration isolated from the rest of your UI.

```tsx
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
  isMiniappMode,
  onWalletChange,
  requestCreateAccount,
} from '@aboutcircles/miniapp-sdk';

type UseCirclesAccountResult = {
  address: string | null;
  inHost: boolean;
  isConnecting: boolean;
  error: string | null;
  createOrConnectAccount: () => Promise<string | null>;
};

export function useCirclesAccount(): UseCirclesAccountResult {
  const inHost = useMemo(() => isMiniappMode(), []);
  const [address, setAddress] = useState<string | null>(null);
  const [isConnecting, setIsConnecting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!inHost) return;
    return onWalletChange(setAddress);
  }, [inHost]);

  const createOrConnectAccount = useCallback(async () => {
    if (!inHost) {
      setError('Open this mini app inside the Circles host.');
      return null;
    }

    setIsConnecting(true);
    setError(null);

    try {
      const result = await requestCreateAccount();
      return result.address;
    } catch (unknownError) {
      const message =
        unknownError instanceof Error
          ? unknownError.message
          : 'Account creation or connection failed.';

      setError(message);
      return null;
    } finally {
      setIsConnecting(false);
    }
  }, [inHost]);

  return {
    address,
    inHost,
    isConnecting,
    error,
    createOrConnectAccount,
  };
}
```

Use the hook in a component:

```tsx
import { useCirclesAccount } from './useCirclesAccount';

export function CirclesAccountButton() {
  const {
    address,
    inHost,
    isConnecting,
    error,
    createOrConnectAccount,
  } = useCirclesAccount();

  if (!inHost) {
    return <p>Open this mini app inside the Circles host.</p>;
  }

  if (address) {
    return <p>Connected: {address}</p>;
  }

  return (
    <div>
      <button
        type="button"
        disabled={isConnecting}
        onClick={createOrConnectAccount}
      >
        {isConnecting ? 'Connecting…' : 'Create or connect Circles account'}
      </button>

      {error ? <p role="alert">{error}</p> : null}
    </div>
  );
}
```

The click handler calls the SDK action directly, preserving the browser user gesture required by the passkey flow.

***

### Important Rules and Common Pitfalls

| Rule                                                                  | Reason                                                                                                                                                      |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Call `requestCreateAccount()` directly from a user gesture.           | The host opens a WebAuthn passkey prompt. Browsers can block the prompt when the request is triggered on page load or after an unrelated asynchronous step. |
| Use `onWalletChange()` as the source of truth.                        | The listener updates after sign-up and whenever wallet state changes later. The result of one promise should not become your only account state.            |
| Check `isMiniappMode()` before showing an interactive account button. | SDK requests cannot complete when the app is opened outside the host iframe.                                                                                |
| Do not send attribution data from the mini app.                       | The host derives sign-up attribution from the iframe origin. There is no attribution parameter for the mini app to provide.                                 |
| Handle cancelled flows.                                               | A user can dismiss or reject the host flow. Keep the button usable and show a non-blocking error message.                                                   |

#### Avoid breaking the user gesture

Start the host request immediately inside the click handler:

```ts
button.addEventListener('click', async () => {
  await requestCreateAccount();
});
```

Avoid unrelated asynchronous work before calling the SDK:

```ts
button.addEventListener('click', async () => {
  await fetch('/api/prepare-signup'); // Avoid doing this first.
  await requestCreateAccount();
});
```

***

### Use the Connected Account

After the account flow completes, the user has a registered Circles account. The following examples show common next steps.

#### Read a Circles profile

Profile data can be read from the Circles RPC endpoint:

```ts
const CIRCLES_RPC_URL = 'https://rpc.aboutcircles.com/';

type CirclesProfile = {
  name?: string;
  previewImageUrl?: string;
  [key: string]: unknown;
};

export async function fetchCirclesProfile(
  address: string,
): Promise<CirclesProfile | null> {
  const response = await fetch(CIRCLES_RPC_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'circles_getProfileByAddress',
      params: [address],
    }),
  });

  if (!response.ok) {
    throw new Error(`Circles RPC request failed: ${response.status}`);
  }

  const payload = (await response.json()) as {
    result?: CirclesProfile | null;
    error?: { message?: string };
  };

  if (payload.error) {
    throw new Error(payload.error.message ?? 'Circles RPC returned an error.');
  }

  return payload.result ?? null;
}
```

A newly created account may not have a profile yet. In that case, `circles_getProfileByAddress` can return `null` even though the account is registered and usable.

#### Request transaction signing

Use `sendTransactions()` to ask the host to sign and submit one or more transactions with the user's Safe:

```ts
import { sendTransactions } from '@aboutcircles/miniapp-sdk';

const transactionHashes = await sendTransactions([
  {
    to: '0xRecipient',
    value: '0',
    data: '0x...',
  },
]);

console.log(transactionHashes);
```

The promise resolves with an array of transaction hashes and rejects if the user declines the request.

#### Request message signing

Use `signMessage()` when your app needs a user signature:

```ts
import { signMessage } from '@aboutcircles/miniapp-sdk';

const result = await signMessage('hello world');

console.log(result.signature);
console.log(result.verified);
```

The default signature type is `'erc1271'`, which uses EIP-191 prefix hashing. Pass `'raw'` only when your verifier expects raw UTF-8 byte signing:

```ts
const result = await signMessage('hello world', 'raw');
```

The two signature modes are not interchangeable. Match the SDK option to the verification logic in your application.

***

### Test the Integration Inside the Host

Serve your mini app locally:

```bash
npm run dev
```

For the repository demo, the local URL is:

```
http://localhost:5190
```

Open the development host playground and pass your local mini app URL:

```
https://circles-dev.gnosis.io/playground?url=http://localhost:5190
```

The playground embeds your app inside an iframe. Use the account button to run the real host-managed passkey and invitation flow.

#### Local testing checklist

Before debugging application logic, confirm that:

* Your app is loaded through the host playground rather than opened directly.
* The iframe URL matches your local development server.
* The account action starts from a button click.
* `isMiniappMode()` returns `true` inside the embedded app.
* Your `onWalletChange()` listener is registered when the app initializes.

***

### SDK API Reference

The following API snapshot corresponds to `@aboutcircles/miniapp-sdk@0.1.44`.

| Export                 | Signature                                                                                                    | Purpose                                                                                                |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `isMiniappMode`        | `() => boolean`                                                                                              | Returns `true` when the app is running inside the host iframe.                                         |
| `onWalletChange`       | `(callback: (address: string \| null) => void) => () => void`                                                | Emits the current wallet state immediately and emits future changes. Returns an unsubscribe callback.  |
| `requestCreateAccount` | `() => Promise<{ authenticated: boolean; address: string }>`                                                 | Opens the host-managed account creation or login flow. Call it directly from a user gesture.           |
| `sendTransactions`     | `(transactions: Transaction[]) => Promise<string[]>`                                                         | Requests host signing and submission of transactions with the user's Safe. Returns transaction hashes. |
| `signMessage`          | `(message: string, signatureType?: 'erc1271' \| 'raw') => Promise<{ signature: string; verified: boolean }>` | Requests a host-managed signature. Defaults to `'erc1271'`.                                            |
| `onAppData`            | `(callback: (data: string) => void) => void`                                                                 | Receives app-specific data passed by the host through a `?data=` parameter.                            |

#### Type definitions

```ts
interface Transaction {
  to: string;
  data?: string;
  value?: string;
}

interface AuthResult {
  authenticated: boolean;
  address: string;
}

interface SignResult {
  signature: string;
  verified: boolean;
}

type SignatureType = 'erc1271' | 'raw';
```

***

### Troubleshooting

| Symptom                                                         | Likely cause                                                                                                         | Resolution                                                                                                       |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| The passkey prompt does not open or is blocked.                 | `requestCreateAccount()` was not called directly from a user gesture.                                                | Move the SDK call into the button click handler. Avoid unrelated `await` calls before it.                        |
| The sign-up button does nothing.                                | The app was opened as a standalone page, so no host can answer the SDK request.                                      | Load the app through the host playground and check `isMiniappMode()`.                                            |
| `onWalletChange()` remains `null`.                              | The host has not returned a connected wallet state, the app is not embedded correctly, or the host URL is incorrect. | Verify the playground URL, confirm iframe embedding, and render an appropriate disconnected state while waiting. |
| The sign-up request succeeds but profile lookup returns `null`. | Newly registered accounts may not have profile metadata yet.                                                         | Treat the account as usable and render an empty-profile state.                                                   |
| Users can start duplicate requests.                             | The connect button remains enabled during the host flow.                                                             | Disable the button until the promise resolves or rejects.                                                        |

***

### Advanced: Raw `postMessage` Protocol

Prefer the SDK for application code. It centralizes the host protocol and reduces the amount of browser messaging logic you need to maintain.

For debugging or protocol-level understanding, the account exchange uses a request identifier so that the mini app can match the response to the original request:

```
Mini app → host
{ type: 'request_create_account', requestId }

Host → mini app
{ type: 'auth_success', address, requestId }

or

{ type: 'auth_rejected', reason, requestId }
```

A minimal illustrative implementation looks like this:

```ts
function createAccountWithPostMessage(): Promise<string> {
  return new Promise((resolve, reject) => {
    const requestId = `req_${Math.random().toString(36).slice(2)}`;

    function cleanup() {
      window.removeEventListener('message', onMessage);
    }

    function onMessage(event: MessageEvent) {
      const data = event.data;

      if (!data || data.requestId !== requestId) return;

      if (data.type === 'auth_success') {
        cleanup();
        resolve(data.address);
      }

      if (data.type === 'auth_rejected') {
        cleanup();
        reject(new Error(data.reason));
      }
    }

    window.addEventListener('message', onMessage);

    window.parent.postMessage(
      {
        type: 'request_create_account',
        requestId,
      },
      '*',
    );
  });
}
```

This example is intentionally minimal. For production code, use the SDK rather than reimplementing the protocol.


# Contribute Mini Apps

{% embed url="<https://github.com/aboutcircles/CirclesMiniapps>" %}

{% hint style="info" %}
There are two ways to contribute Mini Apps to Circles:

* **Garage Mini Apps** — externally hosted experimental, community, and hackathon apps
* **Embedded Mini Apps** — curated production-grade apps fully shipped from this repository
  {% endhint %}

***

## Circles Mini App Host

Circles Mini Apps are hosted by a SvelteKit application that renders apps inside iframes at:

```txt
https://<VITE_BASE_URL>/miniapps
```

Mini apps communicate with the host through a `postMessage` wallet bridge for:

* Wallet connection
* Transaction requests
* Message signing
* App metadata

The marketplace reads app metadata from:

```txt
static/miniapps.json
```

When a user opens:

```txt
/miniapps/<slug>
```

the host loads the corresponding app URL inside an iframe.

***

## Choose Your Submission Type

| Type              | Best For                                                 | Hosting                          | Review Level       |
| ----------------- | -------------------------------------------------------- | -------------------------------- | ------------------ |
| Garage Mini App   | Experimental apps, hackathons, demos, community projects | External hosting allowed         | Lightweight review |
| Embedded Mini App | Curated production apps                                  | Must live inside this repository | Full review        |

***

## Garage Mini Apps

Garage Mini Apps are intended for:

* Hackathon projects
* Experimental apps
* Community tools
* Early-stage prototypes
* Circles Garage competition entries

Garage apps:

* Can be hosted anywhere
* Only require a manifest PR
* Run under a stricter transaction policy
* Show a "use at your own risk" disclaimer

Examples of allowed hosting:

* Vercel
* Netlify
* GitHub Pages
* Your own domain

### What a Garage PR should contain

Garage submissions only require a metadata entry.

The app itself remains in your own repository and deployment.

Your PR should contain:

* A new entry in `static/miniapps.json`
* Optionally a logo under `static/app-logos/`
* A deployed HTTPS URL that already works inside an iframe

Open the PR against:

```txt
aboutcircles/CirclesMiniapps
```

branch:

```txt
master
```

***

## Embedded Mini Apps

Embedded Mini Apps are curated production-grade apps that ship directly from this repository.

This path is intended for apps that:

* Should become part of the official Mini Apps experience
* Need deeper review and maintainability
* Are intended to be maintained long term inside this repository

For Embedded Mini Apps:

* The full source code must be included in the PR
* The app must live inside this repository
* External-host-only submissions are not accepted

***

## Embedded Mini App structure

Recommended layout:

```txt
src/routes/apps/<slug>/+page.svelte
src/routes/apps/<slug>/components/...
static/app-logos/<slug>.png
static/apps/<slug>/...
```

Recommended routing pattern:

| Route              | Purpose                   |
| ------------------ | ------------------------- |
| `/miniapps/<slug>` | Marketplace wrapper route |
| `/apps/<slug>`     | Actual embedded app route |

Example manifest entry:

```json
{
  "slug": "my-app",
  "name": "My App",
  "logo": "/app-logos/my-app.png",
  "url": "/apps/my-app",
  "description": "Short description of the app.",
  "tags": ["payments", "tools"],
  "category": "miniapp"
}
```

***

## Manifest Format

Both Garage and Embedded Mini Apps are registered in:

```txt
static/miniapps.json
```

### Required fields

| Field         | Required | Notes                        |
| ------------- | -------: | ---------------------------- |
| `slug`        |      yes | URL-safe unique identifier   |
| `name`        |      yes | Display name                 |
| `logo`        |      yes | HTTPS URL or local repo path |
| `url`         |      yes | App URL                      |
| `description` |      yes | Short app description        |
| `tags`        |      yes | Discovery tags               |
| `category`    |      yes | `"garage"` or `"miniapp"`    |
| `isHidden`    |       no | Hide from marketplace grid   |

***

## Example Garage Entry

```json
{
  "slug": "your-app-slug",
  "name": "Your App",
  "logo": "/app-logos/your-app.png",
  "url": "https://your-app.example.com/",
  "description": "One-sentence description.",
  "tags": ["demo", "tools"],
  "category": "garage"
}
```

***

## Example Embedded Entry

```json
{
  "slug": "embedded-app",
  "name": "Embedded App",
  "logo": "/app-logos/embedded-app.png",
  "url": "/apps/embedded-app",
  "description": "Embedded app description.",
  "tags": ["payments"],
  "category": "miniapp"
}
```

***

## Host Integration

Mini apps communicate with the host using `postMessage`.

Mini app → host:

* `request_address`
* `send_transactions`
* `sign_message`

Host → mini app:

* `wallet_connected`
* `wallet_disconnected`
* `app_data`
* `tx_success`
* `tx_rejected`
* `sign_success`
* `sign_rejected`

Current implementation lives in:

```txt
src/routes/miniapps/[slug]/+page.svelte
src/routes/playground/+page.svelte
```

Build against the actual implementation, not assumptions.

***

## Garage Transaction Policy

Garage apps run under a stricter transaction policy.

Before approval, the host rejects any transaction batch that:

* Targets the user's currently acting Safe
* Targets the user's primary Safe while in child-safe mode
* Uses restricted Safe-management selectors

Restricted selectors:

| Selector     | Function                                 |
| ------------ | ---------------------------------------- |
| `0x0d582f13` | `addOwnerWithThreshold(address,uint256)` |
| `0xf8dc5dd9` | `removeOwner(address,address,uint256)`   |
| `0xe318b52b` | `swapOwner(address,address,address)`     |
| `0x694e80c3` | `changeThreshold(uint256)`               |
| `0xe19a9dd9` | `setGuard(address)`                      |
| `0xf08a0323` | `setFallbackHandler(address)`            |
| `0x610b5925` | `enableModule(address)`                  |
| `0xe009cfde` | `disableModule(address,address)`         |
| `0x6a761202` | `execTransaction(...)`                   |

The entire batch is rejected on the first violation.

The user sees a restricted-action modal and the iframe receives:

```txt
tx_rejected
```

### Garage apps cannot

* Call `execTransaction`
* Add/remove owners
* Change Safe configuration
* Enable modules
* Modify guards
* Wrap their own Safe execution flow

If your app requires these permissions, it should be submitted as an Embedded Mini App instead.

***

## Local Development

Before opening a PR, run:

```sh
npm install
npm run check
npm run build
```

Then verify manually:

1. Open `/miniapps`
2. Confirm the app appears
3. Open `/miniapps/<slug>`
4. Confirm the iframe loads correctly
5. Test the main app flow
6. Test wallet/signing flows if applicable

***

## PR Expectations

All PRs should include:

* App metadata in `static/miniapps.json`
* Working routes and URLs
* Logo/assets
* Clear PR description
* Testing notes

Embedded Mini Apps must additionally include:

* Full application source code
* Any required dependencies
* Local assets and routes

Recommended PR titles:

Garage:

```txt
feat: add <app-name> garage app
```

Embedded:

```txt
feat: add <app-name> embedded miniapp
```

***

## Review Criteria

Maintainers will review:

* Whether the app works inside the iframe host
* Wallet/signing safety
* Build stability
* Asset quality
* Maintainability
* Transaction behavior
* Whether the correct submission path was used

***

## Common Rejection Reasons

Garage submissions may be rejected if:

* The app does not load in an iframe
* The URL is broken
* Restricted Safe-management actions are attempted
* The manifest is incomplete

Embedded submissions may be rejected if:

* The PR only links to an external deployment
* Core logic still lives outside this repository
* Local routes/assets are missing
* The app fails build or check
* The iframe integration is broken

***

## Minimal Checklist

### Garage Mini App

* [ ] Added entry to `static/miniapps.json`
* [ ] `category` is `"garage"`
* [ ] App loads over HTTPS
* [ ] App works inside iframe
* [ ] Logo resolves correctly
* [ ] `slug` is unique
* [ ] No restricted Safe-management calls

### Embedded Mini App

* [ ] App source added under `src/routes/apps/<slug>/`
* [ ] Metadata added to `static/miniapps.json`
* [ ] `category` is `"miniapp"`
* [ ] Local logo/assets committed
* [ ] `/miniapps/<slug>` works
* [ ] `npm run check` passes
* [ ] `npm run build` passes

If you want maintainers to merge and host your app directly as part of the Circles Mini Apps platform, submit it as an Embedded Mini App.


# Circles Mini Apps Launchpad

A rolling developer incentive program for builders creating useful, production-ready Mini Apps for the Circles ecosystem.

The Circles MiniApps Launchpad is designed to encourage practical applications that help people do meaningful things with Circles. Selected projects will be highlighted through recurring recognition cycles such as **App of the Week**, **Winner of the Month**, and themed bounty rounds.

The goal is simple: expand the Circles MiniApps ecosystem with apps that are useful, testable, and relevant to real users and communities.

MiniApps can focus on areas such as payments, community coordination, local commerce, events, group use cases, public goods, mutual aid, games, tools, and experimental social-money experiences.

***

### Who Can Participate?

The program is open to all kinds of builders. You do not need to be a protocol expert to participate.

#### Beginner Builders

If you are new to development or new to Web3, you are welcome to participate. You can build simple Circles-enabled apps using starter kits, payment links, QR flows, and basic on-chain confirmation.

Use of AI tools is allowed and encouraged, especially for participants with little or no prior coding experience.

#### Web Developers

Frontend and full-stack developers can build polished MiniApps using React, Svelte, Next.js, or similar frameworks. Apps with a strong user experience, clean interface, and practical flows are encouraged.

#### Web3 Developers

Builders familiar with wallets, smart contracts, Gnosis Chain, indexers, RPC APIs, and event-driven app logic can create deeper integrations with the Circles ecosystem.

#### Community Builders

People building for local groups, cooperatives, events, mutual-aid networks, merchants, communities, or Circles groups are encouraged to submit practical tools that solve real coordination problems.

#### Designers and Product Builders

You do not need to write deep protocol code to participate. Useful user journeys, onboarding flows, payment experiences, community tools, and product prototypes are all welcome.

***

### Program Format

The Circles MiniApps Builder Program is a rolling bounty program with recurring recognition and rewards.

#### App of the Week ( $500 )

Every week, we will select one standout submission as **App of the Week**.

The selected app will be highlighted publicly and receive a $500 reward.

Weekly winners may include simple but useful apps, strong prototypes, polished user experiences, or creative uses of Circles MiniApps.\
\
Submissions upto Sunday 12:00 GMT will be considered for that week.

#### Winner of the Month ( $1,000 )

Every month, we will select the strongest app submitted or meaningfully improved during that month.

The monthly winner will receive a $1,000 reward.

For the monthly prize, the evaluation will consider not only the app itself but also its traction, usefulness, audience, and potential impact in the Circles ecosystem.\
\
Submissions upto last day of the month 12:00 GMT will be considered for that month.

{% hint style="info" %}
We encourage all participants to join the [Circles Builder Telegram](https://t.me/about_circles/499).
{% endhint %}

***

### What You Can Build

Participants are encouraged to build apps that help people use Circles in practical and meaningful ways.

Example app ideas include:

* Payment tools for merchants, events, or communities
* QR-based payment experiences
* Local commerce and marketplace tools
* Community coordination apps
* Group treasury or group payment tools
* Event check-in or ticketing flows
* Public goods and donation tools
* Mutual-aid coordination tools
* Games or social-money experiments
* Tools for Circles groups and organizations
* Dashboards, explorers, or analytics tools
* Onboarding flows that make Circles easier to understand and use

These are only examples. Builders are encouraged to experiment and submit new ideas.

***

### Eligibility

* Anyone can participate.
* Teams are allowed.
* All submissions must be publicly hosted on GitHub and released under an open-source license.
* Existing apps can participate if they add meaningful new Circles MiniApp functionality.

***

### Submission Requirements

To be eligible, your app must follow the rules below:

* The app must include a real Circles-related feature.
* The app must be testable by reviewers.
* The app must not mislead users about payments, recipients, or transaction outcomes.
* The app must not ask users for seed phrases or private keys.
* The app must clearly show the payment amount and recipient before sending users into a wallet flow.
* The app should avoid collecting unnecessary personal data.
* The app must be publicly available on GitHub with an open-source license.

Submissions should include enough information for reviewers to understand, run, and test the app.

A strong submission should include:

* A short description of the app with a clear explanation of how it uses Circles
* A link to the GitHub repository
* A live demo link, if available
* Screenshots or a short demo video (highly recommended)

***

### Repeat Participation

Builders can submit multiple apps over time.

You can also resubmit meaningful improvements to an existing app.

A previous weekly winner can still be eligible for a monthly or future prize if the app improves meaningfully, gains traction, or adds important new functionality.

The program is designed to reward continuous building, iteration, and real usage.

***

### How to Submit?

To submit you mini app, fill this form with all the details - [Submission Form](https://forms.gle/4Zw77qz1oAzgjVnv9)

Each submission should include the app details, GitHub repository, demo link if available, and a short explanation of how the app uses Circles.

If you are building an embedded-style MiniApp, you should also contribute to the [Circles MiniApps repository](https://github.com/aboutcircles/CirclesMiniapps) and add your app through a pull request. <br>

***

### Resources & Examples

Use the following resources to get started:

* Circles MiniApps documentation: <https://docs.aboutcircles.com/miniapps>
* Circles MiniApps GitHub repository: <https://github.com/aboutcircles/CirclesMiniapps>
* Circles SDK MCP - [https://context7.com/aboutcircles/sdk](https://context7.com/aboutcircles/sdk/)&#x20;

Embedded Mini Apps

* <https://circles.gnosis.io/miniapps>

Standalone Mini Apps

* [https://miniapps.aboutcircles.com](https://miniapps.aboutcircles.com/)

{% hint style="info" %}
If you need any technical help or need any help, feel free to reach out to us on [Discord](https://discord.gg/BcffB9NhM) or on [Circles builder TG](https://t.me/about_circles/499).
{% endhint %}

***

### What We Are Looking For

The best submissions will be useful, understandable, and easy to test.

Production-ready does not mean the app has to be complex. A small app that solves a real problem clearly is often better than a large app that is difficult to use.

Reviewers will look for:

* Practical usefulness & Potential for real-world usage
* Clear Circles integration
* Good user experience
* Safety and transparency around payments
* Open-source availability
* Ease of testing
* Community relevance
* Meaningful iteration over time

***

This program is for anyone who wants to explore what people can do with social money, community payments, and local coordination tools.

Whether you are building your first MiniApp, experimenting with a new idea, or creating a production-ready tool for a real community, you are welcome to participate.

Build something useful. Submit it. Improve it. Help grow the Circles Mini Apps ecosystem.


# Agentic Setup for Circles

Build Circles dApps and miniapps using AI coding assistants grounded in always-fresh, version-aware Circles documentation.

This page is a practical guide for developers (and curious non-developers) who want to **vibe-code** on Circles using an AI assistant like Claude, Cursor, Windsurf, or VS Code Copilot. By wiring [Context7](https://context7.com/) into your editor, your AI agent pulls live snippets from the official Circles docs and SDK source, so it stops hallucinating outdated APIs and starts writing code that actually compiles.

***

### What is an agentic workflow?

An *agentic workflow* is one where you describe what you want in plain language and an AI agent does the work — reading docs, writing code, running it, fixing its own errors, and iterating. The catch: large language models are trained on data that's months or years old. Without a grounding layer, an agent confidently writes code that imports packages that don't exist or calls methods that were renamed in v2.

**Context7** fixes this. It's an open Model Context Protocol (MCP) server from Upstash that injects up-to-date, version-specific documentation directly into your AI assistant's context window the moment you ask about a library. The Circles team publishes two libraries on Context7:

* [**`aboutcircles/circles-docs`**](https://context7.com/aboutcircles/circles-docs) — the full developer documentation, including protocol concepts, configuration, and end-to-end examples.
* [**`aboutcircles/sdk`**](https://context7.com/aboutcircles/sdk) — the TypeScript SDK reference, including every package (`@aboutcircles/sdk`, `sdk-core`, `sdk-rpc`, `sdk-runner`, `sdk-pathfinder`, `sdk-profiles`, `sdk-transfers`) with code examples for almost every method.

Together, they give your agent everything it needs to build production-grade Circles apps without you having to copy-paste from docs.

***

### Why Context7 + Circles

Without Context7, ask Claude to "trust an address with the Circles SDK" and you'll get pre-2024 examples using `@circles-sdk/sdk` and ethers v6. With Context7, the same prompt produces the current `@aboutcircles/sdk` flow on Gnosis Chain (chainId 100) with viem, a `ContractRunner`, and the right `circlesConfig[100]`. The difference is hours of debugging vs. shipping.

The Circles SDK has a layered architecture — `Sdk` for high-level avatar work, `CirclesRpc` for read-only queries, `Core` for raw contract calls, `SafeContractRunner` / `SafeBrowserRunner` for Safe-based transactions, and a `Pathfinder` for multi-hop trust transfers. An AI assistant with the right docs in context can navigate all of these for you.

***

### Prerequisites

Before you start, make sure you have:

* **Node.js 18+** installed (`node --version`)
* An **MCP-compatible AI assistant**: Claude Desktop, Claude Code, Cursor, Windsurf, or VS Code with an MCP extension

{% hint style="info" %}
*Optional (but recommended):* A free **Context7 API key** from [context7.com/dashboard](https://context7.com/dashboard) for higher rate limits
{% endhint %}

***

### Step 1: Install Context7 in your AI assistant

Pick the section that matches your editor. The configuration is essentially the same JSON across all clients — Context7 runs as a local MCP server via `npx`.

<details>

<summary>Cursor</summary>

* Open Cursor → **Settings** → **Cursor Settings** → **MCP** → **Add new global MCP server**.
* Or edit the config file directly:
  * macOS / Linux: `~/.cursor/mcp.json`
  * Windows: `%USERPROFILE%\.cursor\mcp.json`

1. Add the following:

```json
{
  "mcpServers": {
    "context7": {
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp@latest"],
      "env": {
        "CONTEXT7_API_KEY": "your-key-here-optional"
      }
    }
  }
}
```

4. Restart Cursor.

</details>

<details>

<summary>Claude Desktop</summary>

* Open your config file:
  * macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
  * Windows: `%APPDATA%\Claude\claude_desktop_config.json`
* Add the same `mcpServers` block as above.

1. Quit and reopen Claude Desktop.

</details>

<details>

<summary>Claude Code</summary>

Run a single command in your terminal:

```bash
claude mcp add --scope user context7 -- npx -y @upstash/context7-mcp@latest --api-key YOUR_KEY
```

The `--scope user` flag registers Context7 globally so it's available in every Claude Code session. Verify with `/mcp` inside a session — you should see `context7` with a *connected* status.

</details>

<details>

<summary>VS Code</summary>

```json
"mcp.servers": {
  "context7": {
    "command": "npx",
    "args": ["-y", "@upstash/context7-mcp@latest"]
  }
}
```

Add Context7 under `mcp.servers` in your `settings.json`:

Then reload the window (Ctrl/Cmd+Shift+P → "Developer: Reload Window").

</details>

#### Verify it's working

In any of the above clients, send a prompt like:

> *Using the latest @aboutcircles/sdk, write me a TypeScript snippet that registers a new human avatar on Gnosis Chain. use context7*

If Context7 is wired up correctly, the response will include the current packages (`@aboutcircles/sdk`, `@aboutcircles/sdk-core`, `@aboutcircles/sdk-runner`, viem) and use `circlesConfig[100]` and not the deprecated `@circles-sdk/sdk` packages.

***

### Step 2: Connect the Circles libraries

Context7 auto-discovers libraries when you reference them in a prompt, but for the most reliable results, point your agent directly at the Circles entries by name or ID.

#### Library IDs

| Library               | Context7 ID                 | What's inside                                                                                                                                                                   |
| --------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Circles documentation | `aboutcircles/circles-docs` | Protocol overview, getting-started guides, every SDK method with runnable TypeScript examples, RPC endpoints, REST API for profiles                                             |
| Circles SDK v2        | `aboutcircles/sdk`          | Per-package API reference: `Sdk`, `HumanAvatar`, `OrganisationAvatar`, `BaseGroupAvatar`, `CirclesRpc`, `Core`, `SafeContractRunner`, `Pathfinder`, `Profiles`, utility helpers |

#### Pin a library in a prompt

You can reference either entry with the slash syntax to make sure the agent looks at the right one:

```
Show me how to use sdk.register.asGroup to create a BaseGroup.
use library /aboutcircles/sdk
```

Or, for documentation-level questions like configuration and protocol concepts:

```
Explain how the Circles trust graph works and how pathfinding routes payments.
use library /aboutcircles/circles-docs
```

***

### Step 3: Add a project rule so you don't have to remember

Typing `use context7` every time gets old fast. Instead, add a one-time rule so your assistant **automatically** fetches Circles docs whenever it sees a relevant import or keyword.

#### For Cursor — `.cursorrules`

Create a `.cursorrules` file at the root of your project:

```
You are building on the Circles protocol (Gnosis Chain, chainId 100).

Whenever I ask about anything involving Circles, CRC, trust graphs, avatars,
pathfinding, group minting, or any package starting with @aboutcircles/,
automatically use the Context7 MCP server to fetch the latest docs from
the aboutcircles/sdk and aboutcircles/circles-docs libraries before writing code.

Never use the deprecated @circles-sdk/* packages. Always prefer:
  - @aboutcircles/sdk for high-level avatar workflows
  - @aboutcircles/sdk-rpc for read-only data queries
  - @aboutcircles/sdk-core for raw contract calls
  - @aboutcircles/sdk-runner for Safe transaction execution
  - viem (not ethers) as the underlying client

Default to circlesConfig[100] from @aboutcircles/sdk-core for all Gnosis Chain config.
All token amounts are BigInt in atto-CRC (10^18 per CRC).
```

#### For Claude Code — `CLAUDE.md`

Drop the same content into a `CLAUDE.md` at your project root. Claude Code reads it automatically.

***

### Step 4: Vibe-code your first Circles Mini App

A **Circles miniapp** is a lightweight, focused dApp built with the Circles SDK, usually a single-page web app that reads/writes the Circles graph, mints or transfers CRC, manages a group, or exposes a small piece of functionality that complements Gnosis Wallet.&#x20;

Here's a starter prompt you can give any Context7-equipped agent. It assumes the rules from Step 3 are already in place:

> Build a Vite + React + TypeScript miniapp that:
>
> 1. Connects a Safe via `SafeBrowserRunner` from `@aboutcircles/sdk-runner`
> 2. Shows the connected avatar's profile, total CRC balance, and a paginated trust list
> 3. Has a "Mint" button that calls `avatar.personalToken.mint()` only when `getMintableAmount()` returns > 0
> 4. Has a "Send" form that uses `avatar.transfer.advanced(recipient, amount, { useWrappedBalances: true })` and shows the pathfinder route before submitting
> 5. Subscribes to `avatar.events` and shows a live feed of incoming `CrcV2_TransferSingle` events
>
> Use Tailwind for styling. Keep state local with React hooks. Don't add a backend.

The agent will fetch the relevant SDK pages from Context7, scaffold the project, and write the code. You then iterate the same way: "the trust list pagination breaks at page 2", "wrap the mint button in a loading state", "add a copy-address button". Each follow-up triggers another targeted Context7 lookup.

{% hint style="info" %}
Use [Mini Apps SDK](https://www.npmjs.com/package/@aboutcircles/miniapp-sdk) for building Embedded Mini Apps.
{% endhint %}

***

### Querying the docs without an editor

If you're building a fully autonomous agent (no editor, no MCP), the Circles docs site itself ships an agent-friendly endpoint. Send an HTTP GET with an `ask` query parameter:

```
GET https://docs.aboutcircles.com/readme.md?ask=<your-question>
```

The response is a structured answer plus relevant excerpts and source links from the documentation. This is useful for:

* Server-side agents that need to look up Circles concepts on demand
* CI pipelines that validate generated code against current API shapes
* Backfill workflows where you want to enrich code comments with doc links

Use it alongside Context7 when you need a quick natural-language answer rather than a code snippet.

***

### Bonus

You can also checkout this Agentic first MiniApps branch for builiding your own applications

{% embed url="<https://github.com/aboutcircles/CirclesMiniapps/tree/feature/autonomous-agent-build-instructions>" %}

### Troubleshooting

**The agent still suggests `@circles-sdk/*` packages.** The deprecation note is on the docs but the agent's pre-training data is loud. Strengthen your project rule (Step 3) with an explicit "never use @circles-sdk/\*" line, and prefix one-off prompts with `use context7`.

**`use context7` does nothing.** Check that the MCP server is actually running: in Claude Code run `/mcp`; in Cursor open the MCP panel; in Claude Desktop check the gear icon. The most common causes are a JSON syntax error in the config file, missing Node.js in `PATH`, or forgetting to restart the editor.

**Rate limits.** Without an API key, Context7 is generous but capped. Grab a free key from [context7.com/dashboard](https://context7.com/dashboard) and add it under the `env` block in your MCP config — no other changes needed.

**The generated code uses `ethers` instead of `viem`.** Older Circles SDK examples used ethers. The current `@aboutcircles/sdk` uses viem. Either pin viem explicitly in your prompt or add it to the project rule.

**Pathfinder returns `maxFlow: 0`.** Either there's no trust path between sender and recipient, or both balances need wrapping. Try passing `useWrappedBalances: true` and inspect `rpc.pathfinder.findMaxFlow` first to confirm any liquid path exists.

Happy building. The trust economy is yours to extend.


