├── .DS_Store
├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ └── publish.yml
├── .gitignore
├── .npmignore
├── .prettierignore
├── README.md
├── examples
├── custom-point-distribution
│ ├── README.md
│ ├── index.ts
│ └── package.json
└── next-js
│ ├── .env
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── next.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── next.svg
│ └── vercel.svg
│ ├── src
│ ├── app
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── page.module.css
│ │ └── page.tsx
│ └── lib
│ │ └── guild.ts
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── src
├── client.ts
├── clients
│ ├── guild.ts
│ ├── guildAdmin.ts
│ ├── guildReward.ts
│ ├── platform.ts
│ ├── platformUser.ts
│ ├── requirement.ts
│ ├── role.ts
│ ├── rolePlatform.ts
│ ├── user.ts
│ └── userAddress.ts
├── common.ts
├── error.ts
├── index.ts
└── utils.ts
├── tests
├── auth.test.ts
├── callGuildAPI.test.ts
├── clients
│ ├── actions
│ │ ├── accessCheck.test.ts
│ │ └── join.test.ts
│ ├── guild.test.ts
│ ├── guildAdmin.test.ts
│ ├── guildPlatform.test.ts
│ ├── platform.test.ts
│ ├── platformUser.test.ts
│ ├── requirement.test.ts
│ ├── role.test.ts
│ ├── rolePlatform.test.ts
│ ├── user.test.ts
│ └── userAddress.test.ts
├── common.ts
├── points.test.ts
└── utils.ts
├── tsconfig.json
├── v2-migration-guide.md
└── vitest.config.ts
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guildxyz/guild-sdk/75470add817abdac0842847b89757473cf0b0a5c/.DS_Store
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build
2 | node_modules
3 | examples
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es2021": true,
4 | "node": true
5 | },
6 | "extends": ["airbnb-base", "prettier"],
7 | "settings": {
8 | "import/resolver": {
9 | "node": {
10 | "extensions": [".js", ".jsx", ".ts", ".tsx"]
11 | }
12 | }
13 | },
14 | "parser": "@typescript-eslint/parser",
15 | "parserOptions": {
16 | "ecmaVersion": 12,
17 | "sourceType": "module"
18 | },
19 | "plugins": ["@typescript-eslint"],
20 | "rules": {
21 | "import/extensions": [
22 | "error",
23 | "ignorePackages",
24 | {
25 | "jsx": "never",
26 | "ts": "never",
27 | "tsx": "never"
28 | }
29 | ]
30 | },
31 | "overrides": [
32 | {
33 | "files": ["**/tests/*.test.ts"],
34 | "env": {
35 | "jest": true
36 | }
37 | }
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish @guildxyz/sdk
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | jobs:
9 | build:
10 | runs-on: self-hosted
11 | container: node:lts-alpine3.18
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | # Setup .npmrc file to publish to npm
16 | - uses: actions/setup-node@v3
17 | with:
18 | node-version: "20.x"
19 | registry-url: "https://registry.npmjs.org"
20 |
21 | - name: "Install dependencies"
22 | run: npm ci
23 |
24 | - name: "Bump package version"
25 | run: |
26 | npm version $( echo "$GITHUB_REF_NAME" | sed 's/v//g' ) --git-tag-version=false
27 |
28 | - name: "Publish package to NPM"
29 | run: npm publish
30 | env:
31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | others/
3 | node_modules/
4 | .vscode
5 | .env*
6 | dist
7 | *.env
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | others/
2 | node_modules/
3 | .vscode
4 | .env*
5 | src/
6 | tests/
7 | examples/
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | tsconfig.json
4 | .github
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 | ## Summary
19 |
20 | The Guild SDK library is a Typescript library for interacting with the Guild API. This document explains how to authenticate, manage your Guilds easily and automate token-gated access in any application with this SDK.
21 |
22 | Guild.xyz is the membership layer protocol for web3 communities, making community management easy and interoperable between platforms.
23 |
24 | ## Migration Guide to V2
25 |
26 | ⚠️ `1.x.x versions` of the SDK are **_deprecated_**, these versions won't work after **_2024-01-31_**. Please migrate to the latest version. You can find the migration guide [HERE](https://github.com/guildxyz/guild-sdk/blob/main/v2-migration-guide.md#guild-sdk-v2-migration-guide).
27 |
28 | ## Demo app
29 |
30 | A demo app is available [here](https://github.com/guildxyz/guild-sdk/tree/main/examples/next-js), it shows how to:
31 |
32 | - Connect wallet (with wagmi)
33 | - Sign a message
34 | - Get user profile with signature
35 | - Fetch data (with SWR)
36 | - Guild roles
37 | - User memberships
38 | - Leaderboard
39 |
40 | ## Contents
41 |
42 | - [Installation](#installation)
43 | - [Importing the package and creating a Guild client](#importing-the-package-and-creating-a-guild-client)
44 | - [SignerFunctions and Authentication](#signerfunctions-and-authentication)
45 | - [ethers.js](#creating-a-signer-from-an-ethers-wallet)
46 | - [web3-react](#creating-a-custom-signer-for-usage-with-web3-react)
47 | - [wagmi](#creating-a-custom-signer-for-usage-with-wagmi)
48 | - [EIP-1271](#support-for-eip-1271-smart-contract-wallets)
49 | - [Clients](#clients)
50 | - [Guild client](#guild-client)
51 | - [Points](#points)
52 | - [Guild admin client](#guild-admin-client)
53 | - [Guild reward client](#guild-reward-client)
54 | - [Role client](#role-client)
55 | - [Requirement client](#requirement-client)
56 | - [Role reward client](#role-reward-client)
57 | - [User client](#user-client)
58 | - [User address client](#user-address-client)
59 | - [User platform client](#user-platform-client)
60 | - [Modular / multi-platform architecture](#modular--multi-platform-architecture)
61 | - [Examples](#examples)
62 | - [Example flow from Create Guild to Join](#example-flow-from-create-guild-to-join)
63 | - [Multiple telegram groups guild](#multiple-telegram-groups-guild)
64 |
65 | ### Installation
66 |
67 | To install our SDK, open your terminal and run:
68 |
69 | ```
70 | npm i @guildxyz/sdk
71 | ```
72 |
73 | ### Importing the package and creating a Guild client
74 |
75 | ```typescript
76 | import { createGuildClient, createSigner } from "@guildxyz/sdk";
77 |
78 | // The only parameter is the name of your project
79 | const guildClient = createGuildClient("My project");
80 | ```
81 |
82 | ### SignerFunctions and Authentication
83 |
84 | #### `Creating a signer from an ethers wallet`
85 |
86 | ```ts
87 | import { ethers } from "ethers";
88 |
89 | const ethersWallet = new ethers.Wallet(...);
90 |
91 | const signerFunction = createSigner.fromEthersWallet(ethersWallet);
92 | ```
93 |
94 | #### `Creating a custom signer for usage with web3-react`
95 |
96 | ```ts
97 | import { useWeb3React } from "@web3-react/core";
98 |
99 | const { account: walletAddress, library } = useWeb3React();
100 |
101 | const signerFunction = createSigner.custom(
102 | (message) => library.getSigner(account).signMessage(signableMessage),
103 | address
104 | );
105 | ```
106 |
107 | #### `Creating a custom signer for usage with wagmi`
108 |
109 | ```ts
110 | import { useAccount, useSignMessage } from "wagmi";
111 |
112 | const { signMessageAsync } = useSignMessage();
113 | const { address } = useAccount();
114 |
115 | const signerFunction = createSigner.custom(
116 | (message) => signMessageAsync({ message }),
117 | address
118 | );
119 | ```
120 |
121 | #### `Support for EIP-1271 smart contract wallets`
122 |
123 | For signatures produced by EIP-1272 wallets, pass `{ chainIdOfSmartContractWallet: chainId }` as the third parameter of `createSigner.custom`, where `chainId` is the chain where the wallet operates. The Guild backend will try to call `isValidSignature` on the specified chain.
124 | We have an example app under [`examples`](https://github.com/guildxyz/guild-sdk/tree/main/examples), which covers this parameter
125 |
126 | ### Clients
127 |
128 | We have multiple clients for different entities. These clients are created from the `guildClient` that we created above.
129 |
130 | #### `Guild client`
131 |
132 | ```ts
133 | const { guild: client } = guildClient;
134 |
135 | // Get Guild by its numeric ID
136 | const guild = await client.get(guildId);
137 |
138 | // Get Guild by its urlName (slug)
139 | const guild = await client.get(urlName);
140 |
141 | // Get multiple guilds by their IDs
142 | const guilds = await client.getMany([guildId1, guildId2]);
143 |
144 | // Search guilds with pagination
145 | const guilds = await client.search({ limit: 10, offset: 0, search: "our" });
146 |
147 | // Get the members of a guild
148 | const members = await client.getMembers(
149 | guildId,
150 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
151 | );
152 |
153 | // Join into a guild
154 | const joinResult = await client.join(guildId, signerFunction);
155 |
156 | // Check access to a guild, signerFuncion is required
157 | await client.accessCheck(guildId, signerFunction);
158 |
159 | // Create a new guild, check the possible creation parameters according to the typing
160 | await client.create(
161 | {
162 | // In this example we are creating a guild with one FREE role
163 | name: "My Guild",
164 | urlName: "my-guild",
165 | roles: [{ name: "My Role", requirements: [{ type: "FREE" }] }],
166 | },
167 | signerFunction
168 | );
169 |
170 | // Update an existing guild, check the possible update parameters according to the typing
171 | await client.update(guildId, { description: "Edited" }, signerFunction);
172 |
173 | // Delete a guild
174 | await client.delete(guildId, signerFunction);
175 | ```
176 |
177 | ##### `Points`
178 |
179 | ```ts
180 | // For a given role, create a new point system, and assign some points as reward
181 | const created = await guild.role.reward.create(
182 | guildId,
183 | roleId, // This role will have the 5 points reward
184 | {
185 | guildPlatform: {
186 | platformGuildId: "my-points", // Some unique name for your point system
187 | platformName: "POINTS",
188 | platformGuildData: { name: "coins" }, // Assign a custom name for the points
189 | },
190 | platformRoleData: { score: 5 }, // Members will get this many points
191 | },
192 | signerFunction
193 | );
194 |
195 | // Use an existing point system for a role
196 | const created = await guild.role.reward.create(
197 | guildId,
198 | roleId, // This role will have the 10 points reward
199 | {
200 | guildPlatformId, // The ID of the existing guildPlatform (reward) object
201 | platformRoleData: { score: 10 },
202 | },
203 | signerFunction
204 | );
205 |
206 | // Get leaderboard for a specific point guild reward
207 | const { leaderboard, aroundUser } = await guild.getLeaderboard(
208 | guildId,
209 | guildPlatformId,
210 | signerFunction // Optional. If provided, the response will include an "aroundUser" field, which contains leaderboard items from around the user's position, otherwise it will be undefined
211 | );
212 |
213 | // Get user's rank in a specific reward
214 | const response = await user.getRankInGuild(userId, guildId, guildPlatformId); // Returns the leaderboard position of a user for the given reward
215 |
216 | // Get all the points of a user across all relevant rewards
217 | const response = await user.getPoints(userId, signerFunction);
218 | ```
219 |
220 | #### `Guild admin client`
221 |
222 | ```ts
223 | const {
224 | guild: { admin: adminClient },
225 | } = guildClient;
226 |
227 | // Get all admins of a guild
228 | const admins = await adminClient.getAll(guildIdOrUrlName);
229 |
230 | // Get a specific admin of a guild
231 | const admin = await adminClient.get(guildIdOrUrlName, userIdOfAdmin);
232 | ```
233 |
234 | #### `Guild reward client`
235 |
236 | ```ts
237 | const {
238 | guild: { reward: guildRewardClient },
239 | } = guildClient;
240 |
241 | // Get a guild reward (like a Discord server)
242 | const guildReward = await guildRewardClient.get(
243 | guildIdOrUrlName,
244 | guildPlatformId,
245 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
246 | );
247 |
248 | // Get all rewards of a guild
249 | const guildRewards = await guildRewardClient.getAll(
250 | guildIdOrUrlName,
251 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
252 | );
253 |
254 | // Add a new reward to a guild
255 | const createdGuildReward = await guildRewardClient.create(guildIdOrUrlName, {
256 | platformName: "DISCORD", // In this example we are adding a Discord server
257 | platformGuildId: "",
258 | });
259 |
260 | // Delete a reward from a guild
261 | await guildRewardClient.delete(
262 | guildIdOrUrlName,
263 | guildPlatformId,
264 | signerFunction
265 | );
266 | ```
267 |
268 | #### `Role client`
269 |
270 | ```ts
271 | const {
272 | guild: { role: roleClient },
273 | } = guildClient;
274 |
275 | // Get a role
276 | await roleClient.get(
277 | guildIdOrUrlName,
278 | roleId,
279 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
280 | );
281 |
282 | // Get all roles of a guild
283 | await roleClient.getAll(
284 | guildIdOrUrlName,
285 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
286 | );
287 |
288 | // Create a new role. Refer to the typing for other possible input parameters, like description, logic, or visibility
289 | const createdRole = await roleClient.create(
290 | guildIdOrUrlName,
291 | {
292 | name: "My new role",
293 | requirements: [{ type: "FREE" }],
294 | },
295 | signerFunction
296 | );
297 |
298 | // Update an existing role
299 | const updatedRole = await roleClient.update(
300 | guildIdOrUrlName,
301 | roleId,
302 | { description: "Edited" },
303 | signerFunction
304 | );
305 |
306 | // Delete a role
307 | await roleClient.delete(guildIdOrUrlName, roleId, signerFunction);
308 | ```
309 |
310 | #### `Requirement client`
311 |
312 | ```ts
313 | const {
314 | guild: {
315 | role: { requirement: requirementClient },
316 | },
317 | } = guildClient;
318 |
319 | // Get a requirement
320 | const requirement = await requirementClient.get(
321 | guildIdOrUrlName,
322 | roleId,
323 | requirementId,
324 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
325 | );
326 |
327 | // Get all requirements of a role
328 | const requirements = await requirementClient.getAll(
329 | guildIdOrUrlName,
330 | roleId,
331 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
332 | );
333 |
334 | // Create a new requirement
335 | const createdRequirement = await requirementClient.create(
336 | guildIdOrUrlName,
337 | roleId,
338 | { type: "FREE" },
339 | signerFunction
340 | );
341 |
342 | // Update an existing requirement (for example addresses in an ALLOWLIST requirement)
343 | const updatedRequirement = await requirementClient.update(
344 | guildIdOrUrlName,
345 | roleId,
346 | requirementId,
347 | { data: { addresses: ["0x..."] } }, // Lowercased addresses
348 | signerFunction
349 | );
350 |
351 | // Delete a requirement
352 | await requirementClient.delete(
353 | guildIdOrUrlName,
354 | roleId,
355 | requirementId,
356 | signerFunction
357 | );
358 | ```
359 |
360 | #### `Role reward client`
361 |
362 | ```ts
363 | const {
364 | guild: {
365 | role: { reward: rewardClient },
366 | },
367 | } = guildClient;
368 |
369 | // Get a role reward
370 | const reward = rewardClient.get(
371 | guildIdOrUrlName,
372 | roleId,
373 | rolePlatformId,
374 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
375 | );
376 |
377 | // Get all rewards of a role
378 | const roleReward = rewardClient.getAll(
379 | guildIdOrUrlName,
380 | roleId,
381 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
382 | );
383 |
384 | // Create a role reward (for example a Discord role) with a guild reward (Discord server)
385 | const createdReward = rewardClient.create(
386 | guildIdOrUrlName,
387 | roleId,
388 | {
389 | guildPlatform: {
390 | // Here we are also creating a guild reward (the Discord server)
391 | platformName: "DISCORD",
392 | platformGuildId: "",
393 | },
394 | platformRoleId: "",
395 | },
396 | signerFunction
397 | );
398 |
399 | // Or create a role reward using an existing guild reward
400 | const createdReward = rewardClient.create(
401 | guildIdOrUrlName,
402 | roleId,
403 | {
404 | guildPlatformId, // Here we are passing the id of an existing guild role (in this case a Discord server)
405 | platformRoleId: "",
406 | },
407 | signerFunction
408 | );
409 |
410 | // Update an existing role reward
411 | const updatedRoleReward = rewardClient.update(
412 | guildIdOrUrlName,
413 | roleId,
414 | rolePlatformId,
415 | { visibility: "HIDDEN" }, // In this example we update a reward's visibility to HIDDEN
416 | signerFunction
417 | );
418 |
419 | // Delete a role reward
420 | rewardClient.delete(guildIdOrUrlName, roleId, rolePlatformId, signerFunction);
421 | ```
422 |
423 | #### `User client`
424 |
425 | ```ts
426 | const { user: userClient } = guildClient;
427 |
428 | // Get a user by numeric ID, or an address
429 | const user = await userClient.get(userIdOrAddress);
430 |
431 | // Get current memberships of a user
432 | const userMemberships = await userClient.getMemberships(
433 | userIdOrAddress,
434 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
435 | );
436 |
437 | // Get a user's profile
438 | const profile = await userClient.getProfile(
439 | userIdOrAddress,
440 | signerFunction // Optional, if a valid signer is provided, the result will contain private data
441 | );
442 |
443 | // Delete a user
444 | await userClient.delete(userIdOrAddress, signerFunction);
445 | ```
446 |
447 | #### `User address client`
448 |
449 | ```ts
450 | const {
451 | user: { address: userAddressClient },
452 | } = guildClient;
453 |
454 | // Get a user address
455 | const userAddress = await userAddressClient.get(
456 | userIdOrAddress, // Used for identifying the guild user
457 | address, // The userAddress with this address will be returned
458 | signerFunction
459 | );
460 |
461 | // Get all addresses of a user
462 | const userAddresses = await userAddressClient.getAll(
463 | userIdOrAddress,
464 | signerFunction
465 | );
466 |
467 | // Create (connect / link) a new user address
468 | const linkedAddress = await userAddressClient.create(
469 | userIdOrAddress,
470 | signerFunctionOfAddressToLink, // Should be a SignerFunction that is derived from the wallet that is being linked to the user. Can be obtained as described above
471 | signerFunction
472 | );
473 |
474 | // Update a user address
475 | const updatedUserAddress = await userAddressClient.update(
476 | userIdOrAddress,
477 | addressToUpdate,
478 | { isPrimary: true }, // In this example we update an address to be primary
479 | signerFunction
480 | );
481 |
482 | // Delete (disconnect / unlink) a user address
483 | await userAddressClient.delete(
484 | userIdOrAddress,
485 | addressToUpdate,
486 | signerFunction
487 | );
488 | ```
489 |
490 | #### `User platform client`
491 |
492 | ```ts
493 | const {
494 | user: { platform: userPlatformClient },
495 | } = guildClient;
496 |
497 | // Get a user platform connection
498 | const userPlatform = await userPlatformClient.get(
499 | userIdOrAddress,
500 | platformId,
501 | signerFunction
502 | );
503 |
504 | // Get all platform connections of a user
505 | const userPlatforms = await userPlatformClient.getAll(
506 | userIdOrAddress,
507 | signerFunction
508 | );
509 |
510 | // Delete (disconnect / unlink) a platform connection
511 | await userPlatformClient.delete(userIdOrAddress, platformId, signerFunction);
512 | ```
513 |
514 | ### Modular / multi-platform architecture
515 |
516 | Guild.xyz no longer limits its platform gating functionalities to a single gateable Discord server or Telegram group. In the new multi-platform architecture you can gate more platforms in a single guild/role.
517 |
518 | The `guildPlatform` entity refers to a platform gated by the guild. It contains information about the gate platform, e.g.: a Discord server's id (`platformGuildId` which is a uniqu identifier of this platform) and optionally some additional data like the `inviteChannel` in the `platformRoleData` property in this case.
519 |
520 | The `rolePlatform` entity connects a `guildPlatform` to a role indicating that this role gives access to that platform. It can also contain some additional information about the platform (`platformRoleId` and `platformRoleData`), in Discord's case it's the Discord-role's id.
521 |
522 | Note that for example in Telegram's case `platformRoleId` is not required; only `platformGuild` (which refers to a telegram group's id) needs to be provided in `guildPlatform`.
523 |
524 | ### Examples
525 |
526 | #### `Example flow from Create Guild to Join`
527 |
528 | ```ts
529 | import { createGuildClient, createSigner } from "@guildxyz/sdk";
530 | import { Wallet } from "ethers";
531 | import { randomBytes } from "crypto";
532 |
533 | // Creating a guild client
534 | const guildClient = createGuildClient("sdk-readme-example");
535 |
536 | // Creating a random wallet for the example
537 | const wallet = new Wallet(randomBytes(32).toString("hex"));
538 |
539 | // Creating a signer function
540 | const signerFunction = createSigner.fromEthersWallet(wallet);
541 |
542 | // Creating a Guild
543 |
544 | await guildClient.guild.create(
545 | {
546 | name: "My New Guild",
547 | urlName: "my-new-guild-123", // Optinal
548 | description: "Cool stuff", // Optional
549 | admins: ["0x916b1aBC3C38852B338a22B08aF19DEe14113627"], // Optional
550 | showMembers: true, // Optional
551 | hideFromExplorer: false, // Optional
552 | theme: [{ color: "#000000" }], // Optional
553 | guildPlatforms: [
554 | // Optional (declaring the gated platforms)
555 | {
556 | platformName: "DISCORD",
557 | platformGuildId: "717317894983225012",
558 | platformGuildData: { inviteChannel: "832195274127999019" },
559 | },
560 | ],
561 | roles: [
562 | {
563 | name: "My First Role",
564 | logic: "AND",
565 | requirements: [
566 | {
567 | type: "ALLOWLIST",
568 | data: {
569 | addresses: [
570 | "0xedd9C1954c77beDD8A2a524981e1ea08C7E484Be",
571 | "0x1b64230Ad5092A4ABeecE1a50Dc7e9e0F0280304",
572 | ],
573 | },
574 | },
575 | ],
576 | rolePlatforms: [
577 | // Optional (connecting gated platforms to the role)
578 | {
579 | guildPlatformIndex: 0,
580 | platformRoleId: "947846353822178118",
581 | },
582 | ],
583 | },
584 | {
585 | name: "My Second Role",
586 | logic: "OR",
587 | requirements: [
588 | {
589 | type: "ERC20",
590 | chain: "ETHEREUM",
591 | address: "0xf76d80200226ac250665139b9e435617e4ba55f9",
592 | data: {
593 | amount: 1,
594 | },
595 | },
596 | {
597 | type: "ERC721",
598 | chain: "ETHEREUM",
599 | address: "0x734AA2dac868218D2A5F9757f16f6f881265441C",
600 | data: {
601 | amount: 1,
602 | },
603 | },
604 | ],
605 | rolePlatforms: [
606 | // Optional (connecting gated platforms to the role)
607 | {
608 | guildPlatformIndex: 0,
609 | platformRoleId: "283446353822178118",
610 | },
611 | ],
612 | },
613 | ],
614 | },
615 | signerFunction
616 | );
617 |
618 | // Joining to a Guild if any role is accessible by the given address
619 | await guildClient.guild.join(myGuild.id, signerFunction);
620 | ```
621 |
622 | #### `Multiple telegram groups guild`
623 |
624 | ```typescript
625 | const myGuild = await guildClient.guild.create(
626 | {
627 | name: "My Telegram Guild",
628 | guildPlatforms: [
629 | {
630 | platformName: "TELEGRAM", // Telegram group 0
631 | platformGuildId: "-1001190870894",
632 | },
633 | {
634 | platformName: "TELEGRAM", // Telegram group 1
635 | platformGuildId: "-1003847238493",
636 | },
637 | {
638 | platformName: "TELEGRAM", // Telegram group 2
639 | platformGuildId: "-1008347384212",
640 | },
641 | ],
642 | roles: [
643 | {
644 | name: "My First Role",
645 | logic: "AND",
646 | requirements: [
647 | {
648 | type: "ALLOWLIST",
649 | data: {
650 | addresses: [
651 | "0xedd9C1954c77beDD8A2a524981e1ea08C7E484Be",
652 | "0x1b64230Ad5092A4ABeecE1a50Dc7e9e0F0280304",
653 | ],
654 | },
655 | },
656 | ],
657 | rolePlatforms: [
658 | {
659 | guildPlatformIndex: 0, // Telegram group 0
660 | },
661 | {
662 | guildPlatformIndex: 2, // Telegram group 2
663 | },
664 | ],
665 | },
666 | {
667 | name: "My Second Role",
668 | logic: "OR",
669 | requirements: [
670 | {
671 | type: "ERC20",
672 | chain: "ETHEREUM",
673 | address: "0xf76d80200226ac250665139b9e435617e4ba55f9",
674 | data: {
675 | amount: 1,
676 | },
677 | },
678 | ],
679 | rolePlatforms: [
680 | {
681 | guildPlatformIndex: 1, // Telegram group 1
682 | },
683 | ],
684 | },
685 | ],
686 | },
687 | signerFunction
688 | );
689 | ```
690 |
--------------------------------------------------------------------------------
/examples/custom-point-distribution/README.md:
--------------------------------------------------------------------------------
1 | # Custom point distribution
2 |
3 | This example script shows how you can distribute points on Guild based on custom data / logic
4 |
5 | ## Setup
6 |
7 | - Install the dependencies: `npm i`
8 | - Set the `PRIVATE_KEY` environment variable: `export PRIVATE_KEY=0x...`
9 | - If you already have a Guild, in which you intend to create the point reward, the account of the private key has to be an admin in that Guild, so it has the necessary permissions to create and update the points
10 |
11 | ## Creating a Guild
12 |
13 | This example app can create a Guild by executing `npx ts-node index.js create-guild`
14 | It will print the Guild's URL, and the relevant ID-s for the point updates
15 |
16 | It sets the following things in the Guild:
17 |
18 | - Basic data (its name, urlName, ...)
19 | - A role with a GUILD_SNAPSHOT requirement. This requirement will be the source of the point distribution
20 | - A POINTS reward, which taks it's input from the requirement
21 |
22 | ## Editing the points
23 |
24 | The script shows how you can edit the points by running `npx ts-node index.js edit {guildId} {roleId} {requirementId}`
25 |
26 | The `snapshot` represents the point distribution. It maps addresses to point values
27 |
28 | > When a snapshot is updated this way, the whole previous `data` field fill be overwritten, so you'll have to call this update with the whole list every time it changes
29 |
--------------------------------------------------------------------------------
/examples/custom-point-distribution/index.ts:
--------------------------------------------------------------------------------
1 | import { createGuildClient, createSigner } from "@guildxyz/sdk";
2 | import { randomBytes } from "crypto";
3 | import { privateKeyToAccount } from "viem/accounts";
4 |
5 | const YOUR_PROJECT_NAME = "snapshot-test-snippet";
6 |
7 | const client = createGuildClient(YOUR_PROJECT_NAME);
8 |
9 | if (!process.env.PRIVATE_KEY) {
10 | throw new Error("Please set the PRIVATE_KEY env var");
11 | }
12 |
13 | // Load an account here (viem is just an example, any library works by implementing the singer function in createSigner.custom())
14 | const viemAccount = privateKeyToAccount(
15 | process.env.PRIVATE_KEY as `0x${string}`
16 | );
17 | // Pass a function which signs a message with the admin account, and the address of this account
18 | const signer = createSigner.custom(
19 | (message) => viemAccount.signMessage({ message }),
20 | viemAccount.address
21 | );
22 |
23 | // If you don't have a guild yet, you can create one by calling this function
24 | async function createGuildWithSnapshot() {
25 | try {
26 | const createdGuild = await client.guild.create(
27 | {
28 | name: "For snapshot testing",
29 | urlName: "snapshot-testing",
30 | contacts: [],
31 | roles: [
32 | {
33 | name: "Test role",
34 | requirements: [
35 | {
36 | type: "GUILD_SNAPSHOT",
37 | data: {
38 | snapshot: [
39 | {
40 | key: "0x0000000000000000000000000000000000000000",
41 | value: 10,
42 | },
43 | ],
44 | },
45 | },
46 | ],
47 | },
48 | ],
49 | },
50 | signer
51 | );
52 |
53 | console.log("Guild created!");
54 |
55 | const guildId = createdGuild.id;
56 | const roleId = createdGuild.roles[0].id;
57 | const requirementId = createdGuild.roles[0].requirements[0].id;
58 |
59 | console.log(createdGuild.guildPlatforms);
60 |
61 | // Created the reward on the guild
62 | await client.guild.role.reward.create(
63 | guildId,
64 | roleId,
65 | {
66 | guildPlatform: {
67 | platformName: "POINTS",
68 | platformGuildId: `unique-name-${randomBytes(4).toString("hex")}`,
69 | platformGuildData: {
70 | name: "Tokens",
71 | },
72 | },
73 | platformRoleData: { score: "0" },
74 | dynamicAmount: {
75 | operation: {
76 | type: "LINEAR",
77 | input: [
78 | {
79 | type: "REQUIREMENT_AMOUNT",
80 | roleId,
81 | requirementId,
82 | },
83 | ],
84 | },
85 | },
86 | },
87 | signer
88 | );
89 | console.log(`https://guild.xyz/${createdGuild.urlName}`);
90 | console.log(
91 | `You can now edit the snapshot with:\nnpx ts-node index.ts edit ${guildId} ${roleId} ${requirementId}`
92 | );
93 | return createdGuild;
94 | } catch (error) {
95 | console.error(error);
96 | throw error;
97 | }
98 | }
99 |
100 | // This function edits an existing snapshot
101 | async function editSnapshot(
102 | guildId: number,
103 | roleId: number,
104 | requirementId: number
105 | ) {
106 | try {
107 | const editedRequirement = await client.guild.role.requirement.update(
108 | guildId,
109 | roleId,
110 | requirementId,
111 | {
112 | data: {
113 | // The provided snapshot overwrites the previous array
114 | snapshot: [
115 | { key: "0x0000000000000000000000000000000000000000", value: 12 },
116 | { key: "0x0000000000000000000000000000000000000001", value: 10 },
117 | ],
118 | },
119 | },
120 | signer
121 | );
122 | console.log("Snapshot edited!");
123 | return editedRequirement;
124 | } catch (error) {
125 | console.error(error);
126 | throw error;
127 | }
128 | }
129 |
130 | /**
131 | * Set the `PRIVATE_KEY` env var. It should be an admin in the snapshot's guild,
132 | * so it has access to edit the snapshot
133 | *
134 | * Create a guild with `npx ts-node index.ts create-guild`
135 | *
136 | * Edit the snapshot with `npx ts-node index.ts edit {guildId} {roleId} {requirementId}`
137 | *
138 | * The three ID-s for editing can be retrieved by the
139 | * https://api.guild.xyz/v2/guilds/guild-page/{urlName} endpoint.
140 | * The creation command also outputs them for the freshly created guild
141 | *
142 | * If the guild is created externally, make sure that the `PRIVATE_KEY`'s account is
143 | * added to the guild as an admin
144 | */
145 | async function main() {
146 | const [, , command, ...params] = process.argv;
147 | switch (command) {
148 | case "create-guild": {
149 | await createGuildWithSnapshot();
150 | break;
151 | }
152 | case "edit": {
153 | const [guildIdStr, roleIdStr, requirementIdStr] = params;
154 | await editSnapshot(+guildIdStr, +roleIdStr, +requirementIdStr);
155 | break;
156 | }
157 | }
158 | }
159 |
160 | main();
161 |
--------------------------------------------------------------------------------
/examples/custom-point-distribution/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "custom-point-distribution",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {},
7 | "author": "",
8 | "license": "ISC",
9 | "dependencies": {
10 | "@guildxyz/sdk": "^2.5.0",
11 | "viem": "^2.17.10"
12 | },
13 | "devDependencies": {
14 | "ts-node": "^10.9.2"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/next-js/.env:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=9c4d98baa956e03db8fa96f45d6aa78e
--------------------------------------------------------------------------------
/examples/next-js/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next-js/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
37 |
38 | !.env
--------------------------------------------------------------------------------
/examples/next-js/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/examples/next-js/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = { reactStrictMode: false };
3 |
4 | module.exports = nextConfig;
5 |
--------------------------------------------------------------------------------
/examples/next-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-js",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@chakra-ui/react": "^2.8.2",
13 | "@emotion/react": "^11.11.3",
14 | "@emotion/styled": "^11.11.0",
15 | "@guildxyz/sdk": "^2.6.8",
16 | "@guildxyz/types": "^1.10.0",
17 | "framer-motion": "^11.0.3",
18 | "next": "latest",
19 | "react": "latest",
20 | "react-dom": "latest",
21 | "swr": "^2.2.4",
22 | "viem": "^1.12.2",
23 | "wagmi": "^1.4.2"
24 | },
25 | "devDependencies": {
26 | "@types/node": "latest",
27 | "@types/react": "latest",
28 | "@types/react-dom": "latest",
29 | "eslint": "latest",
30 | "eslint-config-next": "latest",
31 | "typescript": "latest"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/examples/next-js/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next-js/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next-js/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guildxyz/guild-sdk/75470add817abdac0842847b89757473cf0b0a5c/examples/next-js/src/app/favicon.ico
--------------------------------------------------------------------------------
/examples/next-js/src/app/globals.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --max-width: 1100px;
3 | --border-radius: 12px;
4 | --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
5 | 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
6 | 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
7 |
8 | --foreground-rgb: 0, 0, 0;
9 | --background-start-rgb: 214, 219, 220;
10 | --background-end-rgb: 255, 255, 255;
11 |
12 | --primary-glow: conic-gradient(
13 | from 180deg at 50% 50%,
14 | #16abff33 0deg,
15 | #0885ff33 55deg,
16 | #54d6ff33 120deg,
17 | #0071ff33 160deg,
18 | transparent 360deg
19 | );
20 | --secondary-glow: radial-gradient(
21 | rgba(255, 255, 255, 1),
22 | rgba(255, 255, 255, 0)
23 | );
24 |
25 | --tile-start-rgb: 239, 245, 249;
26 | --tile-end-rgb: 228, 232, 233;
27 | --tile-border: conic-gradient(
28 | #00000080,
29 | #00000040,
30 | #00000030,
31 | #00000020,
32 | #00000010,
33 | #00000010,
34 | #00000080
35 | );
36 |
37 | --callout-rgb: 238, 240, 241;
38 | --callout-border-rgb: 172, 175, 176;
39 | --card-rgb: 180, 185, 188;
40 | --card-border-rgb: 131, 134, 135;
41 | }
42 |
43 | @media (prefers-color-scheme: dark) {
44 | :root {
45 | --foreground-rgb: 255, 255, 255;
46 | --background-start-rgb: 0, 0, 0;
47 | --background-end-rgb: 0, 0, 0;
48 |
49 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
50 | --secondary-glow: linear-gradient(
51 | to bottom right,
52 | rgba(1, 65, 255, 0),
53 | rgba(1, 65, 255, 0),
54 | rgba(1, 65, 255, 0.3)
55 | );
56 |
57 | --tile-start-rgb: 2, 13, 46;
58 | --tile-end-rgb: 2, 5, 19;
59 | --tile-border: conic-gradient(
60 | #ffffff80,
61 | #ffffff40,
62 | #ffffff30,
63 | #ffffff20,
64 | #ffffff10,
65 | #ffffff10,
66 | #ffffff80
67 | );
68 |
69 | --callout-rgb: 20, 20, 20;
70 | --callout-border-rgb: 108, 108, 108;
71 | --card-rgb: 100, 100, 100;
72 | --card-border-rgb: 200, 200, 200;
73 | }
74 | }
75 |
76 | * {
77 | box-sizing: border-box;
78 | padding: 0;
79 | margin: 0;
80 | }
81 |
82 | html,
83 | body {
84 | max-width: 100vw;
85 | overflow-x: hidden;
86 | }
87 |
88 | body {
89 | color: rgb(var(--foreground-rgb));
90 | background: linear-gradient(
91 | to bottom,
92 | transparent,
93 | rgb(var(--background-end-rgb))
94 | )
95 | rgb(var(--background-start-rgb));
96 | }
97 |
98 | a {
99 | color: inherit;
100 | text-decoration: none;
101 | }
102 |
103 | @media (prefers-color-scheme: dark) {
104 | html {
105 | color-scheme: dark;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/examples/next-js/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Inter } from "next/font/google";
4 | import React from "react";
5 | import { createPublicClient, http } from "viem";
6 | import { polygon } from "viem/chains";
7 | import { WagmiConfig, createConfig } from "wagmi";
8 | import "../lib/guild";
9 | import "./globals.css";
10 |
11 | const inter = Inter({ subsets: ["latin"] });
12 |
13 | const config = createConfig({
14 | autoConnect: true,
15 | publicClient: createPublicClient({
16 | chain: polygon,
17 | transport: http(),
18 | }),
19 | });
20 |
21 | export default function RootLayout({
22 | children,
23 | }: {
24 | children: React.ReactNode;
25 | }) {
26 | return (
27 |
28 |
29 | {children}
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/examples/next-js/src/app/page.module.css:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | align-items: center;
6 | padding: 6rem;
7 | min-height: 100vh;
8 | }
9 |
10 | .description {
11 | display: inherit;
12 | justify-content: inherit;
13 | align-items: inherit;
14 | font-size: 0.85rem;
15 | max-width: var(--max-width);
16 | width: 100%;
17 | z-index: 2;
18 | font-family: var(--font-mono);
19 | }
20 |
21 | .description a {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | gap: 0.5rem;
26 | }
27 |
28 | .description p {
29 | position: relative;
30 | margin: 0;
31 | padding: 1rem;
32 | background-color: rgba(var(--callout-rgb), 0.5);
33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34 | border-radius: var(--border-radius);
35 | }
36 |
37 | .code {
38 | font-weight: 700;
39 | font-family: var(--font-mono);
40 | }
41 |
42 | .grid {
43 | display: grid;
44 | grid-template-columns: repeat(4, minmax(25%, auto));
45 | max-width: 100%;
46 | width: var(--max-width);
47 | }
48 |
49 | .card {
50 | padding: 1rem 1.2rem;
51 | border-radius: var(--border-radius);
52 | background: rgba(var(--card-rgb), 0);
53 | border: 1px solid rgba(var(--card-border-rgb), 0);
54 | transition: background 200ms, border 200ms;
55 | }
56 |
57 | .card span {
58 | display: inline-block;
59 | transition: transform 200ms;
60 | }
61 |
62 | .card h2 {
63 | font-weight: 600;
64 | margin-bottom: 0.7rem;
65 | }
66 |
67 | .card p {
68 | margin: 0;
69 | opacity: 0.6;
70 | font-size: 0.9rem;
71 | line-height: 1.5;
72 | max-width: 30ch;
73 | }
74 |
75 | .center {
76 | display: flex;
77 | justify-content: center;
78 | align-items: center;
79 | position: relative;
80 | padding: 4rem 0;
81 | }
82 |
83 | .center::before {
84 | background: var(--secondary-glow);
85 | border-radius: 50%;
86 | width: 480px;
87 | height: 360px;
88 | margin-left: -400px;
89 | }
90 |
91 | .center::after {
92 | background: var(--primary-glow);
93 | width: 240px;
94 | height: 180px;
95 | z-index: -1;
96 | }
97 |
98 | .center::before,
99 | .center::after {
100 | content: '';
101 | left: 50%;
102 | position: absolute;
103 | filter: blur(45px);
104 | transform: translateZ(0);
105 | }
106 |
107 | .logo {
108 | position: relative;
109 | }
110 | /* Enable hover only on non-touch devices */
111 | @media (hover: hover) and (pointer: fine) {
112 | .card:hover {
113 | background: rgba(var(--card-rgb), 0.1);
114 | border: 1px solid rgba(var(--card-border-rgb), 0.15);
115 | }
116 |
117 | .card:hover span {
118 | transform: translateX(4px);
119 | }
120 | }
121 |
122 | @media (prefers-reduced-motion) {
123 | .card:hover span {
124 | transform: none;
125 | }
126 | }
127 |
128 | /* Mobile */
129 | @media (max-width: 700px) {
130 | .content {
131 | padding: 4rem;
132 | }
133 |
134 | .grid {
135 | grid-template-columns: 1fr;
136 | margin-bottom: 120px;
137 | max-width: 320px;
138 | text-align: center;
139 | }
140 |
141 | .card {
142 | padding: 1rem 2.5rem;
143 | }
144 |
145 | .card h2 {
146 | margin-bottom: 0.5rem;
147 | }
148 |
149 | .center {
150 | padding: 8rem 0 6rem;
151 | }
152 |
153 | .center::before {
154 | transform: none;
155 | height: 300px;
156 | }
157 |
158 | .description {
159 | font-size: 0.8rem;
160 | }
161 |
162 | .description a {
163 | padding: 1rem;
164 | }
165 |
166 | .description p,
167 | .description div {
168 | display: flex;
169 | justify-content: center;
170 | position: fixed;
171 | width: 100%;
172 | }
173 |
174 | .description p {
175 | align-items: center;
176 | inset: 0 0 auto;
177 | padding: 2rem 1rem 1.4rem;
178 | border-radius: 0;
179 | border: none;
180 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
181 | background: linear-gradient(
182 | to bottom,
183 | rgba(var(--background-start-rgb), 1),
184 | rgba(var(--callout-rgb), 0.5)
185 | );
186 | background-clip: padding-box;
187 | backdrop-filter: blur(24px);
188 | }
189 |
190 | .description div {
191 | align-items: flex-end;
192 | pointer-events: none;
193 | inset: auto 0 0;
194 | padding: 2rem;
195 | height: 200px;
196 | background: linear-gradient(
197 | to bottom,
198 | transparent 0%,
199 | rgb(var(--background-end-rgb)) 40%
200 | );
201 | z-index: 1;
202 | }
203 | }
204 |
205 | /* Tablet and Smaller Desktop */
206 | @media (min-width: 701px) and (max-width: 1120px) {
207 | .grid {
208 | grid-template-columns: repeat(2, 50%);
209 | }
210 | }
211 |
212 | @media (prefers-color-scheme: dark) {
213 | .vercelLogo {
214 | filter: invert(1);
215 | }
216 |
217 | .logo {
218 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
219 | }
220 | }
221 |
222 | @keyframes rotate {
223 | from {
224 | transform: rotate(360deg);
225 | }
226 | to {
227 | transform: rotate(0deg);
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/examples/next-js/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Button,
5 | ChakraProvider,
6 | HStack,
7 | ListItem,
8 | OrderedList,
9 | Spinner,
10 | Stack,
11 | Text,
12 | UnorderedList,
13 | } from "@chakra-ui/react";
14 | import { createSigner } from "@guildxyz/sdk";
15 | import { UserProfile } from "@guildxyz/types";
16 | import { useState } from "react";
17 | import useSWR from "swr";
18 | import { useAccount, useConnect, useDisconnect, useSignMessage } from "wagmi";
19 | import { InjectedConnector } from "wagmi/connectors/injected";
20 | import { WalletConnectConnector } from "wagmi/connectors/walletConnect";
21 | import guildClient from "../lib/guild";
22 |
23 | // Id of Our Guild (https://guild.xyz/our-guild)
24 | // You can check your guild's id with the following endpoint:
25 | // https://api.guild.xyz/v2/guilds/our-guild
26 | const GUILD_ID = 1985;
27 |
28 | function fetchUserMembershipsInGuild(address: `0x${string}`, guildId: number) {
29 | return guildClient.user
30 | .getMemberships(address)
31 | .then((results) => results.find((item) => item.guildId === guildId));
32 | }
33 |
34 | function fetchRoleNames(guildId: number) {
35 | return guildClient.guild.role
36 | .getAll(guildId)
37 | .then((roles) =>
38 | Object.fromEntries(roles.map(({ id, name }) => [id, name]))
39 | );
40 | }
41 |
42 | async function fetchLeaderboard(
43 | guildIdOrUrlName: number | string,
44 | isAllUser: boolean = false
45 | ) {
46 | const rewards = await guildClient.guild.reward.getAll(guildIdOrUrlName);
47 |
48 | // platformId === 13 means that the reward is point-based
49 | const pointsReward = rewards.find((reward) => reward.platformId === 13);
50 |
51 | // The guildPlatformId parameter could also be hardcoded
52 | // isAllUser means, that the response contains the whole leaderboard, while the value is false, it returns the first 500 user & address
53 | return guildClient.guild.getLeaderboard(
54 | guildIdOrUrlName,
55 | pointsReward!.id,
56 | undefined,
57 | isAllUser
58 | );
59 | }
60 |
61 | export default function Home() {
62 | const { address } = useAccount();
63 |
64 | const { connect: connectInjected } = useConnect({
65 | connector: new InjectedConnector(),
66 | });
67 | const { connect: connectWalletConnect } = useConnect({
68 | connector: new WalletConnectConnector({
69 | options: { projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID },
70 | }),
71 | });
72 | const { disconnect } = useDisconnect();
73 | const { signMessageAsync } = useSignMessage();
74 |
75 | const [profile, setProfile] = useState();
76 |
77 | const { data: userMemberships, isLoading: isUserMembershipsLoading } = useSWR(
78 | !!address ? ["memberships", address, GUILD_ID] : null,
79 | ([, ...props]) => fetchUserMembershipsInGuild(...props)
80 | );
81 |
82 | const { data: roles, isLoading: isRolesLoading } = useSWR(
83 | ["roles", GUILD_ID],
84 | ([, ...props]) => fetchRoleNames(...props)
85 | );
86 |
87 | const { data: leaderboard, isLoading: isLeaderboardLoading } = useSWR(
88 | ["leaderboard", "walletconnect", false],
89 | ([, ...params]) => fetchLeaderboard(...params)
90 | );
91 |
92 | return (
93 |
94 |
95 | {address ? (
96 | <>
97 |
98 | Connected to {address}
99 |
100 | disconnect()}>Disconnect
101 |
102 | >
103 | ) : (
104 |
105 | connectInjected()}>Connect Injected
106 | connectWalletConnect()}>
107 | Connect WalletConnect
108 |
109 |
110 | )}
111 |
112 | {!!address && (
113 | <>
114 | Fetch user profile
115 | {!profile ? (
116 |
118 | guildClient.user
119 | .getProfile(
120 | address,
121 | createSigner.custom(
122 | (message) => signMessageAsync({ message }),
123 | address
124 | )
125 | )
126 | .then(setProfile)
127 | }
128 | >
129 | Call Guild API
130 |
131 | ) : (
132 | {JSON.stringify(profile)}
133 | )}
134 | >
135 | )}
136 |
137 | List Memberships
138 |
139 | {isUserMembershipsLoading || isRolesLoading ? (
140 |
141 | ) : !userMemberships || !roles ? (
142 | No data
143 | ) : (
144 |
145 | {userMemberships.roleIds.map((roleId) => (
146 |
147 | {roles[roleId]} (#{roleId})
148 |
149 | ))}
150 |
151 | )}
152 |
153 | Listing Point Leaderboard
154 |
155 | {leaderboard?.isRevalidating && (
156 |
157 | Leaderboard is currently revalidating, current data might be
158 | inconsistent.
159 |
160 | )}
161 |
162 | {isLeaderboardLoading ? (
163 |
164 | ) : !leaderboard ? (
165 | No data
166 | ) : (
167 |
168 | {leaderboard.leaderboard.map(({ userId, address, totalPoints }) => (
169 |
170 | {address} ({totalPoints} points)
171 |
172 | ))}
173 |
174 | )}
175 |
176 |
177 | );
178 | }
179 |
--------------------------------------------------------------------------------
/examples/next-js/src/lib/guild.ts:
--------------------------------------------------------------------------------
1 | import { createGuildClient } from "@guildxyz/sdk";
2 |
3 | const client = createGuildClient("next-js-example-app");
4 |
5 |
6 | export default client
7 |
--------------------------------------------------------------------------------
/examples/next-js/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@guildxyz/sdk",
3 | "version": "2.6.8",
4 | "description": "SDK for Guild.xyz Public API ",
5 | "main": "./dist/index.js",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/guildxyz/guild-sdk.git"
11 | },
12 | "keywords": [
13 | "guild",
14 | "guildxyz",
15 | "guildauth",
16 | "agoraxyz",
17 | "agoraspace"
18 | ],
19 | "author": {
20 | "name": "baloo",
21 | "email": "cs-balazs@guild.xyz"
22 | },
23 | "contributors": [
24 | {
25 | "name": "ejay",
26 | "email": "ejay@guild.xyz"
27 | },
28 | {
29 | "name": "Devid",
30 | "email": "devid@guild.xyz"
31 | }
32 | ],
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/guildxyz/guild-sdk/issues"
36 | },
37 | "homepage": "https://guild.xyz",
38 | "scripts": {
39 | "build": "tsup ./src/index.ts",
40 | "prepublishOnly": "npm run build",
41 | "prepare": "npm run build",
42 | "test": "npx dotenv-cli -e .test.env -- npx vitest run",
43 | "testWithPrivileged": "npx dotenv-cli -e .test.privileged.env -- npm run test"
44 | },
45 | "dependencies": {
46 | "@guildxyz/types": "^1.9.42",
47 | "ethers": "^6.7.1",
48 | "randombytes": "^2.1.0"
49 | },
50 | "devDependencies": {
51 | "@guildxyz/queues": "^0.0.8",
52 | "@types/node": "^20.6.3",
53 | "@types/randombytes": "^2.0.2",
54 | "@typescript-eslint/eslint-plugin": "^6.7.2",
55 | "@typescript-eslint/parser": "^6.7.2",
56 | "dotenv-cli": "^7.3.0",
57 | "eslint": "^8.49.0",
58 | "eslint-config-airbnb-base": "^15.0.0",
59 | "eslint-config-prettier": "^9.0.0",
60 | "lint-staged": "^14.0.1",
61 | "prettier": "^3.0.3",
62 | "pretty-quick": "^3.1.3",
63 | "tsup": "^7.2.0",
64 | "typescript": "^5.2.2",
65 | "vitest": "^0.34.5",
66 | "zod": "^3.22.2"
67 | },
68 | "lint-staged": {
69 | "*.{js,ts}": "eslint --fix --cache --cache-location 'node_modules/.cache/.eslintcache'",
70 | "*.{js,ts,md}": "pretty-quick --staged"
71 | },
72 | "tsup": {
73 | "format": [
74 | "esm",
75 | "cjs"
76 | ],
77 | "dts": {
78 | "resolve": true,
79 | "entry": [
80 | "./src/index.ts"
81 | ],
82 | "compilerOptions": {
83 | "moduleResolution": "node",
84 | "strict": true,
85 | "strictNullChecks": true
86 | }
87 | },
88 | "clean": true,
89 | "target": "esnext"
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/client.ts:
--------------------------------------------------------------------------------
1 | import guild from "./clients/guild";
2 | import platform from "./clients/platform";
3 | import user from "./clients/user";
4 | import { setProjectName } from "./common";
5 |
6 | const createGuildClient = (projectName: string) => {
7 | if (typeof projectName !== "string" || projectName.length <= 0)
8 | throw Error("Project name should be a non-empty string");
9 |
10 | setProjectName(projectName);
11 |
12 | return { guild, platform, user };
13 | };
14 |
15 | export type GuildClient = ReturnType
16 |
17 | export default createGuildClient;
18 |
--------------------------------------------------------------------------------
/src/clients/guild.ts:
--------------------------------------------------------------------------------
1 | import {
2 | GetGuildMembersResponse,
3 | GetLeaderboardResponse,
4 | Guild,
5 | GuildCreationResponse,
6 | Schemas,
7 | } from "@guildxyz/types";
8 | import {
9 | SignerFunction,
10 | callGuildAPI,
11 | castDateInLeaderboardItem,
12 | } from "../utils";
13 | import guildAdmin from "./guildAdmin";
14 | import guildReward from "./guildReward";
15 | import role from "./role";
16 |
17 | const guild = {
18 | role,
19 |
20 | reward: guildReward,
21 |
22 | admin: guildAdmin,
23 |
24 | get: (guildIdOrUrlName: number | string) =>
25 | callGuildAPI({ url: `/guilds/${guildIdOrUrlName}`, method: "GET" }),
26 |
27 | getMany: (guildIds: number[]) =>
28 | callGuildAPI({
29 | url: `/guilds`,
30 | method: "GET",
31 | queryParams: {
32 | guildIds: guildIds.join(","),
33 | },
34 | queryParamsSchema: "GuildGetManyQueryParamsSchema",
35 | }),
36 |
37 | search: (params: Schemas["GuildSearchQueryParams"]) =>
38 | callGuildAPI({
39 | url: `/guilds`,
40 | method: "GET",
41 | queryParams: params,
42 | queryParamsSchema: "GuildSearchQueryParamsSchema",
43 | }),
44 |
45 | /**
46 | * If a signer is provided, the response will include an aroundUser field, which holds leaderboard items from around the signer user
47 | */
48 | getLeaderboard: (
49 | guildIdOrUrlName: number | string,
50 | guildPlatformId: number,
51 | signer?: SignerFunction,
52 | isAllUser: boolean = false,
53 | forceRecalculate: boolean = false
54 | ) =>
55 | callGuildAPI({
56 | url: `/guilds/${guildIdOrUrlName}/points/${guildPlatformId}/leaderboard?isAllUser=${isAllUser}&forceRecalculate=${forceRecalculate}`,
57 | method: "GET",
58 | signer,
59 | }).then(
60 | (result) =>
61 | {
62 | aroundUser: result.aroundUser?.map(castDateInLeaderboardItem),
63 | leaderboard: result.leaderboard.map(castDateInLeaderboardItem),
64 | isRevalidating: !!result.isRevalidating,
65 | }
66 | ),
67 |
68 | getMembers: (guildId: number, signer?: SignerFunction) =>
69 | callGuildAPI({
70 | url: `/guilds/${guildId}/members`,
71 | method: "GET",
72 | signer,
73 | }),
74 |
75 | getUserMemberships: (
76 | guildId: number,
77 | userId: number,
78 | signer?: SignerFunction
79 | ) =>
80 | callGuildAPI>({
81 | url: `/guilds/${guildId}/members/${userId}`,
82 | method: "GET",
83 | signer,
84 | }),
85 |
86 | create: (
87 | guildCreationParams: Schemas["GuildCreationPayload"],
88 | signer: SignerFunction
89 | ) =>
90 | callGuildAPI({
91 | url: `/guilds`,
92 | method: "POST",
93 | signer,
94 | body: {
95 | data: guildCreationParams,
96 | schema: "GuildCreationPayloadSchema",
97 | },
98 | }),
99 |
100 | update: (
101 | guildId: number,
102 | guildUpdateParams: Schemas["GuildUpdatePayload"],
103 | signer: SignerFunction
104 | ) =>
105 | callGuildAPI({
106 | url: `/guilds/${guildId}`,
107 | method: "PUT",
108 | body: {
109 | data: guildUpdateParams,
110 | schema: "GuildUpdatePayloadSchema",
111 | },
112 | signer,
113 | }),
114 |
115 | delete: (guildId: number, signer: SignerFunction) =>
116 | callGuildAPI({
117 | url: `/guilds/${guildId}`,
118 | method: "DELETE",
119 | signer,
120 | }),
121 |
122 | join: (guildId: number, signer: SignerFunction) =>
123 | callGuildAPI<{ success: boolean; accessedRoleIds: number[] }>({
124 | url: `/v1/user/join`,
125 | method: "POST",
126 | body: {
127 | schema: "JoinActionPayloadSchema",
128 | data: {
129 | guildId,
130 | },
131 | },
132 | signer,
133 | }),
134 |
135 | // join: (
136 | // guildId: number,
137 | // signer: SignerFunction,
138 | // pollOptions?: PollOptions
139 | // ) =>
140 | // createAndAwaitJob(
141 | // "/actions/join",
142 | // {
143 | // schema: "JoinActionPayloadSchema",
144 | // data: { guildId },
145 | // },
146 | // { guildId },
147 | // signer,
148 | // pollOptions
149 | // ),
150 |
151 | accessCheck: (guildId: number, signer: SignerFunction) =>
152 | callGuildAPI<
153 | Array<{
154 | roleId: number;
155 | access: boolean | null;
156 | requirements: Array<{ requirementId: number; access: boolean | null }>;
157 | errors: Array<{
158 | requirementId: number;
159 | msg: string;
160 | errorType: string;
161 | subType: string;
162 | }>;
163 | }>
164 | >({
165 | url: `/v1/guild/access/${guildId}/0x0000000000000000000000000000000000000000`,
166 | method: "GET",
167 | signer,
168 | }),
169 |
170 | // accessCheck: (
171 | // guildId: number,
172 | // signer: SignerFunction,
173 | // pollOptions?: PollOptions
174 | // ) =>
175 | // createAndAwaitJob(
176 | // "/actions/access-check",
177 | // {
178 | // schema: "JoinActionPayloadSchema",
179 | // data: { guildId },
180 | // },
181 | // { guildId },
182 | // signer,
183 | // pollOptions
184 | // ),
185 |
186 | // statusUpdate: async (
187 | // guildId: number,
188 | // signer: SignerFunction,
189 | // pollOptions?: PollOptions
190 | // ) => {
191 | // const roles = await guild.role.getAll(guildId, signer);
192 | // return createAndAwaitJob(
193 | // "/actions/status-update",
194 | // {
195 | // schema: "StatusUpdateActionPayloadSchema",
196 | // data: {
197 | // roleIds: (roles ?? []).map(({ id }) => id),
198 | // },
199 | // },
200 | // { guildId },
201 | // signer,
202 | // pollOptions
203 | // );
204 | // },
205 | };
206 |
207 | export default guild;
208 |
--------------------------------------------------------------------------------
/src/clients/guildAdmin.ts:
--------------------------------------------------------------------------------
1 | import { GuildAdmin, Schemas } from "@guildxyz/types";
2 | import { SignerFunction, callGuildAPI } from "../utils";
3 |
4 | const guildAdmin = {
5 | get: (guildIdOrUrlName: string | number, userId: number) =>
6 | callGuildAPI({
7 | url: `/guilds/${guildIdOrUrlName}/admins/${userId}`,
8 | method: "GET",
9 | }),
10 |
11 | getAll: (guildIdOrUrlName: string | number) =>
12 | callGuildAPI({
13 | url: `/guilds/${guildIdOrUrlName}/admins`,
14 | method: "GET",
15 | }),
16 |
17 | create: (
18 | guildIdOrUrlName: string | number,
19 | guildAdminCreationParams: Schemas["GuildAdminCreationPayload"],
20 | signer: SignerFunction
21 | ) =>
22 | callGuildAPI({
23 | url: `/guilds/${guildIdOrUrlName}/admins`,
24 | method: "POST",
25 | body: {
26 | schema: "GuildAdminCreationPayloadSchema",
27 | data: guildAdminCreationParams,
28 | },
29 | signer,
30 | }),
31 |
32 | update: (
33 | guildIdOrUrlName: string | number,
34 | adminUserId: number,
35 | guildAdminUpdateParams: Schemas["GuildAdminUpdatePayload"],
36 | signer: SignerFunction
37 | ) =>
38 | callGuildAPI({
39 | url: `/guilds/${guildIdOrUrlName}/admins/${adminUserId}`,
40 | method: "PUT",
41 | body: {
42 | schema: "GuildAdminUpdatePayloadSchema",
43 | data: guildAdminUpdateParams,
44 | },
45 | signer,
46 | }),
47 |
48 | delete: (
49 | guildIdOrUrlName: string | number,
50 | adminUserId: number,
51 | signer: SignerFunction
52 | ) =>
53 | callGuildAPI({
54 | url: `/guilds/${guildIdOrUrlName}/admins/${adminUserId}`,
55 | method: "DELETE",
56 | signer,
57 | }),
58 | };
59 |
60 | export default guildAdmin;
61 |
--------------------------------------------------------------------------------
/src/clients/guildReward.ts:
--------------------------------------------------------------------------------
1 | import { GuildReward, Schemas } from "@guildxyz/types";
2 | import { SignerFunction, callGuildAPI } from "../utils";
3 |
4 | const guildReward = {
5 | get: (
6 | guildIdOrUrlName: string | number,
7 | guildPlatformId: number,
8 | signer?: SignerFunction
9 | ) =>
10 | callGuildAPI({
11 | url: `/guilds/${guildIdOrUrlName}/guild-platforms/${guildPlatformId}`,
12 | method: "GET",
13 | signer,
14 | }),
15 |
16 | getAll: (guildIdOrUrlName: string | number, signer?: SignerFunction) =>
17 | callGuildAPI({
18 | url: `/guilds/${guildIdOrUrlName}/guild-platforms`,
19 | method: "GET",
20 | signer,
21 | }),
22 |
23 | create: (
24 | guildIdOrUrlName: string | number,
25 | guildPlatformCreationParams: Schemas["GuildRewardCreation"],
26 | signer: SignerFunction
27 | ) =>
28 | callGuildAPI({
29 | url: `/guilds/${guildIdOrUrlName}/guild-platforms`,
30 | method: "POST",
31 | body: {
32 | schema: "GuildRewardCreationSchema",
33 | data: guildPlatformCreationParams,
34 | },
35 | signer,
36 | }),
37 |
38 | update: (
39 | guildIdOrUrlName: string | number,
40 | guildPlatformId: number,
41 | guildPlatformUpdateParams: Schemas["GuildRewardUpdate"],
42 | signer: SignerFunction
43 | ) =>
44 | callGuildAPI({
45 | url: `/guilds/${guildIdOrUrlName}/guild-platforms/${guildPlatformId}`,
46 | method: "PUT",
47 | body: {
48 | schema: "GuildRewardUpdateSchema",
49 | data: guildPlatformUpdateParams,
50 | },
51 | signer,
52 | }),
53 |
54 | delete: (
55 | guildIdOrUrlName: string | number,
56 | guildPlatformId: number,
57 | signer: SignerFunction
58 | ) =>
59 | callGuildAPI({
60 | url: `/guilds/${guildIdOrUrlName}/guild-platforms/${guildPlatformId}`,
61 | method: "DELETE",
62 | signer,
63 | }),
64 | };
65 |
66 | export default guildReward;
67 |
--------------------------------------------------------------------------------
/src/clients/platform.ts:
--------------------------------------------------------------------------------
1 | import {
2 | GuildByPlatformResponse,
3 | PlatformName,
4 | User,
5 | UserGuildAccessesByPlatformResponse,
6 | } from "@guildxyz/types";
7 | import { SignerFunction, callGuildAPI } from "../utils";
8 |
9 | const platform = {
10 | getGuildByPlatform: (
11 | platformName: PlatformName,
12 | platformGuildId: string,
13 | signer?: SignerFunction
14 | ) =>
15 | callGuildAPI({
16 | url: `/platforms/${platformName}/guilds/${platformGuildId}`,
17 | method: "GET",
18 | signer,
19 | }),
20 |
21 | getUserGuildAccessByPlatform: (
22 | platformName: PlatformName,
23 | platformGuildId: string,
24 | platformUserId: string,
25 | signer?: SignerFunction
26 | ) =>
27 | callGuildAPI({
28 | url: `/platforms/${platformName}/guilds/${platformGuildId}/users/${platformUserId}/access`,
29 | method: "GET",
30 | signer,
31 | }),
32 |
33 | withPlatformName: (platformName: PlatformName) => ({
34 | getGuildByPlatform: (platformGuildId: string, signer?: SignerFunction) =>
35 | platform.getGuildByPlatform(platformName, platformGuildId, signer),
36 |
37 | getUserGuildAccessByPlatform: (
38 | platformGuildId: string,
39 | platformUserId: string,
40 | signer?: SignerFunction
41 | ) =>
42 | platform.getUserGuildAccessByPlatform(
43 | platformName,
44 | platformGuildId,
45 | platformUserId,
46 | signer
47 | ),
48 |
49 | getUserByPlatformUserId: (platformUserId: string, signer: SignerFunction) =>
50 | callGuildAPI({
51 | url: `/platforms/${platformName}/users/${platformUserId}`,
52 | method: "GET",
53 | signer,
54 | }),
55 | }),
56 | };
57 |
58 | export default platform;
59 |
--------------------------------------------------------------------------------
/src/clients/platformUser.ts:
--------------------------------------------------------------------------------
1 | import { PlatformUser, Schemas } from "@guildxyz/types";
2 | import { SignerFunction, callGuildAPI } from "../utils";
3 |
4 | const platformUser = {
5 | get: (
6 | userIdOrAddress: string | number,
7 | platformId: number,
8 | signer: SignerFunction
9 | ) =>
10 | callGuildAPI({
11 | url: `/users/${userIdOrAddress}/platform-users/${platformId}`,
12 | method: "GET",
13 | signer,
14 | }),
15 |
16 | getAll: (userIdOrAddress: string | number, signer: SignerFunction) =>
17 | callGuildAPI({
18 | url: `/users/${userIdOrAddress}/platform-users`,
19 | method: "GET",
20 | signer,
21 | }),
22 |
23 | create: (
24 | userIdOrAddress: string | number,
25 | platformUserCreationParams: Schemas["PlatformUserCreation"],
26 | signer: SignerFunction
27 | ) =>
28 | callGuildAPI({
29 | url: `/users/${userIdOrAddress}/platform-users`,
30 | method: "POST",
31 | body: {
32 | schema: "PlatformUserCreationSchema",
33 | data: platformUserCreationParams,
34 | },
35 | signer,
36 | }),
37 |
38 | delete: (
39 | userIdOrAddress: string | number,
40 | platformId: number,
41 | signer: SignerFunction
42 | ) =>
43 | callGuildAPI({
44 | url: `/users/${userIdOrAddress}/platform-users/${platformId}`,
45 | method: "DELETE",
46 | signer,
47 | }),
48 | };
49 |
50 | export default platformUser;
51 |
--------------------------------------------------------------------------------
/src/clients/requirement.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Requirement,
3 | RequirementUpdatePayload,
4 | Schemas,
5 | } from "@guildxyz/types";
6 | import { SignerFunction, callGuildAPI } from "../utils";
7 |
8 | const requirement = {
9 | get: (
10 | guildIdOrUrlName: string | number,
11 | roleId: number,
12 | requirementId: number,
13 | signer?: SignerFunction
14 | ) =>
15 | callGuildAPI({
16 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/requirements/${requirementId}`,
17 | method: "GET",
18 | signer,
19 | }),
20 |
21 | getAll: (
22 | guildIdOrUrlName: string | number,
23 | roleId: number,
24 | signer?: SignerFunction
25 | ) =>
26 | callGuildAPI({
27 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/requirements`,
28 | method: "GET",
29 | signer,
30 | }),
31 |
32 | create: (
33 | guildIdOrUrlName: string | number,
34 | roleId: number,
35 | requirementCreationParams: Schemas["RequirementCreationPayload"],
36 | signer: SignerFunction
37 | ) =>
38 | callGuildAPI({
39 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/requirements`,
40 | method: "POST",
41 | body: {
42 | data: requirementCreationParams,
43 | schema: "RequirementCreationPayloadSchema",
44 | },
45 | signer,
46 | }),
47 |
48 | update: (
49 | guildIdOrUrlName: string | number,
50 | roleId: number,
51 | requirementId: number,
52 | requirementUpdateParams: RequirementUpdatePayload,
53 | signer: SignerFunction
54 | ) =>
55 | callGuildAPI({
56 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/requirements/${requirementId}`,
57 | method: "PUT",
58 | body: {
59 | data: requirementUpdateParams,
60 | schema: "RequirementUpdatePayloadSchema",
61 | },
62 | signer,
63 | }),
64 |
65 | delete: (
66 | guildIdOrUrlName: string | number,
67 | roleId: number,
68 | requirementId: number,
69 | signer: SignerFunction
70 | ) =>
71 | callGuildAPI({
72 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/requirements/${requirementId}`,
73 | method: "DELETE",
74 | signer,
75 | }),
76 | };
77 |
78 | export default requirement;
79 |
--------------------------------------------------------------------------------
/src/clients/role.ts:
--------------------------------------------------------------------------------
1 | import { Role, RoleCreationResponse, Schemas } from "@guildxyz/types";
2 | import { SignerFunction, callGuildAPI } from "../utils";
3 | import requirement from "./requirement";
4 | import rolePlatform from "./rolePlatform";
5 |
6 | const role = {
7 | requirement,
8 |
9 | reward: rolePlatform,
10 |
11 | get: (
12 | guildIdOrUrlName: number | string,
13 | roleId: number,
14 | signer?: SignerFunction
15 | ) =>
16 | callGuildAPI({
17 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}`,
18 | method: "GET",
19 | signer,
20 | }),
21 |
22 | getAll: (guildIdOrUrlName: number | string, signer?: SignerFunction) =>
23 | callGuildAPI({
24 | url: `/guilds/${guildIdOrUrlName}/roles`,
25 | method: "GET",
26 | signer,
27 | }),
28 |
29 | create: (
30 | guildIdOrUrlName: number | string,
31 | roleCreationParams: Schemas["RoleCreationPayload"],
32 | signer: SignerFunction
33 | ) =>
34 | callGuildAPI({
35 | url: `/guilds/${guildIdOrUrlName}/roles`,
36 | method: "POST",
37 | body: {
38 | data: roleCreationParams,
39 | schema: "RoleCreationPayloadSchema",
40 | },
41 | signer,
42 | }),
43 |
44 | update: (
45 | guildIdOrUrlName: number | string,
46 | roleId: number,
47 | roleUpdateParams: Schemas["RoleUpdatePayload"],
48 | signer: SignerFunction
49 | ) =>
50 | callGuildAPI({
51 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}`,
52 | method: "PUT",
53 | body: {
54 | data: roleUpdateParams,
55 | schema: "RoleUpdatePayloadSchema",
56 | },
57 | signer,
58 | }),
59 |
60 | delete: (
61 | guildIdOrUrlName: number | string,
62 | roleId: number,
63 | signer: SignerFunction
64 | ) =>
65 | callGuildAPI({
66 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}`,
67 | method: "DELETE",
68 | signer,
69 | }),
70 | };
71 |
72 | export default role;
73 |
--------------------------------------------------------------------------------
/src/clients/rolePlatform.ts:
--------------------------------------------------------------------------------
1 | import {
2 | RolePlatformClaimResponse,
3 | RoleReward,
4 | Schemas,
5 | } from "@guildxyz/types";
6 | import { SignerFunction, callGuildAPI } from "../utils";
7 |
8 | const rolePlatform = {
9 | get: (
10 | guildIdOrUrlName: string | number,
11 | roleId: number,
12 | rolePlatformId: number,
13 | signer?: SignerFunction
14 | ) =>
15 | callGuildAPI({
16 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/role-platforms/${rolePlatformId}`,
17 | method: "GET",
18 | signer,
19 | }),
20 |
21 | getAll: (
22 | guildIdOrUrlName: string | number,
23 | roleId: number,
24 | signer?: SignerFunction
25 | ) =>
26 | callGuildAPI({
27 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/role-platforms`,
28 | method: "GET",
29 | signer,
30 | }),
31 |
32 | claim: (
33 | guildIdOrUrlName: string | number,
34 | roleId: number,
35 | rolePlatformId: number,
36 | args: string[],
37 | signer: SignerFunction
38 | ) =>
39 | callGuildAPI({
40 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/role-platforms/${rolePlatformId}/claim`,
41 | method: "POST",
42 | body: {
43 | data: { args },
44 | schema: "RolePlatformClaimPayloadSchema",
45 | },
46 | signer,
47 | }),
48 |
49 | create: (
50 | guildIdOrUrlName: string | number,
51 | roleId: number,
52 | rolePlatformCreationParams: Schemas["RoleRewardCreationPayload"],
53 | signer: SignerFunction
54 | ) =>
55 | callGuildAPI({
56 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/role-platforms`,
57 | method: "POST",
58 | body: {
59 | data: rolePlatformCreationParams,
60 | schema: "RoleRewardCreationPayloadSchema",
61 | },
62 | signer,
63 | }),
64 |
65 | update: (
66 | guildIdOrUrlName: string | number,
67 | roleId: number,
68 | rolePlatformId: number,
69 | rolePlatformUpdateParams: Schemas["RoleRewardUpdatePayload"],
70 | signer: SignerFunction
71 | ) =>
72 | callGuildAPI({
73 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/role-platforms/${rolePlatformId}`,
74 | method: "PUT",
75 | body: {
76 | data: rolePlatformUpdateParams,
77 | schema: "RoleRewardUpdatePayloadSchema",
78 | },
79 | signer,
80 | }),
81 |
82 | delete: (
83 | guildIdOrUrlName: string | number,
84 | roleId: number,
85 | rolePlatformId: number,
86 | signer: SignerFunction
87 | ) =>
88 | callGuildAPI({
89 | url: `/guilds/${guildIdOrUrlName}/roles/${roleId}/role-platforms/${rolePlatformId}`,
90 | method: "DELETE",
91 | signer,
92 | }),
93 | };
94 |
95 | export default rolePlatform;
96 |
--------------------------------------------------------------------------------
/src/clients/user.ts:
--------------------------------------------------------------------------------
1 | import {
2 | LeaderboardItem,
3 | MembershipResult,
4 | PlatformName,
5 | PublicUserProfile,
6 | User,
7 | UserPointsResponse,
8 | UserProfile,
9 | } from "@guildxyz/types";
10 | import {
11 | SignerFunction,
12 | callGuildAPI,
13 | castDateInLeaderboardItem,
14 | } from "../utils";
15 | import platformUser from "./platformUser";
16 | import userAddress from "./userAddress";
17 |
18 | const user = {
19 | platform: platformUser,
20 |
21 | address: userAddress,
22 |
23 | get: (userIdOrAddress: string | number) =>
24 | callGuildAPI({
25 | url: `/users/${userIdOrAddress}`,
26 | method: "GET",
27 | }),
28 |
29 | getPoints: (userIdOrAddress: number | string, signer: SignerFunction) =>
30 | callGuildAPI({
31 | url: `/users/${userIdOrAddress}/points`,
32 | method: "GET",
33 | signer,
34 | }),
35 |
36 | getRankInGuild: (
37 | userIdOrAddress: number | string,
38 | guildIdOrUrlName: number | string,
39 | guildPlatformId: number
40 | ) =>
41 | callGuildAPI({
42 | url: `/guilds/${guildIdOrUrlName}/points/${guildPlatformId}/users/${userIdOrAddress}`,
43 | method: "GET",
44 | }).then(castDateInLeaderboardItem),
45 |
46 | getProfile: (
47 | userIdOrAddress: string | number,
48 | signer?: Sig
49 | ) =>
50 | callGuildAPI({
51 | url: `/users/${userIdOrAddress}/profile`,
52 | method: "GET",
53 | signer: typeof signer === "string" ? undefined : signer,
54 | }),
55 |
56 | getMemberships: (userIdOrAddress: string | number, signer?: SignerFunction) =>
57 | callGuildAPI({
58 | url: `/users/${userIdOrAddress}/memberships`,
59 | method: "GET",
60 | signer,
61 | }),
62 |
63 | delete: (userIdOrAddress: string | number, signer: SignerFunction) =>
64 | callGuildAPI({
65 | url: `/users/${userIdOrAddress}`,
66 | method: "DELETE",
67 | signer,
68 | }),
69 |
70 | listGateables: (
71 | userIdOrAddress: string | number,
72 | platformName: PlatformName,
73 | signer: SignerFunction
74 | ) =>
75 | callGuildAPI({
76 | url: `/users/${userIdOrAddress}/platforms/${platformName}/gateables`,
77 | method: "GET",
78 | signer,
79 | }),
80 | };
81 |
82 | export default user;
83 |
--------------------------------------------------------------------------------
/src/clients/userAddress.ts:
--------------------------------------------------------------------------------
1 | import { Schemas, UserAddress } from "@guildxyz/types";
2 | import { SignerFunction, callGuildAPI } from "../utils";
3 |
4 | const userAddress = {
5 | get: (
6 | userIdOrAddress: string | number,
7 | address: string,
8 | signer: SignerFunction
9 | ) =>
10 | callGuildAPI({
11 | url: `/users/${userIdOrAddress}/addresses/${address}`,
12 | method: "GET",
13 | signer,
14 | }),
15 |
16 | getAll: (userIdOrAddress: string | number, signer: SignerFunction) =>
17 | callGuildAPI({
18 | url: `/users/${userIdOrAddress}/addresses`,
19 | method: "GET",
20 | signer,
21 | }),
22 |
23 | create: async (
24 | userIdOrAddress: string | number,
25 | signerOfNewAddress: SignerFunction,
26 | signer: SignerFunction
27 | ) => {
28 | const { params, sig } = await signerOfNewAddress(
29 | {},
30 | (p) => `Address: ${p.addr}\nNonce: ${p.nonce}\n Timestamp: ${p.ts}`
31 | );
32 |
33 | return callGuildAPI({
34 | url: `/users/${userIdOrAddress}/addresses`,
35 | method: "POST",
36 | body: {
37 | schema: "UserAddressCreationPayloadSchema",
38 | data: {
39 | address: params.addr,
40 | signature: sig,
41 | nonce: params.nonce,
42 | timestamp: +params.ts,
43 | },
44 | },
45 | signer,
46 | });
47 | },
48 |
49 | update: (
50 | userIdOrAddress: string | number,
51 | address: string,
52 | addressUpdateParams: Schemas["UserAddressUpdatePayload"],
53 | signer: SignerFunction
54 | ) =>
55 | callGuildAPI({
56 | url: `/users/${userIdOrAddress}/addresses/${address}`,
57 | method: "PUT",
58 | body: {
59 | schema: "UserAddressUpdatePayloadSchema",
60 | data: addressUpdateParams,
61 | },
62 | signer,
63 | }),
64 |
65 | delete: (
66 | userIdOrAddress: string | number,
67 | address: string,
68 | signer: SignerFunction
69 | ) =>
70 | callGuildAPI({
71 | url: `/users/${userIdOrAddress}/addresses/${address}`,
72 | method: "DELETE",
73 | signer,
74 | }),
75 | };
76 |
77 | export default userAddress;
78 |
--------------------------------------------------------------------------------
/src/common.ts:
--------------------------------------------------------------------------------
1 | import { consts } from "@guildxyz/types";
2 |
3 | const globals = {
4 | apiBaseUrl: process.env.GUILD_SDK_BASE_URL ?? "https://api.guild.xyz/v2",
5 | headers: {
6 | "Content-Type": "application/json",
7 | [consts.SDK_VERSION_HEADER_NAME]: "2.0.0-rc.2",
8 | [consts.SDK_PROJECT_NAME_HEADER_NAME]: "",
9 | },
10 | };
11 |
12 | const setProjectName = (projectName: string) => {
13 | globals.headers[consts.SDK_PROJECT_NAME_HEADER_NAME] = projectName;
14 | };
15 |
16 | export { globals, setProjectName };
17 |
--------------------------------------------------------------------------------
/src/error.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-classes-per-file */
2 | import type { ZodError } from "zod";
3 |
4 | class GuildAPICallFailed extends Error {
5 | endpoint: string;
6 |
7 | message: string;
8 |
9 | correlationId: string;
10 |
11 | statusCode: number;
12 |
13 | constructor(
14 | endpoint: string,
15 | message: string,
16 | statusCode: number,
17 | correlationId: string
18 | ) {
19 | super(
20 | `Guild API call failed on ${endpoint} with http code ${statusCode} with message: ${
21 | message ?? "Unexpected Error"
22 | }\nID: ${correlationId}`
23 | );
24 | this.endpoint = endpoint;
25 | this.message = message;
26 | this.statusCode = statusCode;
27 | this.correlationId = correlationId;
28 | }
29 | }
30 |
31 | /**
32 | * This error represents a validation on a guild request body. When this
33 | * error is thrown, no request was sent. Instances of this error contin a
34 | * `zodError` field, which contains information on why the supplied data
35 | * didn't pass validation
36 | */
37 | class GuildSDKValidationError> extends Error {
38 | zodError: Err;
39 |
40 | constructor(zodError: Err) {
41 | super(`A value passed as request body did not pass validation.`);
42 | this.zodError = zodError;
43 | }
44 | }
45 |
46 | export { GuildAPICallFailed, GuildSDKValidationError };
47 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default as createGuildClient, type GuildClient } from "./client";
2 | export { GuildAPICallFailed, GuildSDKValidationError } from "./error";
3 | export { createSigner } from "./utils";
4 |
5 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | import { consts, LeaderboardItem, schemas, Schemas } from "@guildxyz/types";
3 | import { keccak256, type Wallet } from "ethers";
4 | import randomBytes from "randombytes";
5 | import type { z } from "zod";
6 | import { globals } from "./common";
7 | import { GuildAPICallFailed, GuildSDKValidationError } from "./error";
8 |
9 | export const recreateMessage = (params: Schemas["Authentication"]["params"]) =>
10 | `${params.msg}\n\nAddress: ${params.addr}\nMethod: ${params.method}${
11 | params.method === consts.AuthMethod.EIP1271
12 | ? `\nChainId: ${params.chainId}`
13 | : ""
14 | }${params.hash ? `\nHash: ${params.hash}` : ""}\nNonce: ${
15 | params.nonce
16 | }\nTimestamp: ${params.ts}`;
17 |
18 | export type SignerFunction = (
19 | // eslint-disable-next-line no-unused-vars
20 | payload?: any,
21 | // eslint-disable-next-line no-unused-vars
22 | getMessage?: (params: Schemas["Authentication"]["params"]) => string
23 | ) => Promise<{
24 | params: Schemas["Authentication"]["params"];
25 | sig: string;
26 | payload: string;
27 | }>;
28 |
29 | type SignerOptions = {
30 | msg?: string;
31 | };
32 |
33 | export const createSigner = {
34 | fromEthersWallet:
35 | (
36 | wallet: Wallet,
37 | { msg = "Please sign this message" }: SignerOptions = {}
38 | ): SignerFunction =>
39 | async (payload = {}, getMessage = recreateMessage) => {
40 | const stringPayload = JSON.stringify(payload);
41 |
42 | const params = schemas.AuthenticationParamsSchema.parse({
43 | method: consts.AuthMethod.EOA,
44 | addr: wallet.address,
45 | msg,
46 | nonce: randomBytes(32).toString("base64"),
47 | ts: `${Date.now()}`,
48 | hash: keccak256(Buffer.from(stringPayload)),
49 | });
50 |
51 | if (params.method !== consts.AuthMethod.EOA) {
52 | throw new Error("This shouldn't happen, please open an issue");
53 | }
54 |
55 | const sig = await wallet.signMessage(Buffer.from(getMessage(params)));
56 |
57 | return { params, sig, payload: stringPayload };
58 | },
59 |
60 | custom:
61 | (
62 | // eslint-disable-next-line no-unused-vars
63 | sign: (message: string) => Promise,
64 | address: string,
65 | {
66 | msg = "Please sign this message",
67 | chainIdOfSmartContractWallet,
68 | }: { chainIdOfSmartContractWallet?: number } & SignerOptions = {}
69 | ): SignerFunction =>
70 | async (payload = {}, getMessage = recreateMessage) => {
71 | const stringPayload = JSON.stringify(payload);
72 |
73 | const params = schemas.AuthenticationParamsSchema.parse({
74 | addr: address,
75 | msg,
76 | nonce: randomBytes(32).toString("base64"),
77 | ts: `${Date.now()}`,
78 | hash: keccak256(Buffer.from(stringPayload)),
79 | ...(typeof chainIdOfSmartContractWallet === "number"
80 | ? {
81 | chainId: chainIdOfSmartContractWallet.toString(),
82 | method: consts.AuthMethod.EIP1271,
83 | }
84 | : { method: consts.AuthMethod.EOA }),
85 | });
86 |
87 | const sig = await sign(getMessage(params));
88 |
89 | return { params, sig, payload: stringPayload };
90 | },
91 | };
92 |
93 | type SchemasImportType = (typeof import("@guildxyz/types"))["schemas"];
94 | type SchemaNames = keyof SchemasImportType;
95 | type MappedSchemas = {
96 | [Schema in SchemaNames]: {
97 | schema: Schema;
98 | data: z.input;
99 | };
100 | }[SchemaNames];
101 |
102 | type CallGuildAPIParams = {
103 | url: string;
104 | queryParams?: Record;
105 | queryParamsSchema?: MappedSchemas["schema"];
106 | signer?: SignerFunction;
107 | } & (
108 | | {
109 | method: "GET";
110 | }
111 | | {
112 | method: "POST" | "PUT" | "DELETE" | "PATCH";
113 | body?: MappedSchemas;
114 | }
115 | );
116 |
117 | // Couldn't get proper type inference if the type params are on the same function
118 | export const callGuildAPI = async (
119 | params: CallGuildAPIParams
120 | ): Promise => {
121 | let parsedQueryParams = null;
122 | if (params.queryParams) {
123 | if (params.queryParamsSchema) {
124 | const validationResult = schemas[params.queryParamsSchema].safeParse(
125 | params.queryParams
126 | );
127 | if (validationResult.success) {
128 | parsedQueryParams = params.queryParams;
129 | } else {
130 | throw new GuildSDKValidationError(validationResult.error);
131 | }
132 | } else {
133 | parsedQueryParams = params.queryParams;
134 | }
135 | }
136 |
137 | const queryParamEntries = Object.entries(parsedQueryParams ?? {})
138 | .filter(([, value]) => !!value)
139 | .map(([key, value]) => [key, `${value}`]);
140 |
141 | const baseUrl = params.url.startsWith("/v1/")
142 | ? globals.apiBaseUrl.replace("/v2", "")
143 | : globals.apiBaseUrl;
144 |
145 | const url =
146 | queryParamEntries.length > 0
147 | ? `${baseUrl}${params.url}?${new URLSearchParams(queryParamEntries)}`
148 | : `${baseUrl}${params.url}`;
149 |
150 | let parsedPayload: any = {};
151 | if (params.method !== "GET" && !!params.body?.schema && !!params.body?.data) {
152 | const validationResult = schemas[params.body.schema].safeParse(
153 | params.body.data
154 | );
155 | if (validationResult.success) {
156 | parsedPayload = validationResult.data;
157 | } else {
158 | throw new GuildSDKValidationError(validationResult.error);
159 | }
160 | }
161 |
162 | const authentication = await params.signer?.(parsedPayload);
163 |
164 | const isPrivileged = "headers" in (authentication ?? {});
165 |
166 | const response = await fetch(url, {
167 | method: params.method,
168 | body:
169 | // eslint-disable-next-line no-nested-ternary
170 | params.method === "GET"
171 | ? undefined
172 | : isPrivileged
173 | ? JSON.stringify(parsedPayload)
174 | : JSON.stringify(authentication ?? parsedPayload),
175 | headers: {
176 | ...(params.method === "GET" && authentication && !isPrivileged
177 | ? {
178 | [consts.PARAMS_HEADER_NAME]: Buffer.from(
179 | JSON.stringify(authentication.params)
180 | ).toString("base64"),
181 | [consts.SIG_HEADER_NAME]: Buffer.from(
182 | authentication.sig.startsWith("0x")
183 | ? authentication.sig.slice(2)
184 | : authentication.sig,
185 | "hex"
186 | ).toString("base64"),
187 | }
188 | : {}),
189 | ...globals.headers,
190 | ...(isPrivileged ? (authentication as any).headers : {}),
191 | },
192 | });
193 |
194 | const responseBody = await response.json();
195 |
196 | if (!response.ok) {
197 | throw new GuildAPICallFailed(
198 | url.replace(baseUrl, ""),
199 | responseBody?.errors?.[0]?.msg ??
200 | responseBody?.message ??
201 | "Unexpected Error",
202 | response.status,
203 | response.headers.get("x-correlation-id") ?? ""
204 | );
205 | }
206 |
207 | return responseBody;
208 | };
209 |
210 | export type OnPoll = (
211 | job: Job | null,
212 | promiseHandlers: {
213 | resolve: (value: Job | PromiseLike | null) => void;
214 | reject: (reason?: any) => void;
215 | }
216 | ) => void;
217 |
218 | export type PollOptions = { onPoll?: OnPoll; intervalMs?: number };
219 |
220 | export const createAndAwaitJob = async <
221 | Job extends { done?: boolean; error?: any; errorMsg?: any },
222 | >(
223 | url: string,
224 | body: MappedSchemas,
225 | queryParams: Record,
226 | signer: SignerFunction,
227 | { onPoll, intervalMs = 1000 }: PollOptions = {}
228 | ) => {
229 | await callGuildAPI<{ jobId: string }>({
230 | url,
231 | method: "POST",
232 | body,
233 | signer,
234 | });
235 |
236 | let interval: ReturnType;
237 |
238 | return new Promise((resolve, reject) => {
239 | interval = setInterval(() => {
240 | callGuildAPI({
241 | url,
242 | method: "GET",
243 | queryParams,
244 | signer,
245 | }).then(([job = null]) => {
246 | onPoll?.(job, { resolve, reject });
247 |
248 | if (!job) {
249 | reject(job);
250 | return; // Return is needed, so TS knows, that after this point job is not null
251 | }
252 | if (!job.done) return;
253 |
254 | if (job.error ?? job.errorMsg) reject(job);
255 | else resolve(job);
256 | });
257 | }, intervalMs);
258 | }).finally(() => {
259 | clearInterval(interval);
260 | });
261 | };
262 |
263 | export function castDateInLeaderboardItem(leaderboardItem: LeaderboardItem) {
264 | return {
265 | ...leaderboardItem,
266 | oldestRoleDate: new Date(leaderboardItem.oldestRoleDate),
267 | };
268 | }
269 |
--------------------------------------------------------------------------------
/tests/auth.test.ts:
--------------------------------------------------------------------------------
1 | import { schemas } from "@guildxyz/types";
2 | import { randomBytes } from "crypto";
3 | import { Wallet, keccak256, toUtf8Bytes, verifyMessage } from "ethers";
4 | import { assert, describe, expect, test } from "vitest";
5 | import { createSigner, recreateMessage } from "../src/utils";
6 |
7 | const WALLET = new Wallet(randomBytes(32).toString("hex"));
8 | const TEST_PAYLOAD = { someKey: "someValue" };
9 | const TEST_MSG = "Some test message";
10 |
11 | describe.concurrent("Authentication", () => {
12 | describe.concurrent("EOA Wallet", () => {
13 | test("Can sign simple message", async () => {
14 | const signer = createSigner.fromEthersWallet(WALLET);
15 | const { params, sig, payload } = await signer();
16 |
17 | expect(() =>
18 | schemas.AuthenticationParamsSchema.parse(params)
19 | ).not.toThrow();
20 | expect(payload).toEqual("{}");
21 | assert(sig.startsWith("0x"));
22 | expect(sig).toHaveLength(132);
23 | expect(verifyMessage(recreateMessage(params), sig)).toEqual(
24 | WALLET.address
25 | );
26 | });
27 |
28 | test("Can sign message with some payload", async () => {
29 | const signer = createSigner.fromEthersWallet(WALLET);
30 | const { params, sig, payload } = await signer(TEST_PAYLOAD);
31 |
32 | expect(() =>
33 | schemas.AuthenticationParamsSchema.parse(params)
34 | ).not.toThrow();
35 | expect(params).toMatchObject({
36 | hash: keccak256(toUtf8Bytes(JSON.stringify(TEST_PAYLOAD))),
37 | });
38 | expect(payload).toEqual(JSON.stringify(TEST_PAYLOAD));
39 | assert(sig.startsWith("0x"));
40 | expect(sig).toHaveLength(132);
41 | expect(verifyMessage(recreateMessage(params), sig)).toEqual(
42 | WALLET.address
43 | );
44 | });
45 |
46 | test("Can sign message with some payload", async () => {
47 | const signer = createSigner.fromEthersWallet(WALLET, { msg: TEST_MSG });
48 | const { params, sig, payload } = await signer();
49 |
50 | expect(() =>
51 | schemas.AuthenticationParamsSchema.parse(params)
52 | ).not.toThrow();
53 | expect(params).toMatchObject({ msg: TEST_MSG });
54 | expect(payload).toEqual("{}");
55 | assert(sig.startsWith("0x"));
56 | expect(sig).toHaveLength(132);
57 | expect(verifyMessage(recreateMessage(params), sig)).toEqual(
58 | WALLET.address
59 | );
60 | });
61 |
62 | test("Can sign with given hex privateKey", async () => {
63 | const privateKeyHex = `0x${randomBytes(32).toString("hex")}`;
64 | const ethersWallet = new Wallet(privateKeyHex);
65 | const expectedAddr = ethersWallet.address;
66 |
67 | const signer = createSigner.fromEthersWallet(ethersWallet);
68 | const { params, sig, payload } = await signer();
69 |
70 | expect(() =>
71 | schemas.AuthenticationParamsSchema.parse(params)
72 | ).not.toThrow();
73 | expect(params.addr).toEqual(expectedAddr.toLowerCase());
74 | expect(payload).toEqual("{}");
75 | assert(sig.startsWith("0x"));
76 | expect(sig).toHaveLength(132);
77 | expect(verifyMessage(recreateMessage(params), sig)).toEqual(expectedAddr);
78 | });
79 |
80 | test("Can sign with given buffer privateKey", async () => {
81 | const privateKeyBuffer = randomBytes(32);
82 | const ethersWallet = new Wallet(`0x${privateKeyBuffer.toString("hex")}`);
83 | const expectedAddr = ethersWallet.address;
84 |
85 | const signer = createSigner.fromEthersWallet(ethersWallet);
86 | const { params, sig, payload } = await signer();
87 |
88 | expect(() =>
89 | schemas.AuthenticationParamsSchema.parse(params)
90 | ).not.toThrow();
91 | expect(params.addr).toEqual(expectedAddr.toLowerCase());
92 | expect(payload).toEqual("{}");
93 | assert(sig.startsWith("0x"));
94 | expect(sig).toHaveLength(132);
95 | expect(verifyMessage(recreateMessage(params), sig)).toEqual(expectedAddr);
96 | });
97 | });
98 | });
99 |
--------------------------------------------------------------------------------
/tests/callGuildAPI.test.ts:
--------------------------------------------------------------------------------
1 | import { randomBytes } from "crypto";
2 | import { Wallet } from "ethers";
3 | import { describe, expect, test } from "vitest";
4 | import { GuildAPICallFailed, GuildSDKValidationError } from "../src/error";
5 | import { callGuildAPI, createSigner } from "../src/utils";
6 |
7 | const WALLET = new Wallet(randomBytes(32).toString("hex"));
8 |
9 | describe.concurrent("callGuildAPI", () => {
10 | test("It fails with validation error correctly", async () => {
11 | const signer = createSigner.fromEthersWallet(WALLET);
12 |
13 | try {
14 | await callGuildAPI({
15 | url: `/guilds/0/roles/0/requirements`,
16 | method: "POST",
17 | body: {
18 | schema: "RequirementCreationPayloadSchema",
19 | data: {} as any, // Missing required "type" field
20 | },
21 | signer,
22 | });
23 | } catch (error) {
24 | expect(error).toBeInstanceOf(GuildSDKValidationError);
25 | expect(error.zodError).toBeTruthy();
26 | expect(error.zodError?.issues).toBeTruthy(); // Not checking `instanceof ZodError`, as zod is only a dev dependency, so we can't access the class
27 | }
28 | });
29 |
30 | test("It handles API errors (guild doesn't exist)", async () => {
31 | const signer = createSigner.fromEthersWallet(WALLET);
32 |
33 | try {
34 | await callGuildAPI({
35 | url: `/guilds/0/roles/0/requirements`,
36 | method: "POST",
37 | body: {
38 | schema: "RequirementCreationPayloadSchema",
39 | data: { type: "ALLOWLIST", data: { addresses: [] } },
40 | },
41 | signer,
42 | });
43 | } catch (error) {
44 | expect(error).toBeInstanceOf(GuildAPICallFailed);
45 | expect(error.endpoint).toEqual(`/guilds/0/roles/0/requirements`);
46 | expect(error.statusCode).toEqual(404);
47 | expect(error.message).toEqual("Guild not found");
48 | }
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/tests/clients/actions/accessCheck.test.ts:
--------------------------------------------------------------------------------
1 | import { Wallet } from "ethers";
2 | import { describe, expect, it } from "vitest";
3 | import { createGuildClient, createSigner } from "../../../src";
4 |
5 | const GUILD_ID = 1984;
6 | const TEST_WALLET_SIGNER = createSigner.fromEthersWallet(
7 | new Wallet(process.env.PRIVATE_KEY!)
8 | );
9 |
10 | const { guild } = createGuildClient("vitest");
11 |
12 | describe.skip("Access check action", () => {
13 | it("can check access", async () => {
14 | // const onPoll = vi.fn();
15 |
16 | const result = await guild.accessCheck(
17 | GUILD_ID,
18 | TEST_WALLET_SIGNER
19 | // {
20 | // onPoll,
21 | // }
22 | );
23 |
24 | // expect(result!.done).toBeTruthy();
25 | expect(result.length).toBeGreaterThan(0);
26 | // expect(onPoll).toHaveBeenCalled();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/clients/actions/join.test.ts:
--------------------------------------------------------------------------------
1 | import { Wallet } from "ethers";
2 | import { describe, expectTypeOf, it } from "vitest";
3 | import { createGuildClient, createSigner } from "../../../src";
4 |
5 | const GUILD_ID = 4486;
6 | const TEST_WALLET_SIGNER = createSigner.fromEthersWallet(
7 | new Wallet(process.env.PRIVATE_KEY!)
8 | );
9 |
10 | const { guild } = createGuildClient("vitest");
11 |
12 | describe.skip("Join action", () => {
13 | it("can join", async () => {
14 | // const onPoll = vi.fn();
15 | const result = await guild.join(
16 | GUILD_ID,
17 | TEST_WALLET_SIGNER
18 | // { onPoll }
19 | );
20 |
21 | expectTypeOf(result.success).toBeBoolean();
22 | // expect(result!.done).toBeTruthy();
23 | // expect(onPoll).toHaveBeenCalled();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/tests/clients/guild.test.ts:
--------------------------------------------------------------------------------
1 | import { assert, describe, expect, it } from "vitest";
2 | import { GuildSDKValidationError } from "../../src/error";
3 | import { CLIENT, TEST_SIGNER, TEST_USER } from "../common";
4 | import { createTestGuild, pick } from "../utils";
5 |
6 | const createdGuild = await createTestGuild();
7 | const createdGuild2 = await createTestGuild();
8 |
9 | describe("Guild client", () => {
10 | it("Can get a guild by id", async () => {
11 | const response = await CLIENT.guild.get(createdGuild.id);
12 |
13 | expect(response).toMatchObject(pick(createdGuild, ["id", "urlName"]));
14 | });
15 |
16 | it("Can get multiple guilds by ids", async () => {
17 | const response = await CLIENT.guild.getMany([
18 | createdGuild.id,
19 | createdGuild2.id,
20 | ]);
21 |
22 | expect(response).toMatchObject([
23 | pick(createdGuild, ["id", "urlName"]),
24 | pick(createdGuild2, ["id", "urlName"]),
25 | ]);
26 | });
27 |
28 | it("Can get guild members", async () => {
29 | const response = await CLIENT.guild.getMembers(createdGuild.id);
30 |
31 | expect(response).toHaveLength(1);
32 | });
33 |
34 | it("Can get user membership for guild", async () => {
35 | const response = await CLIENT.guild.getUserMemberships(
36 | createdGuild.id,
37 | TEST_USER.id
38 | );
39 |
40 | expect(response).toHaveLength(1);
41 | });
42 |
43 | it("Can update guild", async () => {
44 | try {
45 | await CLIENT.guild.update(createdGuild.id, {}, TEST_SIGNER);
46 | assert(false);
47 | } catch (error) {
48 | expect(error).toBeInstanceOf(GuildSDKValidationError);
49 | }
50 |
51 | const updated = await CLIENT.guild.update(
52 | createdGuild.id,
53 | { description: "EDITED" },
54 | TEST_SIGNER
55 | );
56 |
57 | expect(updated.description).toMatchObject("EDITED");
58 | });
59 |
60 | it("Subsequent GET returns updated data", async () => {
61 | const fetchedGuild = await CLIENT.guild.get(createdGuild.id);
62 | expect(fetchedGuild.description).toEqual("EDITED");
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/tests/clients/guildAdmin.test.ts:
--------------------------------------------------------------------------------
1 | import { Wallet } from "ethers";
2 | import { describe, expect, it } from "vitest";
3 | import { GuildAPICallFailed } from "../../src";
4 | import { CLIENT, TEST_SIGNER, TEST_USER } from "../common";
5 | import { createTestGuild } from "../utils";
6 |
7 | const adminAddress = Wallet.createRandom().address.toLowerCase();
8 |
9 | const guild = await createTestGuild();
10 |
11 | describe("Guild admins", () => {
12 | it("get all", async () => {
13 | const admins = await CLIENT.guild.admin.getAll(guild.id);
14 | expect(admins.length).toBeGreaterThan(0);
15 | });
16 |
17 | it("get", async () => {
18 | const admin = await CLIENT.guild.admin.get(guild.id, TEST_USER.id);
19 | expect(admin).toMatchObject({ userId: TEST_USER.id });
20 | });
21 |
22 | let adminUserId: number;
23 |
24 | it("create", async () => {
25 | const newAdmin = await CLIENT.guild.admin.create(
26 | guild.id,
27 | {
28 | address: adminAddress,
29 | isOwner: false,
30 | },
31 | TEST_SIGNER
32 | );
33 |
34 | adminUserId = newAdmin.userId;
35 |
36 | expect(newAdmin).toMatchObject({ isOwner: false });
37 | });
38 |
39 | it("get created admin", async () => {
40 | const admin = await CLIENT.guild.admin.get(guild.id, adminUserId);
41 | expect(admin).toMatchObject({ userId: adminUserId, isOwner: false });
42 | });
43 |
44 | it("delete", async () => {
45 | await CLIENT.guild.admin.delete(
46 | guild.id,
47 | adminUserId,
48 |
49 | TEST_SIGNER
50 | );
51 | });
52 |
53 | it("can't get created admin", async () => {
54 | try {
55 | await CLIENT.guild.admin.get(guild.id, adminUserId);
56 | } catch (error) {
57 | expect(error).toBeInstanceOf(GuildAPICallFailed);
58 | expect(error.statusCode).toEqual(404);
59 | }
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/tests/clients/guildPlatform.test.ts:
--------------------------------------------------------------------------------
1 | import { GuildReward } from "@guildxyz/types";
2 | import { describe } from "node:test";
3 | import { assert, expect, it } from "vitest";
4 | import { GuildAPICallFailed } from "../../src/error";
5 | import { CLIENT, TEST_SIGNER } from "../common";
6 | import { createTestGuild, omit } from "../utils";
7 |
8 | const guildPlatformToCreate = {
9 | platformGuildId: "my-point-system",
10 | platformName: "POINTS",
11 | platformGuildData: { name: "xp" },
12 | } as const;
13 |
14 | const guildPlatformUpdate = {
15 | platformGuildData: { name: "coins" },
16 | } as const;
17 |
18 | let createdGuildPlatform: GuildReward;
19 |
20 | const guild = await createTestGuild();
21 |
22 | describe("guildPlatform client", () => {
23 | it("Can create guildPlatform", async () => {
24 | createdGuildPlatform = await CLIENT.guild.reward.create(
25 | guild.id,
26 | guildPlatformToCreate,
27 | TEST_SIGNER
28 | );
29 |
30 | expect(createdGuildPlatform).toMatchObject(
31 | omit(guildPlatformToCreate, ["platformName"])
32 | );
33 | });
34 |
35 | it("Can update guildPlatform", async () => {
36 | const updated = await CLIENT.guild.reward.update(
37 | guild.id,
38 | createdGuildPlatform.id,
39 | guildPlatformUpdate,
40 | TEST_SIGNER
41 | );
42 |
43 | expect(updated).toMatchObject({
44 | ...omit(guildPlatformToCreate, ["platformName"]),
45 | ...guildPlatformUpdate,
46 | });
47 | });
48 |
49 | it("Can fetch updated guildPlatform", async () => {
50 | const fetched = await CLIENT.guild.reward.get(
51 | guild.id,
52 | createdGuildPlatform.id,
53 | TEST_SIGNER
54 | );
55 |
56 | expect(fetched).toMatchObject({
57 | ...omit(guildPlatformToCreate, ["platformName"]),
58 | ...guildPlatformUpdate,
59 | });
60 | });
61 |
62 | it("Can fetch updated guildPlatform by guildId", async () => {
63 | const fetched = await CLIENT.guild.reward.getAll(guild.id, TEST_SIGNER);
64 |
65 | expect(
66 | fetched.some(
67 | ({ platformGuildData }) =>
68 | platformGuildData.name === guildPlatformUpdate.platformGuildData.name
69 | )
70 | ).toBeTruthy();
71 | });
72 |
73 | it("Can delete guildPlatform", async () => {
74 | await CLIENT.guild.reward.delete(
75 | guild.id,
76 | createdGuildPlatform.id,
77 | TEST_SIGNER
78 | );
79 | });
80 |
81 | it("Returns 404 after delete", async () => {
82 | try {
83 | await CLIENT.guild.reward.get(
84 | guild.id,
85 | createdGuildPlatform.id,
86 | TEST_SIGNER
87 | );
88 | assert(false);
89 | } catch (error) {
90 | expect(error).toBeInstanceOf(GuildAPICallFailed);
91 | expect(error.statusCode).toEqual(404);
92 | }
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/tests/clients/platform.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { createGuildClient } from "../../src";
3 |
4 | const OUR_GUILD_DC_SERVER_ID = "886314998131982336";
5 |
6 | const { platform } = createGuildClient("vitest");
7 |
8 | describe.skip("platform client", () => {
9 | it("Can get guild by platform data", async () => {
10 | const ourGuild = await platform.getGuildByPlatform(
11 | "DISCORD",
12 | OUR_GUILD_DC_SERVER_ID
13 | );
14 | expect(ourGuild.urlName).toEqual("our-guild");
15 | });
16 |
17 | it("Can get user guild access by platform data", async () => {
18 | const ourGuild = await platform.getUserGuildAccessByPlatform(
19 | "DISCORD",
20 | OUR_GUILD_DC_SERVER_ID,
21 | "604927885530234908"
22 | );
23 | expect(ourGuild.platformGuildId).toEqual(OUR_GUILD_DC_SERVER_ID);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/tests/clients/platformUser.test.ts:
--------------------------------------------------------------------------------
1 | import { Wallet } from "ethers";
2 | import { describe, expect, it } from "vitest";
3 | import { createGuildClient } from "../../src";
4 | import { createSigner } from "../../src/utils";
5 |
6 | const TEST_WALLET_ADDRESS = new Wallet(process.env.PRIVATE_KEY!).address;
7 | const TEST_WALLET_SIGNER = createSigner.fromEthersWallet(
8 | new Wallet(process.env.PRIVATE_KEY!)
9 | );
10 |
11 | const { user } = createGuildClient("vitest");
12 |
13 | describe.skip.concurrent("platformUser client", () => {
14 | it("can get a platformUser", async () => {
15 | const result = await user.platform.get(
16 | TEST_WALLET_ADDRESS,
17 | 2,
18 | TEST_WALLET_SIGNER
19 | );
20 |
21 | expect(result).toMatchObject({ platformId: 2 });
22 | });
23 |
24 | it("can get all platformUsers of user", async () => {
25 | const results = await user.platform.getAll(
26 | TEST_WALLET_ADDRESS,
27 | TEST_WALLET_SIGNER
28 | );
29 |
30 | expect(results).toMatchObject([{ platformId: 2 }]);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/tests/clients/requirement.test.ts:
--------------------------------------------------------------------------------
1 | import { Schemas } from "@guildxyz/types";
2 | import { Wallet } from "ethers";
3 | import { assert, describe, expect, it } from "vitest";
4 | import { GuildAPICallFailed, GuildSDKValidationError } from "../../src/error";
5 | import { CLIENT, TEST_SIGNER } from "../common";
6 | import { createTestGuild, omit } from "../utils";
7 |
8 | const ALLOWLIST_ADDRESS = Wallet.createRandom().address;
9 |
10 | const guild = await createTestGuild();
11 | let createdRequirement: Schemas["RequirementCreateResponse"];
12 |
13 | describe("Requirement client", () => {
14 | const requirementToCreate: Schemas["RequirementCreationPayload"] = {
15 | type: "ALLOWLIST",
16 | data: { addresses: [] },
17 | };
18 |
19 | it("Can create requirement", async () => {
20 | createdRequirement = await CLIENT.guild.role.requirement.create(
21 | guild.id,
22 | guild.roles[0].id,
23 | requirementToCreate,
24 | TEST_SIGNER
25 | );
26 |
27 | expect(createdRequirement).toMatchObject(requirementToCreate);
28 | });
29 |
30 | it("Can get a requirement", async () => {
31 | const role = await CLIENT.guild.role.requirement.get(
32 | guild.id,
33 | guild.roles[0].id,
34 | createdRequirement.id
35 | );
36 | expect(role).toMatchObject(
37 | omit(createdRequirement, ["deletedRequirements"])
38 | );
39 | });
40 |
41 | it("Can get all requirements of role", async () => {
42 | const roles = await CLIENT.guild.role.requirement.getAll(
43 | guild.id,
44 | guild.roles[0].id
45 | );
46 |
47 | expect(roles).toMatchObject([
48 | omit(createdRequirement, ["deletedRequirements"]),
49 | ]);
50 | });
51 |
52 | it("Can update requirement", async () => {
53 | const created = await CLIENT.guild.role.requirement.update(
54 | guild.id,
55 | guild.roles[0].id,
56 | createdRequirement.id,
57 | { data: { addresses: [ALLOWLIST_ADDRESS] } },
58 | TEST_SIGNER
59 | );
60 | expect(created.data.addresses).toEqual([ALLOWLIST_ADDRESS.toLowerCase()]);
61 | });
62 |
63 | it("Can't change requirement type", async () => {
64 | try {
65 | await CLIENT.guild.role.requirement.update(
66 | guild.id,
67 | guild.roles[0].id,
68 | createdRequirement.id,
69 | { type: "FREE" } as any,
70 | TEST_SIGNER
71 | );
72 | assert(false);
73 | } catch (error) {
74 | expect(error).toBeInstanceOf(GuildSDKValidationError);
75 | }
76 | });
77 |
78 | it("Returns edited requirement", async () => {
79 | const role = await CLIENT.guild.role.requirement.get(
80 | guild.id,
81 | guild.roles[0].id,
82 | createdRequirement.id
83 | );
84 |
85 | expect(role.data.addresses).toEqual([ALLOWLIST_ADDRESS.toLowerCase()]);
86 | });
87 |
88 | // This is needed, so we can test deletion
89 | it("Create one more requirement", async () => {
90 | await CLIENT.guild.role.requirement.create(
91 | guild.id,
92 | guild.roles[0].id,
93 | { type: "ALLOWLIST", data: { addresses: [] } },
94 | TEST_SIGNER
95 | );
96 | });
97 |
98 | it("Can delete requirement", async () => {
99 | await CLIENT.guild.role.requirement.delete(
100 | guild.id,
101 | guild.roles[0].id,
102 | createdRequirement.id,
103 | TEST_SIGNER
104 | );
105 | });
106 |
107 | it("Doesn't return after delete", async () => {
108 | try {
109 | await CLIENT.guild.role.requirement.get(
110 | guild.id,
111 | guild.roles[0].id,
112 | createdRequirement.id
113 | );
114 | assert(false);
115 | } catch (error) {
116 | expect(error).toBeInstanceOf(GuildAPICallFailed);
117 | expect(error.statusCode).toEqual(404);
118 | }
119 | });
120 | });
121 |
--------------------------------------------------------------------------------
/tests/clients/role.test.ts:
--------------------------------------------------------------------------------
1 | import { assert, describe, expect, it } from "vitest";
2 | import { GuildAPICallFailed } from "../../src/error";
3 | import { CLIENT, TEST_SIGNER } from "../common";
4 | import { createTestGuild } from "../utils";
5 |
6 | const guild = await createTestGuild();
7 |
8 | describe("role create - update - delete", () => {
9 | let createdRoleId: number;
10 |
11 | it("Can create role", async () => {
12 | const created = await CLIENT.guild.role.create(
13 | guild.id,
14 | { name: "SDK Role Creation test", requirements: [{ type: "FREE" }] },
15 | TEST_SIGNER
16 | );
17 |
18 | createdRoleId = created.id;
19 | expect(created).toMatchObject({
20 | name: "SDK Role Creation test",
21 | requirements: [{ type: "FREE" }],
22 | });
23 | });
24 |
25 | it("Can get created role", async () => {
26 | const role = await CLIENT.guild.role.get(guild.id, createdRoleId);
27 | expect(role.name).toEqual("SDK Role Creation test");
28 | });
29 |
30 | it("Can update role", async () => {
31 | const created = await CLIENT.guild.role.update(
32 | guild.id,
33 | createdRoleId,
34 | { description: "EDITED" },
35 | TEST_SIGNER
36 | );
37 | expect(created.description).toEqual("EDITED");
38 | });
39 |
40 | it("Returns edited role", async () => {
41 | const role = await CLIENT.guild.role.get(guild.id, createdRoleId);
42 | expect(role.description).toEqual("EDITED");
43 | });
44 |
45 | it("Can delete role", async () => {
46 | await CLIENT.guild.role.delete(guild.id, createdRoleId, TEST_SIGNER);
47 | });
48 |
49 | it("Doesn't return after delete", async () => {
50 | try {
51 | await CLIENT.guild.role.get(guild.id, createdRoleId);
52 | assert(false);
53 | } catch (error) {
54 | expect(error).toBeInstanceOf(GuildAPICallFailed);
55 | expect(error.statusCode).toEqual(404);
56 | }
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/tests/clients/rolePlatform.test.ts:
--------------------------------------------------------------------------------
1 | import { GuildReward, RoleReward } from "@guildxyz/types";
2 | import { randomBytes } from "crypto";
3 | import { assert, describe, expect, it } from "vitest";
4 | import { GuildAPICallFailed } from "../../src/error";
5 | import { CLIENT, TEST_SIGNER } from "../common";
6 | import { createTestGuild } from "../utils";
7 |
8 | const guild = await createTestGuild();
9 |
10 | const guildPlatformToCreate = {
11 | platformGuildId: `my-point-system-${randomBytes(4).toString("hex")}`,
12 | platformName: "POINTS",
13 | platformGuildData: { name: "xp" },
14 | } as const;
15 |
16 | let createdGuildPlatform: GuildReward;
17 | let createdRolePlatform: RoleReward;
18 |
19 | describe("rolePlatform client", () => {
20 | it("Can create guildPlatform", async () => {
21 | createdGuildPlatform = await CLIENT.guild.reward.create(
22 | guild.id,
23 | guildPlatformToCreate,
24 | TEST_SIGNER
25 | );
26 |
27 | expect(createdGuildPlatform.platformGuildId).toEqual(
28 | guildPlatformToCreate.platformGuildId
29 | );
30 | });
31 |
32 | it("Can create rolePlatform", async () => {
33 | createdRolePlatform = await CLIENT.guild.role.reward.create(
34 | guild.id,
35 | guild.roles[0].id,
36 | {
37 | guildPlatformId: createdGuildPlatform.id,
38 | platformRoleData: { score: "5" },
39 | },
40 | TEST_SIGNER
41 | );
42 |
43 | expect(createdRolePlatform).toMatchObject({
44 | guildPlatformId: createdGuildPlatform.id,
45 | platformRoleData: { score: "5" },
46 | });
47 | });
48 |
49 | it("Can update rolePlatform", async () => {
50 | const updated = await CLIENT.guild.role.reward.update(
51 | guild.id,
52 | guild.roles[0].id,
53 | createdRolePlatform.id,
54 | { platformRoleData: { score: "15" } },
55 | TEST_SIGNER
56 | );
57 |
58 | expect(updated.platformRoleData!.score).toEqual("15");
59 | });
60 |
61 | it("Returns updated data", async () => {
62 | const created = await CLIENT.guild.role.reward.get(
63 | guild.id,
64 | guild.roles[0].id,
65 | createdRolePlatform.id
66 | );
67 |
68 | expect(created.platformRoleData!.score).toEqual("15");
69 | });
70 |
71 | it("Returns updated data by role", async () => {
72 | const created = await CLIENT.guild.role.reward.getAll(
73 | guild.id,
74 | guild.roles[0].id
75 | );
76 |
77 | expect(created).toMatchObject([{ platformRoleData: { score: "15" } }]);
78 | });
79 |
80 | it("Can delete rolePlatform", async () => {
81 | await CLIENT.guild.role.reward.delete(
82 | guild.id,
83 | guild.roles[0].id,
84 | createdRolePlatform.id,
85 | TEST_SIGNER
86 | );
87 | });
88 |
89 | it("404 after delete", async () => {
90 | try {
91 | await CLIENT.guild.role.reward.get(
92 | guild.id,
93 | guild.roles[0].id,
94 | createdRolePlatform.id
95 | );
96 | assert(false);
97 | } catch (error) {
98 | expect(error).toBeInstanceOf(GuildAPICallFailed);
99 | expect(error.statusCode).toEqual(404);
100 | }
101 | });
102 | });
103 |
--------------------------------------------------------------------------------
/tests/clients/user.test.ts:
--------------------------------------------------------------------------------
1 | import { randomBytes } from "crypto";
2 | import { Wallet } from "ethers";
3 | import { assert, describe, expect, it } from "vitest";
4 | import { GuildAPICallFailed } from "../../src";
5 | import { createSigner } from "../../src/utils";
6 | import { CLIENT } from "../common";
7 |
8 | const NEW_WALLET = new Wallet(randomBytes(32).toString("hex"));
9 | const NEW_ADDRESS = NEW_WALLET.address.toLowerCase();
10 | const NEW_SIGNER = createSigner.fromEthersWallet(NEW_WALLET);
11 |
12 | describe("User client", () => {
13 | it("user initially doesn't exist", async () => {
14 | try {
15 | await CLIENT.user.get(NEW_ADDRESS);
16 | assert(false);
17 | } catch (error) {
18 | expect(error).toBeInstanceOf(GuildAPICallFailed);
19 | expect((error as GuildAPICallFailed).statusCode).toEqual(404);
20 | }
21 | });
22 |
23 | it("created user with a signed request", async () => {
24 | const profile = await CLIENT.user.getProfile(NEW_ADDRESS, NEW_SIGNER);
25 |
26 | expect(profile.addresses).toMatchObject([{ address: NEW_ADDRESS }]);
27 | });
28 |
29 | it("can fetch user", async () => {
30 | const fetchedUser = await CLIENT.user.get(NEW_ADDRESS);
31 |
32 | expect(fetchedUser.id).toBeGreaterThan(0);
33 | });
34 |
35 | it("can fetch user public user profile", async () => {
36 | const fetchedUserProfile = await CLIENT.user.getProfile(NEW_ADDRESS);
37 |
38 | expect(fetchedUserProfile.id).toBeGreaterThan(0);
39 | });
40 |
41 | it("Can fetch user private user profile", async () => {
42 | const fetchedUserProfile = await CLIENT.user.getProfile(
43 | NEW_ADDRESS,
44 | NEW_SIGNER
45 | );
46 |
47 | expect(fetchedUserProfile.id).toBeGreaterThan(0);
48 | expect(fetchedUserProfile.addresses).toMatchObject([
49 | { address: NEW_ADDRESS },
50 | ]);
51 | });
52 |
53 | it("Can fetch memberships", async () => {
54 | const memberships = await CLIENT.user.getMemberships(NEW_ADDRESS);
55 | expect(memberships.length).toBe(0);
56 | });
57 |
58 | it("can delete user", async () => {
59 | await CLIENT.user.delete(NEW_ADDRESS, NEW_SIGNER);
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/tests/clients/userAddress.test.ts:
--------------------------------------------------------------------------------
1 | import { randomBytes } from "crypto";
2 | import { Wallet } from "ethers";
3 | import { describe, expect, it } from "vitest";
4 | import { createSigner } from "../../src/utils";
5 | import { CLIENT, TEST_ADDRESS, TEST_SIGNER } from "../common";
6 |
7 | const NEW_WALLET = new Wallet(randomBytes(32).toString("hex"));
8 | const NEW_ADDRESS = NEW_WALLET.address.toLowerCase();
9 | const NEW_SIGNER = createSigner.fromEthersWallet(NEW_WALLET);
10 |
11 | describe("userAddress client", () => {
12 | it("Can create userAddress", async () => {
13 | const created = await CLIENT.user.address.create(
14 | TEST_ADDRESS,
15 | NEW_SIGNER,
16 | TEST_SIGNER
17 | );
18 |
19 | expect(created).toMatchObject({
20 | address: NEW_ADDRESS,
21 | isPrimary: false,
22 | });
23 | });
24 |
25 | it("Can get linked address", async () => {
26 | const address = await CLIENT.user.address.get(
27 | TEST_ADDRESS,
28 | NEW_ADDRESS,
29 | TEST_SIGNER
30 | );
31 |
32 | expect(address.address).toEqual(NEW_ADDRESS);
33 | });
34 |
35 | it("Can get linked addresses", async () => {
36 | const addresses = await CLIENT.user.address.getAll(
37 | TEST_ADDRESS,
38 | TEST_SIGNER
39 | );
40 |
41 | expect(addresses.length).toEqual(2);
42 | });
43 |
44 | it("Can delete userAddress", async () => {
45 | await CLIENT.user.address.delete(TEST_ADDRESS, NEW_ADDRESS, TEST_SIGNER);
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/tests/common.ts:
--------------------------------------------------------------------------------
1 | import { randomBytes } from "crypto";
2 | import { Wallet } from "ethers";
3 | import { createGuildClient, createSigner } from "../src";
4 |
5 | export const CLIENT = createGuildClient("vitest");
6 | export const TEST_WALLET = new Wallet(randomBytes(32).toString("hex"));
7 | export const TEST_ADDRESS = TEST_WALLET.address.toLowerCase();
8 | export const TEST_SIGNER = createSigner.fromEthersWallet(TEST_WALLET);
9 | export const TEST_USER = await CLIENT.user.getProfile(
10 | TEST_ADDRESS,
11 | TEST_SIGNER
12 | );
13 |
--------------------------------------------------------------------------------
/tests/points.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | LeaderboardItem,
3 | RoleCreationResponse,
4 | RoleReward,
5 | UserPointsItem,
6 | } from "@guildxyz/types";
7 | import { describe, expect, it, test } from "vitest";
8 | import { CLIENT, TEST_ADDRESS, TEST_SIGNER, TEST_USER } from "./common";
9 | import { createTestGuild } from "./utils";
10 |
11 | const guild = await createTestGuild();
12 | const role1 = guild.roles[0];
13 |
14 | let createdRolePlatform: RoleReward;
15 | let role2: RoleCreationResponse;
16 |
17 | describe("points", () => {
18 | it("created second role", async () => {
19 | role2 = await CLIENT.guild.role.create(
20 | guild.id,
21 | {
22 | name: "Role 2",
23 | requirements: [{ type: "FREE" }],
24 | },
25 | TEST_SIGNER
26 | );
27 |
28 | expect(role2.id).toBeGreaterThan(0);
29 | });
30 |
31 | test("initially has no guildPlatform", async () => {
32 | const guildPlatforms = await CLIENT.guild.reward.getAll(
33 | guild.id,
34 | TEST_SIGNER
35 | );
36 |
37 | expect(guildPlatforms).toHaveLength(0);
38 | });
39 |
40 | test("can create a points reward", async () => {
41 | createdRolePlatform = await CLIENT.guild.role.reward.create(
42 | guild.id,
43 | role1.id,
44 | {
45 | guildPlatform: {
46 | platformGuildId: "my-points",
47 | platformName: "POINTS",
48 | platformGuildData: { name: "coins" },
49 | },
50 | platformRoleData: { score: "5" },
51 | },
52 | TEST_SIGNER
53 | );
54 |
55 | expect(createdRolePlatform.platformRoleData).toEqual({ score: "5" });
56 | });
57 |
58 | test("can create a points reward for other role", async () => {
59 | createdRolePlatform = await CLIENT.guild.role.reward.create(
60 | guild.id,
61 | role2.id,
62 | {
63 | guildPlatformId: createdRolePlatform.guildPlatformId,
64 | platformRoleData: { score: "10" },
65 | },
66 | TEST_SIGNER
67 | );
68 |
69 | expect(createdRolePlatform.platformRoleData).toEqual({ score: "10" });
70 | });
71 |
72 | test("get leaderboard - just for triggering revalidation", async () => {
73 | await CLIENT.guild.getLeaderboard(
74 | guild.id,
75 | createdRolePlatform.guildPlatformId
76 | );
77 |
78 | await new Promise((resolve) => {
79 | setTimeout(() => {
80 | resolve();
81 | }, 2000);
82 | });
83 | });
84 |
85 | test("get leaderboard - not signed", async () => {
86 | const { leaderboard, aroundUser } = await CLIENT.guild.getLeaderboard(
87 | guild.id,
88 | createdRolePlatform.guildPlatformId
89 | );
90 |
91 | expect(leaderboard).toBeTruthy();
92 | expect(aroundUser).toBeFalsy();
93 | expect(leaderboard).toHaveLength(1);
94 | expect(leaderboard[0]).toMatchObject({
95 | // roleIds: [role1.id, role2.id],
96 | userId: TEST_USER.id,
97 | totalPoints: 15,
98 | // rank: 1,
99 | });
100 | });
101 |
102 | test("get leaderboard - signed", async () => {
103 | const { leaderboard, aroundUser } = await CLIENT.guild.getLeaderboard(
104 | guild.id,
105 | createdRolePlatform.guildPlatformId,
106 | TEST_SIGNER
107 | );
108 |
109 | expect(leaderboard).toBeTruthy();
110 | expect(aroundUser).toBeTruthy();
111 | expect(aroundUser).toHaveLength(1);
112 | expect(leaderboard).toHaveLength(1);
113 | expect(leaderboard[0]).toMatchObject({
114 | // roleIds: [role1.id, role2.id],
115 | userId: TEST_USER.id,
116 | totalPoints: 15,
117 | // rank: 1,
118 | });
119 | expect(aroundUser![0]).toMatchObject({
120 | // roleIds: [role1.id, role2.id],
121 | userId: TEST_USER.id,
122 | totalPoints: 15,
123 | // rank: 1,
124 | });
125 | });
126 |
127 | test("get user points", async () => {
128 | const response = await CLIENT.user.getPoints(TEST_USER.id, TEST_SIGNER);
129 |
130 | expect(response).toHaveLength(1);
131 | expect(response[0]).toMatchObject({
132 | guildId: guild.id,
133 | guildPlatformId: createdRolePlatform.guildPlatformId,
134 | // roleIds: [role1.id, role2.id],
135 | totalPoints: 15,
136 | });
137 | });
138 |
139 | test("get user rank", async () => {
140 | const response = await CLIENT.user.getRankInGuild(
141 | TEST_USER.id,
142 | guild.id,
143 | createdRolePlatform.guildPlatformId
144 | );
145 |
146 | expect(response).toMatchObject({
147 | userId: TEST_USER.id,
148 | // roleIds: [role1.id, role2.id],
149 | totalPoints: 15,
150 | rank: 1,
151 | address: TEST_ADDRESS,
152 | });
153 | });
154 |
155 | test("can delete guildPlatform", async () => {
156 | await CLIENT.guild.reward.delete(
157 | guild.id,
158 | createdRolePlatform.guildPlatformId,
159 | TEST_SIGNER
160 | );
161 | });
162 | });
163 |
--------------------------------------------------------------------------------
/tests/utils.ts:
--------------------------------------------------------------------------------
1 | import { randomBytes } from "crypto";
2 | import { afterAll } from "vitest";
3 | import { CLIENT, TEST_SIGNER } from "./common";
4 |
5 | export function omit, K extends keyof T>(
6 | obj: T,
7 | keys: K[]
8 | ): Omit {
9 | return Object.fromEntries(
10 | Object.keys(obj)
11 | .filter((key: keyof T) => !keys.includes(key as K))
12 | .map((key) => [key, obj[key]])
13 | ) as Omit;
14 | }
15 |
16 | export function pick, K extends keyof T>(
17 | obj: T,
18 | keys: K[]
19 | ): Pick {
20 | return Object.fromEntries(keys.map((key) => [key, obj[key]])) as Pick;
21 | }
22 |
23 | export async function createTestGuild() {
24 | const random = randomBytes(4).toString("hex");
25 |
26 | const guild = await CLIENT.guild.create(
27 | {
28 | name: "SDK Test Guild",
29 | urlName: `sdk-test-guild-${random}`,
30 | contacts: [],
31 | roles: [{ name: "SDK Test Role", requirements: [{ type: "FREE" }] }],
32 | },
33 | TEST_SIGNER
34 | );
35 |
36 | afterAll(async () => {
37 | await CLIENT.guild.delete(guild.id, TEST_SIGNER);
38 | });
39 |
40 | return guild;
41 | }
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
9 | // "lib": [], /* Specify library files to be included in the compilation. */
10 | "allowJs": true /* Allow javascript files to be compiled. */,
11 | "checkJs": false /* Report errors in .js files. */,
12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
13 | "declaration": true /* Generates corresponding '.d.ts' file. */,
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | //"sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | "outDir": "./build" /* Redirect output structure to the directory. */,
18 | "rootDir": "./" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | "removeComments": true /* Do not emit comments to output. */,
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true /* Enable all strict type-checking options. */,
29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | "strictNullChecks": true /* Enable strict null checks. */,
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
43 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
44 |
45 | /* Module Resolution Options */
46 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
47 | // "baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
48 | // "paths": {} /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
50 | // "typeRoots": [], /* List of folders to include type definitions from. */
51 | // "types": [], /* Type declaration files to be included in compilation. */
52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
53 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
56 |
57 | /* Source Map Options */
58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
62 |
63 | /* Experimental Options */
64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
66 |
67 | /* Advanced Options */
68 | "skipLibCheck": true /* Skip type checking of declaration files. */,
69 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
70 | "resolveJsonModule": true
71 | },
72 | "exclude": ["node_modules"],
73 | "include": ["src"]
74 | }
75 |
--------------------------------------------------------------------------------
/v2-migration-guide.md:
--------------------------------------------------------------------------------
1 |
15 |
16 | ## Contents
17 |
18 | - [SignerFunction](#signing-a-message)
19 | - [Clients](#clients)
20 | - [Platform](#platform)
21 | - [User](#user)
22 | - [getMemberships](#getmemberships)
23 | - [join](#join)
24 | - [Guild](#guild)
25 | - [getAll](#getall)
26 | - [getByAddress](#getbyaddress)
27 | - [get](#get)
28 | - [getUserAccess](#getuseraccess)
29 | - [getUserMemberships](#getusermemberships)
30 | - [create](#create)
31 | - [update](#update)
32 | - [delete](#delete)
33 | - [Role](#role)
34 | - [get](#get-1)
35 | - [create](#create-1)
36 | - [update](#update-1)
37 | - [delete](#delete-1)
38 |
39 | ## Signing a message
40 |
41 | Use `createSigner` to construct a signer function. There are multiple ways to create a signer function, for example `createSigner.fromEthersWallet` can construct a signer from an `ethers.Walllet`
42 |
43 | ```ts
44 | import { createSigner, guild } from "@guildxyz/sdk";
45 | import { Wallet } from "ethers";
46 | import { randomBytes } from "crypto";
47 |
48 | const privateKey = randomBytes(32);
49 |
50 | const mySigner = createSigner.fromEthersWallet(
51 | new Wallet(privateKey.toString("hex"))
52 | );
53 |
54 | // Use it directly
55 | const myAuthData = await mySigner({ some: "payload" });
56 |
57 | // Or pass it to a client method
58 | const createdGuild = await guild.create(
59 | {
60 | name: "My SDK test Guild",
61 | urlName: "my-sdk-test-guild",
62 | description: "My first Guild created with the SDK",
63 | roles: [{ name: "My SDK test Role", requirements: [{ type: "FREE" }] }],
64 | },
65 | mySigner
66 | );
67 | ```
68 |
69 | ## Clients
70 |
71 | These clients have changed in the latest version of the SDK, and are now more flexible, and easier to use. The `platformName` parameter is no longer needed, as it is now a property of the client. The `signerAddress` and `sign` parameters are no longer needed either, as the clients now accept a single `signer: SignerFunction` parameter. The `SignerFunction` is a function that takes a payload, and returns a signature. See [Signing a message](#signing-a-message) for more details.
72 |
73 | ### Platform
74 |
75 | Use the `withPlatformName` function on the `platform` client to create a platform client that is fixed with a specific `platformName`
76 |
77 | ```ts
78 | import { platform } from "@guildxyz/sdk";
79 |
80 | const discordClient = platform.withPlatformName("DISCORD")
81 |
82 | await discordClient.getGuildByPlatform(...) // Doesn't need a platformName parameter!
83 | ```
84 |
85 | ### User
86 |
87 | #### `getMemberships`
88 |
89 | Now accepts user ids as well, and optionally takes a `SignerFunction`. If the signer is provided, and the signature is successfully validated, the result will be more detailed.
90 |
91 | ```ts
92 | import { user } from "@guildxyz/sdk";
93 |
94 | const userMemberships = await user.getMemberships("0x...", mySigner);
95 |
96 | const guildMembership = userMemberships.find(
97 | ({ guildId }) => guildId === someGuildId
98 | );
99 | ```
100 |
101 | #### `join`
102 |
103 | Use the `actions.join` client. Create a new join action with `actions.join.start`, then poll it's state by either calling `actions.join.poll`, or `actions.join.await`. The former will make a single poll, while the latter will keep polling the state until the job is done
104 |
105 | ```ts
106 | import { actions } from "@guildxyz/sdk";
107 |
108 | // Start a join action, if it hasn't been started yet
109 | await actions.join.start(someGuildId, mySigner);
110 |
111 | // Poll the job until it is done. Poll every 2 seconds, and log results
112 | const joinResult = actions.join.await(someGuildId, mySigner, console.log, 2000);
113 | ```
114 |
115 | ### Guild
116 |
117 | #### `getAll`
118 |
119 | Use `guild.getMany` to fetch multiple guilds by their IDs. Use `guild.search` to search for guilds with paginated results. The `roles: string[]` field, containing the names of the guild's roles isn't returned anymore, if needed, it needs to be fetched separately with a `guild.role.getAll` call
120 |
121 | ```ts
122 | import { guild } from "@guildxyz/sdk";
123 |
124 | const guilds = await guild.getMany([someGuildId, someOtherGuildId]);
125 | ```
126 |
127 | #### `getByAddress`
128 |
129 | This method doesn't exist anymore
130 |
131 | #### `get`
132 |
133 | Use `guild.get`. The response won't include roles, rewards, nor admins. Those can be fetched with `guild.role.getAll`, `guild.reward.getAll`, and `guild.admin.getAll`
134 |
135 | ```ts
136 | import { guild } from "@guildxyz/sdk";
137 |
138 | const guild = await guild.get(someGuildId);
139 | const guildPlatforms = await guild.reward.getAll(someGuildId);
140 | const roles = await guild.role.getAll(someGuildId);
141 | const admins = await guild.admin.getAll(someGuildId);
142 | ```
143 |
144 | > Note that the role response won't include everything either, rolePlatforms/rewards, and requirements will be missing, those can be fetched with `guild.role.reward.getAll` and `guild.role.requirement.getAll`
145 |
146 | #### `getUserAccess`
147 |
148 | Use `guild.getMemberAccess`. It now accepts user id as well, and optionally a signer to get more detailed output
149 |
150 | ```ts
151 | import { guild } from "@guildxyz/sdk";
152 |
153 | const memberAccess = await guild.getMemberAccess(someGuildId, someUserId);
154 | ```
155 |
156 | #### `getUserMemberships`
157 |
158 | Use `user.getMemberships` to fetch for all the guilds, where the user is a member
159 |
160 | ```ts
161 | import { user } from "@guildxyz/sdk";
162 |
163 | const memberships = await user.getMemberships(someUserId);
164 |
165 | const guildMembership = memberships.find(
166 | ({ guildId }) => guildId === someGuildId
167 | );
168 | ```
169 |
170 | #### `create`
171 |
172 | Use `guild.create`. Instead of separate `signerAddress`, and `sign` params, it now accepts a single `signer: SignerFunction`
173 |
174 | ```ts
175 | import { guild } from "@guildxyz/sdk";
176 |
177 | const createdGuild = await guild.create(
178 | {
179 | name: "My SDK test Guild",
180 | urlName: "my-sdk-test-guild",
181 | description: "My first Guild created with the SDK",
182 | roles: [{ name: "My SDK test Role", requirements: [{ type: "FREE" }] }],
183 | },
184 | mySigner
185 | );
186 | ```
187 |
188 | #### `update`
189 |
190 | Use `guild.update`. Instead of separate `signerAddress`, and `sign` params, it now accepts a single `signer: SignerFunction`
191 |
192 | ```ts
193 | import { guild } from "@guildxyz/sdk";
194 |
195 | const updatedGuild = await guild.update(
196 | someGuildId,
197 | {
198 | description: "I've edited my first Guild created with the SDK",
199 | },
200 | mySigner
201 | );
202 | ```
203 |
204 | #### `delete`
205 |
206 | Use `guild.delete`. Instead of separate `signerAddress`, and `sign` params, it now accepts a single `signer: SignerFunction`. The `success` flag in the response isn't returned anymore, if the call resolved, then the deletion was successful, if it rejected, then something went wrong, and a `GuildAPICallFailed` error is thrown
207 |
208 | ```ts
209 | import { guild } from "@guildxyz/sdk";
210 |
211 | await guild.delete(someGuildId, mySigner);
212 | ```
213 |
214 | ### Role
215 |
216 | #### `get`
217 |
218 | Use `guild.role.get`. It now needs a `guildIdOrUrlName` param as well, and optionally takes a signer for more detailed results. The `guildId`, `members`, `requirements` and `rolePlatforms` fields aren't included in the result anymore. `guildId` is assumed to be known, as it is needed to make the get call, the other three can be fetched sepatarately with `guild.getMembers`, `guild.role.requirement.getAll` and `guild.role.reward.getAll`
219 |
220 | ```ts
221 | import { guild } from "@guildxyz/sdk";
222 |
223 | const role = await guild.role.get(someGuildId, someRoleId);
224 | ```
225 |
226 | #### `create`
227 |
228 | Use `guild.role.create`. Instead of separate `signerAddress`, and `sign` params, it now accepts a single `signer: SignerFunction`. Similarly to `get`, the `guildId`, `members`, `requirements` and `rolePlatforms` fields aren't included in the response
229 |
230 | ```ts
231 | import { guild } from "@guildxyz/sdk";
232 |
233 | const createdRole = await guild.role.create(
234 | someGuildId,
235 | { name: "My new Role", requirements: [{ type: "FREE" }] },
236 | mySigner
237 | );
238 | ```
239 |
240 | #### `update`
241 |
242 | Use `guild.role.update`. Instead of separate `signerAddress`, and `sign` params, it now accepts a single `signer: SignerFunction`. Similarly to `get`, the `guildId`, `members`, `requirements` and `rolePlatforms` fields aren't included in the response
243 |
244 | ```ts
245 | import { guild } from "@guildxyz/sdk";
246 |
247 | const updatedRole = await guild.role.update(
248 | someGuildId,
249 | someRoleId,
250 | { description: "I've edited my new role" },
251 | mySigner
252 | );
253 | ```
254 |
255 | #### `delete`
256 |
257 | Use `guild.role.delete`. Instead of separate `signerAddress`, and `sign` params, it now accepts a single `signer: SignerFunction`. The `success` flag in the response isn't returned anymore, if the call resolved, then the deletion was successful, if it rejected, then something went wrong, and a `GuildAPICallFailed` error is thrown
258 |
259 | ```ts
260 | import { guild } from "@guildxyz/sdk";
261 |
262 | await guild.role.delete(someGuildId, someRoleId, mySigner);
263 | ```
264 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-unresolved
2 | import { defineConfig } from "vitest/config";
3 |
4 | export default defineConfig({
5 | test: {
6 | testTimeout: 60_000,
7 | },
8 | });
9 |
--------------------------------------------------------------------------------