Skip to content

Instantly share code, notes, and snippets.

@Brunya
Created October 9, 2024 17:26
Show Gist options
  • Save Brunya/7074991e8e5d4126b16c3800d1b2d729 to your computer and use it in GitHub Desktop.
Save Brunya/7074991e8e5d4126b16c3800d1b2d729 to your computer and use it in GitHub Desktop.
# ERC20 as a reward
Largely based on [CONTRACT_CALL reward](./contract-call-reward.md).
## Overview
Similarly to other text-based rewards, we should add `ERC20` as a new reward type. The guild admin should be able to set up a pool in a contract and fund it. The eligible users should be able to claim the funds - the claimable amount should be either a fixed amount or determined by applying a multiplier to a leaderboard snapshot (uses the Dynamic Amount Rewards feature, see it's [design doc](https://whimsical.com/dynamic-rewards-SADi6pwPS4RsbKbNsz8XPQ)).
As the above suggests, we will need a contract that can hold tokens in pools - similarly to our [generic fee collector](https://github.com/guildxyz/generic-fee-collector-contract) contract, although reversed: instead of the users providing the funds and the admin withdrawing them, here the admin provides all the funds and the users are slowly claiming their part. The eligibility is checked in Guild Core, which also calculates the reward amount and signs the required params for the contract call, very similarly to the [CONTRACT_CALL](./contract-call-reward.md) or the [GUILD_PIN](./guild-pin-reward.md) rewards.
## Database changes
### New Table
- `ClaimData` (stores the wallet/platformUserId - userId pairs used for individual claims)
- roleGuildPlatformId
- account
- userId
### Store:
- `GuildPlatform`
- platformGuildId: uuid
- data (json)
- poolId (the id of the pool to claim tokens from)
- chain
- contractAddress
- name (reward name on frontend)
- description (markdown-formatted text on frontend)
- imageUrl (the token's image or the reward's image on frontend)
- tokenAddress (the token's address, used to get decimals for amount calculation)
- `RolePlatform`
- nothing
## Flows
### Reward setup flow
- basic GuildPlatform CRUD applies here with one key difference: the admin should create a pool in the contract and fund it.
### Claim flow
- the reward is claimable if we access the role it belongs to
- note: to prevent multiple claims, a join job with `saveClaimData: true` should be done prior to the claim. To learn what it does, see [Multiple claim prevention](#multiple-claim-prevention)
- uses the same endpoint as other text-based rewards: `POST` `/guilds/:guildId/roles/:roleId/role-platforms/:rolePlatformId/claim`
- the switch statement in _[src/services/v2/rolePlatformsService.ts/claimReward](../../src/services/v2/rolePlatformsService.ts)_ needs to be extended for the new reward type
- the reward amount is calculated here
- the pool id, the rolePlatformId, the reward amount, the current timestamp, the caller address, the caller user id, the chain id and the contract address are signed using [signContractArgs](../../src/utils/v2/rolePlatformUtils.ts)
- the UserRewards entry is created and the parameters together with the signature are returned
- the frontend triggers the wallet popup with the transaction
- the contract verifies the signature and transfers the reward amount if valid, along with the Guild fee
## Multiple claim prevention
- ERC20 rewards should not be assigned to roles containing requirements that are fulfillable by movable assets
- ideally we should limit this on the frontend
- the contract stores the amount of tokens already claimed by a user from a rolePlatform in a nested mapping in every pool's struct:
```solidity
mapping(uint256 userId => mapping(uint256 rolePlatformId => uint256 amount)) claimsByUserId;
```
- the core calculates only the total claimable amount. The contract is responsible for substracting the already claimed amount from it
- since the contract is aware of every userId's claims but not of the addresses/socials that were used to fulfill the requirements, these should be tracked in the backend
- when the `/claim` endpoint or a join job with `saveClaimData: true` is called, all the addresses and social identities should be saved along with the userId they are connected to
- the new ClaimData table should be used for this (analogous to the RolePlatform's PlatformData, but serves specifically this purpose)
- since the platformUserIds might not be unique across platforms, we should save them with a `platformId-` prefix. Addresses should have somewhat analogously an `address-` prefix.
- if the `/claim` endpoint is called repeatedly, it should still generate a signature as long as the addresses/socials match
- this is because even if the claim endpoint was called, it doesn't mean that the user actually claimed from the contract
- later, if any of them get's connected to a new user and the `/claim` endpoint is called, an error should be thrown
## Other needed code changes
Add the platform to various places:
- types (PLATFORM_NAMES_IDS, PLATFORM_IDS_NAMES, PlatformName)
- a where condition in _[src/utils/db.ts/getPendingActions](../../src/utils/db.ts)_ (this is legacy logic)
- insert to the db
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment