The Circles SDK is a library that allows you to interact with the Circles protocol. It supports version 1 and 2 of the Circles contracts.
Warning: This library is in a very early development stage. Things are broken and the api surface is pretty much WIP.
Currently, there are no npm packages, so you must build the SDK yourself if you want to use it.
If you want to run a local environment with anvil, see the section Run locally.
Make sure you have all the following prerequisites properly installed:
This is a monorepo using npm workspaces.
git clone https://github.com/CirclesUBI/circles-sdk.git
cd circles-sdk
npm install
npm run build
You can choose between the following providers:
- EoaEtheresProvider
Use this provider if you want to use a private key to sign transactions. - BrowserWalletEthersProvider
Use this provider if you have e.g. metamask installed and want to use it to sign transactions.
import { Provider, EoaEtheresProvider, BrowserWalletEthersProvider } from '@circles/circles-sdk-v2-providers';
const wallet = new ethers.Wallet('0x123...'); // Supply your private key
const provider1: Provider = new EoaEtheresProvider('http://localhost:8545', wallet);
await provider1.init();
// or
const provider2: Provider = new BrowserWalletEthersProvider();
await provider2.init();
At a later point we will add more providers, e.g. for Safe.
In order to use the sdk you must supply the contracts addresses and a provider:
import { Sdk } from '@circles/circles-sdk-v2';
const v1HubAddress = '0x123...';
const v2HubAddress = '0x123...';
const provider = // Choose one of the providers from the previous step
const sdk = new Sdk(v1HubMock, v1HubAddress, v2HubAddress, provider);
To interact with Circles, you need an avatar. You can create one by calling createAvatar
.
The address can be any address (EOA, smart contract wallet) that you control.
const avatar = await sdk.createAvatar("0x123..."); // Supply the avatar address
await avatar.init();
Depending on your previous usage of the address, the avatar will be initialized in one of these states:
enum AvatarState {
Unregistered, // The address has not been used with Circles before
V1_Human, // The address is only a V1 human
V1_StoppedHuman, // The address is only a V1 human that has been stopped
V1_Organization, // The address is only a V1 organization
V2_Human, // The address is only a V2 human
V2_Group, // The address is only a V2 group
V2_Organization, // The address is only a V2 organizations
V1_StoppedHuman_and_V2_Human, // The address is a V1 human that has been stopped and a V2 human
Unknown // The address has been used with Circles before, but the state is unknown
}
If you already have a v1 Circles account, you can upgrade it to v2.
After upgrading,
- ... you will have a new personal v2 token that you can use to mint.
- ... you can convert all your v1 token holdings to v2 tokens as long as they're available on v2 already.
If your friends haven't upgraded to v2 yet, you can 'invite' them. This will cost you a fee, but everyone will be able to convert v1 tokens of the invited person to v2 tokens immediately (See 'Invite a friend' below).
Note: If these prerequisites aren't met, you must be invited by an existing member instead.
- The registration period is not over
- Your v1 token must be stopped
// Check if the registration period is already over
if (await sdk.isRegistrationPeriodOver()) {
throw new Error('The registration period is already over');
}
// Check if the avatar is a V1 human and has been stopped
if (avatar.state !== AvatarState.V1_StoppedHuman) {
throw new Error('You cannot register at Circles v2 because your v1 token is not stopped');
}
If you're v1 token is not stopped, you can stop it like this:
if (avatar.state !== AvatarState.V1_Human) {
throw new Error(`You don't have a v1 token`);
}
const txReceipt = await avatar.stopV1();
In Circles v2, every human has a profile. The profile is a JSON object that is stored on IPFS. The profile is identified by a CIDv0 (Content Identifier). The CID is updatable for the case that the profile changes in the future.
The profile schema is defined in ERC-1155 Metadata URI JSON Schema.
const cidV0 = 'Qm...'; // CIDv0 of your profile
const txReceipt = await avatar.registerHuman(cidV0);
If the registration was successful, the state of your avatar should have changed to V1_StoppedHuman_and_V2_Human
.
if (avatar.state !== AvatarState.V1_StoppedHuman_and_V2_Human) {
throw new Error('Something went wrong');
}
If you are already a member of Circles, you can invite a friend to join.
After the end of the registration period, this is the only way for new people to register at Circles.
The friend is identified by their address. The address can be any address (EOA, smart contract wallet) that your friend controls.
You can invite someone in order to:
- ... allow them to join Circles for the first time
- ... allow them to upgrade their existing accounts
- ... convert v1 token holdings to v2 tokens if the person hasn't upgraded yet
After inviting,
- ... the invited person will have a new personal v2 token.
- ... you (and others) can convert v1 token holdings of the invitee's token to v2 tokens.
Note: The inviter must pay an invitation fee. The invitee will get a welcome bonus.
// Mint the outstanding amount of personal tokens to maximize the chances of being able to invite someone.
await avatar.mintPersonalTokens();
// How high is the invitation fee?
const invitationFee: bigint = await sdk.getInvitationFee();
// Whats the balance of the avatar's own token?
const ownTokenBalance: bigint = await avatar.getTokenBalance();
// Can be paid?
if (invitationFee > ownTokenBalance) {
throw new Error('You must mint more personal tokens before you can invite someone.');
}
const txReceipt = await avatar.inviteHuman('0x123...');
If the invitation was successful, the state of the invited avatar should have changed to V2_Human
.
const invitedAvatar = await sdk.createAvatar('0x123...');
await invitedAvatar.init();
if (invitedAvatar.state !== AvatarState.V2_Human) {
throw new Error('Something went wrong');
}
In Circles v2, every human has a profile. The profile is a JSON object that is stored on IPFS. The profile is identified by a CIDv0 (Content Identifier). The CID is updatable for the case that the profile changes in the future.
The profile schema is defined in ERC-1155 Metadata URI JSON Schema.
Use this method, if
- ... you want to update your profile.
- ... you have been invited and don't have a profile yet.
const cidV0 = 'Qm...'; // New CIDv0 of your profile
const txReceipt = await avatar.updateProfile(cidV0);
For testing and development purposes, it makes sense to run a local environment with anvil.
- build the SDK as described in the previous section.
anvil --port 8545 --gas-limit 8000000 --accounts 10
When anvil was started with default values, you can use the following values to connect to:
- URL: http://localhost:8545
- Address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
- Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
If you need more accounts, see anvil's console output.
The SDK comes with a script that deploys all relevant contracts with forge create
.
The deployContracts.sh
script can be configured with environment variables or an .env
file.
Create custom .env files if you want to deploy to other targets. For a local deployment, you can use .env.anvil
:
./deployContracts.sh .env.anvil
After the deployment, the script will print the addresses of the deployed contracts.
Summary:
========
V1 Hub: 0x5FbDB2315678afecb367f032d93F642f64180aa3
V2 Hub: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
You can use the following code to configure the Circles SDK so that it uses the local anvil environment. Here we're using the values from above:
import { Sdk } from '@circles/circles-sdk-v2';
const rpcUrl = 'http://localhost:8545';
const privateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
const v1HubAddress = '0x5FbDB2315678afecb367f032d93F642f64180aa3';
const v2HubAddress = '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512';
const jsonRpcProvider = new ethers.JsonRpcProvider(rpcUrl, wallet);
const wallet = new ethers.Wallet(privateKey, jsonRpcProvider);
const provider = new EoaEtheresProvider(jsonRpcProvider, wallet);
await provider.init();
const sdk = new Sdk(v1HubAddress, v2HubAddress, provider);
const avatar = await sdk.createAvatar(wallet.address);
await avatar.init();
console.log(`Avatar ${avatar.address} state:`, avatar.state);
To run the 'jest' tests, use the following command in the repository root directory:
npm run test