@aboutcircles/sdk-invitations 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ import type { Address, CirclesConfig, TransactionRequest, Hex } from '@aboutcircles/sdk-types';
2
+ import type { ReferralPreviewList } from './types';
3
+ export interface GeneratedInvite {
4
+ secret: Hex;
5
+ signer: Address;
6
+ }
7
+ export interface GenerateInvitesResult {
8
+ invites: GeneratedInvite[];
9
+ transactions: TransactionRequest[];
10
+ }
11
+ /**
12
+ * InviteFarm handles batch invitation generation via the InvitationFarm contract
13
+ *
14
+ * This class provides methods to generate multiple invitations at once using
15
+ * the InvitationFarm contract, which manages a farm of InvitationBot instances.
16
+ */
17
+ export declare class InviteFarm {
18
+ private config;
19
+ private invitations;
20
+ private invitationFarm;
21
+ private referralsModule;
22
+ private hubV2;
23
+ constructor(config: CirclesConfig);
24
+ /**
25
+ * Get the remaining invite quota for a specific inviter
26
+ */
27
+ getQuota(inviter: Address): Promise<bigint>;
28
+ /**
29
+ * Get the invitation fee (96 CRC)
30
+ */
31
+ getInvitationFee(): Promise<bigint>;
32
+ /**
33
+ * Get the invitation module address from the farm
34
+ */
35
+ getInvitationModule(): Promise<Address>;
36
+ /**
37
+ * Generate batch invitations using the InvitationFarm
38
+ *
39
+ * This method:
40
+ * 1. Simulates claimInvites to get token IDs that will be claimed
41
+ * 2. Generates random secrets and derives signer addresses
42
+ * 3. Builds transaction batch: claimInvites + safeBatchTransferFrom
43
+ *
44
+ * @param inviter - Address of the inviter (must have quota)
45
+ * @param count - Number of invitations to generate
46
+ * @returns Generated invites with secrets/signers and transactions to execute
47
+ */
48
+ generateInvites(inviter: Address, count: number): Promise<GenerateInvitesResult>;
49
+ /**
50
+ * List referrals for a given inviter with key previews
51
+ *
52
+ * @param inviter - Address of the inviter
53
+ * @param limit - Maximum number of referrals to return (default 10)
54
+ * @param offset - Number of referrals to skip for pagination (default 0)
55
+ * @returns Paginated list of referral previews with masked keys
56
+ */
57
+ listReferrals(inviter: Address, limit?: number, offset?: number): Promise<ReferralPreviewList>;
58
+ }
59
+ //# sourceMappingURL=InviteFarm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InviteFarm.d.ts","sourceRoot":"","sources":["../src/InviteFarm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAO/F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AASnD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,GAAG,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,YAAY,EAAE,kBAAkB,EAAE,CAAC;CACpC;AAED;;;;;GAKG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,cAAc,CAAgC;IACtD,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,KAAK,CAAuB;gBAExB,MAAM,EAAE,aAAa;IAiBjC;;OAEG;IACG,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjD;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;IAIzC;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC;IAI7C;;;;;;;;;;;OAWG;IACG,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA0EtF;;;;;;;OAOG;IACG,aAAa,CACjB,OAAO,EAAE,OAAO,EAChB,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB,OAAO,CAAC,mBAAmB,CAAC;CAGhC"}
@@ -0,0 +1,123 @@
1
+ import { InvitationFarmContractMinimal, ReferralsModuleContractMinimal, HubV2ContractMinimal } from '@aboutcircles/sdk-core/minimal';
2
+ import { InvitationError } from './errors';
3
+ import { Invitations } from './Invitations';
4
+ import { generatePrivateKey, privateKeyToAddress, encodeAbiParameters, INVITATION_FEE } from '@aboutcircles/sdk-utils';
5
+ /**
6
+ * InviteFarm handles batch invitation generation via the InvitationFarm contract
7
+ *
8
+ * This class provides methods to generate multiple invitations at once using
9
+ * the InvitationFarm contract, which manages a farm of InvitationBot instances.
10
+ */
11
+ export class InviteFarm {
12
+ config;
13
+ invitations;
14
+ invitationFarm;
15
+ referralsModule;
16
+ hubV2;
17
+ constructor(config) {
18
+ this.config = config;
19
+ this.invitations = new Invitations(config);
20
+ this.invitationFarm = new InvitationFarmContractMinimal({
21
+ address: config.invitationFarmAddress,
22
+ rpcUrl: config.circlesRpcUrl,
23
+ });
24
+ this.referralsModule = new ReferralsModuleContractMinimal({
25
+ address: config.referralsModuleAddress,
26
+ rpcUrl: config.circlesRpcUrl,
27
+ });
28
+ this.hubV2 = new HubV2ContractMinimal({
29
+ address: config.v2HubAddress,
30
+ rpcUrl: config.circlesRpcUrl,
31
+ });
32
+ }
33
+ /**
34
+ * Get the remaining invite quota for a specific inviter
35
+ */
36
+ async getQuota(inviter) {
37
+ return this.invitationFarm.inviterQuota(inviter);
38
+ }
39
+ /**
40
+ * Get the invitation fee (96 CRC)
41
+ */
42
+ async getInvitationFee() {
43
+ return this.invitationFarm.invitationFee();
44
+ }
45
+ /**
46
+ * Get the invitation module address from the farm
47
+ */
48
+ async getInvitationModule() {
49
+ return this.invitationFarm.invitationModule();
50
+ }
51
+ /**
52
+ * Generate batch invitations using the InvitationFarm
53
+ *
54
+ * This method:
55
+ * 1. Simulates claimInvites to get token IDs that will be claimed
56
+ * 2. Generates random secrets and derives signer addresses
57
+ * 3. Builds transaction batch: claimInvites + safeBatchTransferFrom
58
+ *
59
+ * @param inviter - Address of the inviter (must have quota)
60
+ * @param count - Number of invitations to generate
61
+ * @returns Generated invites with secrets/signers and transactions to execute
62
+ */
63
+ async generateInvites(inviter, count) {
64
+ if (count <= 0) {
65
+ throw new InvitationError('Count must be greater than 0', {
66
+ code: 'INVITATION_INVALID_COUNT',
67
+ source: 'VALIDATION',
68
+ context: { count },
69
+ });
70
+ }
71
+ const inviterLower = inviter.toLowerCase();
72
+ const numberOfInvites = BigInt(count);
73
+ // Step 1: Simulate claimInvites to get token IDs
74
+ const ids = await this.invitationFarm.read('claimInvites', [numberOfInvites], {
75
+ from: inviterLower
76
+ });
77
+ if (!ids || ids.length === 0) {
78
+ throw new InvitationError('No invitation IDs returned from claimInvites', {
79
+ code: 'INVITATION_NO_IDS',
80
+ source: 'INVITATIONS',
81
+ context: { inviter: inviterLower, count },
82
+ });
83
+ }
84
+ // Step 2: Generate secrets and signers
85
+ const invites = [];
86
+ const signers = [];
87
+ for (let i = 0; i < count; i++) {
88
+ const secret = generatePrivateKey();
89
+ const signer = privateKeyToAddress(secret).toLowerCase();
90
+ invites.push({ secret, signer });
91
+ signers.push(signer);
92
+ }
93
+ // Step 3: Get addresses
94
+ const invitationModuleAddress = await this.invitationFarm.invitationModule();
95
+ // Step 4: Build transactions
96
+ const claimTx = this.invitationFarm.claimInvites(numberOfInvites);
97
+ // Encode createAccounts call
98
+ const createAccountsTx = this.referralsModule.createAccounts(signers);
99
+ const createAccountsData = createAccountsTx.data;
100
+ // Encode data for safeBatchTransferFrom
101
+ const encodedData = encodeAbiParameters(['address', 'bytes'], [this.config.referralsModuleAddress, createAccountsData]);
102
+ // Build amounts array (96 CRC per invite)
103
+ const amounts = ids.map(() => INVITATION_FEE);
104
+ const batchTransferTx = this.hubV2.safeBatchTransferFrom(inviterLower, invitationModuleAddress, ids, amounts, encodedData);
105
+ // Save all referrals to database
106
+ await Promise.all(invites.map((inv) => this.invitations.saveReferralData(inviterLower, inv.secret)));
107
+ return {
108
+ invites,
109
+ transactions: [claimTx, batchTransferTx],
110
+ };
111
+ }
112
+ /**
113
+ * List referrals for a given inviter with key previews
114
+ *
115
+ * @param inviter - Address of the inviter
116
+ * @param limit - Maximum number of referrals to return (default 10)
117
+ * @param offset - Number of referrals to skip for pagination (default 0)
118
+ * @returns Paginated list of referral previews with masked keys
119
+ */
120
+ async listReferrals(inviter, limit = 10, offset = 0) {
121
+ return this.invitations.listReferrals(inviter, limit, offset);
122
+ }
123
+ }
@@ -0,0 +1,44 @@
1
+ import type { ReferralInfo, ReferralList } from "./types";
2
+ /**
3
+ * Referrals service client for retrieving referral information
4
+ *
5
+ * The referrals backend enables Circles SDK users to query referral data:
6
+ * - Retrieve: Get referral info by private key (public)
7
+ * - List: Get all referrals created by authenticated user
8
+ *
9
+ * Note: Storing referrals is handled by Invitations.generateReferral()
10
+ */
11
+ export declare class Referrals {
12
+ private readonly baseUrl;
13
+ private readonly getToken?;
14
+ /**
15
+ * Create a new Referrals client
16
+ *
17
+ * @param baseUrl - The referrals service base URL (e.g., "https://referrals.circles.example")
18
+ * @param getToken - Optional function to get auth token for authenticated endpoints
19
+ */
20
+ constructor(baseUrl: string, getToken?: (() => Promise<string>) | undefined);
21
+ private getBaseUrl;
22
+ private getAuthHeaders;
23
+ /**
24
+ * Retrieve referral info by private key
25
+ *
26
+ * This is a public endpoint - no authentication required.
27
+ * Used by invitees to look up who invited them.
28
+ *
29
+ * @param privateKey - The referral private key
30
+ * @returns Referral info including inviter and status
31
+ * @throws InvitationError if referral not found or expired
32
+ */
33
+ retrieve(privateKey: string): Promise<ReferralInfo>;
34
+ /**
35
+ * List all referrals created by the authenticated user
36
+ *
37
+ * Requires authentication - the user's address is extracted from the JWT token.
38
+ *
39
+ * @returns List of referrals with their status and metadata
40
+ * @throws InvitationError if not authenticated or request fails
41
+ */
42
+ listMine(): Promise<ReferralList>;
43
+ }
44
+ //# sourceMappingURL=Referrals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Referrals.d.ts","sourceRoot":"","sources":["../src/Referrals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAY,MAAM,SAAS,CAAC;AAGpE;;;;;;;;GAQG;AACH,qBAAa,SAAS;IAQlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAR5B;;;;;OAKG;gBAEgB,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,GAAE,MAAM,OAAO,CAAC,MAAM,CAAC,aAAA;IAGnD,OAAO,CAAC,UAAU;YAMJ,cAAc;IAY5B;;;;;;;;;OASG;IACG,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAmCzD;;;;;;;OAOG;IACG,QAAQ,IAAI,OAAO,CAAC,YAAY,CAAC;CAyCxC"}
@@ -0,0 +1,129 @@
1
+ import { InvitationError } from "./errors";
2
+ /**
3
+ * Referrals service client for retrieving referral information
4
+ *
5
+ * The referrals backend enables Circles SDK users to query referral data:
6
+ * - Retrieve: Get referral info by private key (public)
7
+ * - List: Get all referrals created by authenticated user
8
+ *
9
+ * Note: Storing referrals is handled by Invitations.generateReferral()
10
+ */
11
+ export class Referrals {
12
+ baseUrl;
13
+ getToken;
14
+ /**
15
+ * Create a new Referrals client
16
+ *
17
+ * @param baseUrl - The referrals service base URL (e.g., "https://referrals.circles.example")
18
+ * @param getToken - Optional function to get auth token for authenticated endpoints
19
+ */
20
+ constructor(baseUrl, getToken) {
21
+ this.baseUrl = baseUrl;
22
+ this.getToken = getToken;
23
+ }
24
+ getBaseUrl() {
25
+ return this.baseUrl.endsWith("/")
26
+ ? this.baseUrl.slice(0, -1)
27
+ : this.baseUrl;
28
+ }
29
+ async getAuthHeaders() {
30
+ if (!this.getToken) {
31
+ return { "Content-Type": "application/json" };
32
+ }
33
+ const token = await this.getToken();
34
+ return {
35
+ "Content-Type": "application/json",
36
+ Authorization: `Bearer ${token}`,
37
+ };
38
+ }
39
+ /**
40
+ * Retrieve referral info by private key
41
+ *
42
+ * This is a public endpoint - no authentication required.
43
+ * Used by invitees to look up who invited them.
44
+ *
45
+ * @param privateKey - The referral private key
46
+ * @returns Referral info including inviter and status
47
+ * @throws InvitationError if referral not found or expired
48
+ */
49
+ async retrieve(privateKey) {
50
+ try {
51
+ const url = `${this.getBaseUrl()}/referral/retrieve?key=${encodeURIComponent(privateKey)}`;
52
+ const response = await fetch(url);
53
+ if (!response.ok) {
54
+ let errorMessage = `HTTP error! status: ${response.status}`;
55
+ try {
56
+ const error = (await response.json());
57
+ errorMessage = error.error || errorMessage;
58
+ }
59
+ catch {
60
+ errorMessage = response.statusText || errorMessage;
61
+ }
62
+ throw new InvitationError(errorMessage, {
63
+ code: 'INVITATION_RETRIEVE_FAILED',
64
+ source: 'INVITATIONS',
65
+ context: { status: response.status, url, privateKey }
66
+ });
67
+ }
68
+ return response.json();
69
+ }
70
+ catch (error) {
71
+ if (error instanceof InvitationError) {
72
+ throw error;
73
+ }
74
+ throw new InvitationError(`Failed to retrieve referral: ${error instanceof Error ? error.message : 'Unknown error'}`, {
75
+ code: 'INVITATION_RETRIEVE_ERROR',
76
+ source: 'INVITATIONS',
77
+ cause: error,
78
+ context: { privateKey }
79
+ });
80
+ }
81
+ }
82
+ /**
83
+ * List all referrals created by the authenticated user
84
+ *
85
+ * Requires authentication - the user's address is extracted from the JWT token.
86
+ *
87
+ * @returns List of referrals with their status and metadata
88
+ * @throws InvitationError if not authenticated or request fails
89
+ */
90
+ async listMine() {
91
+ if (!this.getToken) {
92
+ throw new InvitationError("Authentication required to list referrals", {
93
+ code: 'INVITATION_AUTH_REQUIRED',
94
+ source: 'INVITATIONS'
95
+ });
96
+ }
97
+ try {
98
+ const url = `${this.getBaseUrl()}/referral/my-referrals`;
99
+ const headers = await this.getAuthHeaders();
100
+ const response = await fetch(url, { headers });
101
+ if (!response.ok) {
102
+ let errorMessage = `HTTP error! status: ${response.status}`;
103
+ try {
104
+ const error = (await response.json());
105
+ errorMessage = error.error || errorMessage;
106
+ }
107
+ catch {
108
+ errorMessage = response.statusText || errorMessage;
109
+ }
110
+ throw new InvitationError(errorMessage, {
111
+ code: 'INVITATION_LIST_FAILED',
112
+ source: 'INVITATIONS',
113
+ context: { status: response.status, url }
114
+ });
115
+ }
116
+ return response.json();
117
+ }
118
+ catch (error) {
119
+ if (error instanceof InvitationError) {
120
+ throw error;
121
+ }
122
+ throw new InvitationError(`Failed to list referrals: ${error instanceof Error ? error.message : 'Unknown error'}`, {
123
+ code: 'INVITATION_LIST_ERROR',
124
+ source: 'INVITATIONS',
125
+ cause: error
126
+ });
127
+ }
128
+ }
129
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,69 @@
1
+ import { CirclesError } from '@aboutcircles/sdk-utils';
2
+ /**
3
+ * Base error for invitations package
4
+ */
5
+ export class InvitationError extends CirclesError {
6
+ constructor(message, options) {
7
+ super('InvitationError', message, { ...options, source: options?.source || 'INVITATIONS' });
8
+ }
9
+ /**
10
+ * Error when no valid invitation path is found
11
+ */
12
+ static noPathFound(from, to, reason) {
13
+ return new InvitationError(`No valid invitation path found from ${from} to ${to}. ${reason || 'The inviter may not have enough balance of the proxy inviter\'s token or there\'s no trust connection.'}`, {
14
+ code: 'INVITATION_NO_PATH',
15
+ source: 'PATHFINDING',
16
+ context: { from, to, reason },
17
+ });
18
+ }
19
+ /**
20
+ * Error when no proxy inviters are available
21
+ */
22
+ static noProxyInviters(inviter) {
23
+ return new InvitationError(`No proxy inviters found for ${inviter}. The inviter must have mutual trust connections with users who are also trusted by the invitation module, and these users must have sufficient balance.`, {
24
+ code: 'INVITATION_NO_PROXY_INVITERS',
25
+ source: 'VALIDATION',
26
+ context: { inviter },
27
+ });
28
+ }
29
+ /**
30
+ * Error when balance is insufficient for the requested invitations
31
+ */
32
+ static insufficientBalance(requestedInvites, availableInvites, requested, available, from, to) {
33
+ const requestedCrc = Number(requested) / 1e18;
34
+ const availableCrc = Number(available) / 1e18;
35
+ return new InvitationError(`Insufficient balance for ${requestedInvites} invitation(s). Can only afford ${availableInvites} invitation(s). Requested: ${requestedCrc.toFixed(6)} CRC, Available: ${availableCrc.toFixed(6)} CRC.`, {
36
+ code: 'INVITATION_INSUFFICIENT_BALANCE',
37
+ source: 'VALIDATION',
38
+ context: {
39
+ from,
40
+ to,
41
+ requestedInvites,
42
+ availableInvites,
43
+ requested: requested.toString(),
44
+ available: available.toString(),
45
+ requestedCrc,
46
+ availableCrc,
47
+ },
48
+ });
49
+ }
50
+ /**
51
+ * Error when invitee is already registered in Circles Hub
52
+ */
53
+ static inviteeAlreadyRegistered(inviter, invitee) {
54
+ return new InvitationError(`Invitee ${invitee} is already registered as a human in Circles Hub. Cannot invite an already registered user.`, {
55
+ code: 'INVITATION_INVITEE_ALREADY_REGISTERED',
56
+ source: 'VALIDATION',
57
+ context: { inviter, invitee },
58
+ });
59
+ }
60
+ /**
61
+ * Error when no addresses are provided for invitation
62
+ */
63
+ static noAddressesProvided() {
64
+ return new InvitationError('At least one address must be provided for invitation.', {
65
+ code: 'INVITATION_NO_ADDRESSES_PROVIDED',
66
+ source: 'VALIDATION',
67
+ });
68
+ }
69
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
- export * from './InvitationBuilder';
1
+ export * from './Invitations';
2
+ export * from './InviteFarm';
3
+ export * from './Referrals';
2
4
  export * from './errors';
5
+ export * from './types';
3
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC"}