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.
Overview
The SDK exposes two levels of abstraction:
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.
Prerequisites
Before using InviteFarm, make sure that:
The inviter already has enough quota for the number of invites.
Your configuration includes
referralsServiceUrl.The inviter's Safe has completed the one-time invitation setup.
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
InviteFarmCreate 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.
Check available quota
Each quota unit represents one invitation funded with 96 CRC.
Useful helpers:
For batch invitations, validate that the inviter has enough quota for the full batch:
Note A quota check is best-effort. The final debit occurs on-chain when
claimInvite()orclaimInvites(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.
Requirements
The
inviteesarray must not be empty.Each invitee must already have a Safe wallet.
Each invitee's Safe must already have the
InvitationModuleenabled. Use the referral flow for brand-new users instead.
Returned transactions
For one invitee, the SDK returns:
For multiple invitees, the SDK returns:
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.
generateReferrals returns one referral object per new user:
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:
Create a device WebAuthn passkey.
Sign the module's EIP-712 passkey digest with the referral secret.
Relay
ReferralsModule.claimAccount(...).Bind the passkey as the Safe signer.
Receive the
48 CRCwelcome bonus.Complete registration as a self-custodial Circles human with the original inviter recorded as the permanent inviter.
The claim call has the following shape:
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:
The high-level facade also exposes:
For single-invite flows, it also provides:
Execute invitation transactions atomically
Both generateInvites and generateReferrals return exactly two transactions:
Always pass the complete array to a runner in a single call:
Use a runner from @aboutcircles/sdk-runner:
SafeContractRunnerfor server-side execution with a private key.SafeBrowserRunnerfor browser-wallet execution.
Important Do not send
claimTxandtransferTxseparately. During the claim, the funding bot grants the inviter temporary trust that is valid only for the current block. TheInvitationModulechecks that trust when the borrowed CRC arrives. If the transactions are split across blocks, the transfer reverts withTrustRequired.
How InviteFarm builds the batch
InviteFarm builds the batchFor 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:
Each transfer sends 96 CRC per invitation from the inviter to the InvitationModule.
The transfer data depends on the onboarding flow:
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:
On-chain result
For each successful invitation:
The inviter's quota decreases by one.
The farm bot's
96 CRCfunds registration and is returned to the bot.The new human is registered with the original inviter recorded as the permanent inviter.
API reference
InviteFarm
InviteFarmPackage: @aboutcircles/sdk-invitations
config must include referralsServiceUrl.
generateInvites
generateReferrals
HumanAvatar.invitation
HumanAvatar.invitationHigh-level, runner-backed facade:
Runner execution
Package: @aboutcircles/sdk-runner
Available Safe runners:
Referral storage
Persist and retrieve referral secrets through the top-level SDK:
Errors and troubleshooting
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, andTrustRequiredare surfaced clearly to users.
Last updated
Was this helpful?