Closed tinypell3ts closed 1 year ago
How actions are created?
Actions aren’t necessarily created, rather than they are defined in the json. Stored as an array of objects and they are passed into.
4 properties: • ID (name of action) • XP (how many points) • Description (description of action) • Token (token ID)
How actions are triggered?
Actions call the trigger method of the ‘action’ class. Takes in the address (who’s receiving the XP). The token (the actions token). The XP (amount of XP the user gets for completing the task).
Post request to the API, to communicate with the smart contract that will reward the user with the token.
How missions are created?
Pre-defined missions in a json file, for example, complete mission 1, 3 times:
missions.json file:
[
{
"id": "mission1",
"description": "Complete action1 three times",
"amount": 20,
"token": "0x21234",
"badge": "0x123342",
"requirements": [
{
"actionId": "connect",
"count": 3
}
]
}
]
Should there be an image option here? So users can add a potential image for the “mission complete NFT” the end user received? Missions should be directly linked to the number of actions that have been completed for simple implementation.
How missions are triggered?
The missions will be checked via the subgraph to see if the user has completed the set task(s) X number of times.
The function would read the missions from the json. This would be compared to the subgraph information and say, “if the mission is complete then enable the user to mint an NFT”. The NFT metadata will be constructed from the missions.json file.
Should we have the end user trigger the minting of the reward to completing a mission rather than automate that?
How to provide web2 authentication with a custodial wallet?
The main logic for the component is as follows:
magiclink > authenticateuser > have we got a private key for them already? If no > create wallet. If yes > do nothing.
Need to do:
Add encryption to protect private keys.
const handleAuthStateChange = async (event: string, session: any) => {
if (event === 'SIGNED_IN') {
const user = session.user;
setIsSignedIn(true);
setUser(user); // Update the signed-in user state
// Check if the user already exists in the "users" table
const { data: existingUsers, error: userError } = await supabase
.from('users')
.select('*')
.eq('id', user.id);
if (userError) {
console.error('Error fetching user:', userError.message);
} else if (existingUsers && existingUsers.length > 0) {
// The user already exists in the "users" table, do nothing
} else {
// The user does not exist in the "users" table, create a new wallet and get the private key
const web3 = new Web3();
const account = web3.eth.accounts.create();
const privateKey = account.privateKey;
setPrivateKey(privateKey);
// Insert the private key into the users table
const { data: userData, error: insertError } = await supabase
.from('users')
.insert([{ id: user.id, email: user.email, private_key: privateKey }]);
if (insertError) {
console.error('Error inserting user:', insertError.message);
}
}
}
};
const authListener = supabase.auth.onAuthStateChange(handleAuthStateChange);
return () => {
authListener.data?.unsubscribe();
};
},);
async function signInWithEmail(data: FormData) {
setLoading(true);
setMessage('');
const { email } = data;
const { user, error } = await supabase.auth.signIn({
email,
provider: 'email',
redirectTo: 'https://localhost:3000',
});
if (error) {
setMessage(error.message);
} else {
setMessage('Check your email for the magic link!');
reset();
}
setLoading(false);
}```
I fed the current Actions implementation into GPT. This is the result:
Your current implementation is quite good and modular. However, there is always room for improvement. One suggestion is to separate the API calls into a dedicated service class to further decouple the action logic from the API logic. Here's an example of how you can refactor your code:
import axios from 'axios';
class RewardService {
static async triggerReward(address, token, amount) {
const data = { receiver: address, token, amount };
try {
const res = await axios.post('api/reward', data);
return res.data;
} catch (err) {
throw new Error(`Reward API failed: ${err.message}`);
}
}
}
export default RewardService;
import RewardService from './RewardService';
export default class Action {
action: ActionType;
constructor(id: string) {
const action = actions[id];
if (action) {
this.action = action;
} else {
throw new Error('Action not found');
}
}
async trigger(address: string): Promise<any> {
const { receiver, token, xp } = this.action;
return RewardService.triggerReward(address, token, xp);
}
get(): ActionType {
return this.action;
}
}
const action = new Action('connect');
const shareAction = new Action('share');
async function handleConnect() { const res = await action.trigger('0x987'); }
By doing this, you have separated the API call logic from the action logic, which makes your code more modular and easier to maintain. If you need to change the API implementation in the future, you only need to update the RewardService class.
Below is an example of SDK functions that could be added to handle rewards. Couple of open questions. @3rdBlock , if you have any feedback, please just comment below.
sdk.Reward.createExperienceToken({ amount: 1000 });
// @QUESTION - Could NIKE use an existing token?
sdk.Reward.createRewardCurrency({
name: 'NIKE',
symbol: 'NIKE',
amount: 1000,
});
// @QUESTION - Do we need to create new functions for this or just use sdk.Token.mint() and sdk.Token.burn();
sdk.Reward.mintRewardCurrency({});
sdk.Reward.burnRewardCurrency({});
// @QUESTION - Are badges lazyMinted and claim, or just minted? LazyMinted means user pays gas fees.
sdk.Reward.createBadge({
name: 'Hero',
symbol: 'HERO',
tokenURI: 'ipfs://',
});
// @QUESTION - Do we have seperate triggers for actions and missions?
// sdk.Reward.action.trigger({
// receiver: '0x123...',
// amount: 1000,
// token: '0x345...',
// id: 'Connect',
// });
// sdk.Reward.mission.trigger({
// receiver: '0x123...',
// amount: 1000,
// token: '0x345...',
// id: 'Connect',
// });
// ACTION TRIGGER
sdk.Reward.trigger({
receiver: '0x123',
tokens: [
// XP
{ amount: 1000, token: '0x123...', type: RewardType.XP },
],
});
// MISSION TRIGGER
sdk.Reward.trigger({
receiver: '0x123',
tokens: [
// NFT Badge
{ amount: 1, token: '0x123...', type: RewardType.BADGE },
// XP
{ amount: 1000, token: '0x123...', type: RewardType.XP },
// Reward Currency
{ amount: 1000, token: '0x123...', type: RewardType.REWARD_CURRENCY },
],
});
Could NIKE use an existing token?
This should be possible, we need this feature.
Do we need to create new functions for this or just use sdk.Token.mint() and sdk.Token.burn();
No need to create new functions, this means we can use the same functions on multiple token types.
Are badges lazyMinted and claim, or just minted? LazyMinted means user pays gas fees.
This is interesting. lazyMinted means the end user will have a greater understanding of what's going on. We could also point users to the faucet if they need a small amount of gas to mint. Minted is more of a web2 experience but will the user understand that they're receiving an NFT? Especially with metamask which is utterly useless at displaying all assets. Likely the NFT will just get lost in the wallet.
Do we have seperate triggers for actions and missions?
Not sure this is needed as the only change is going to be the token rewarded. Or do we want to be strict in how the actions/XP vs Missions are rewarded. i.e. only allow actions to reward XP. Perhaps a dev wants to reward something extra for a certain action?
Could NIKE use an existing token? I agree. As long as the App has access to transfer tokens, this should work.
Do we need to create new functions for this or just use sdk.Token.mint() and sdk.Token.burn();
I'm 50/50 on this one. I think for now. We can use sdk.Token.mint()
and sdk.Token.burn()
, however I think to make it more accessible, there would be no harm in creating extra helpers.
Are badges lazyMinted and claim, or just minted? LazyMinted means user pays gas fees.
Again, 50/50. TBH. I think we could eventually give the developer a choice depending on their requirements. I think initially to keep it simple, the owner just uses mintTo
and sends it to the reward receiver
Do we have seperate triggers for actions and missions? Great! I'm thinking the same thing. It would allows developers to use any tokens. Could just be NFTs. I could see in a education app, the actions would be "complete_video" and you just get an NFT as "proof of I watched a thing and learn't some stuff"
@3rdBlock I'm just playing around with the actions.json
a little. Thoughts on this? It would allow any token to rewarded for actions.
[
{
"id": "login",
"amount": 50,
"description": "Log in to the platform",
"token": "0x123",
"type": RewardType.XP
},
{
"id": "create_post",
"amount": 1,
"description": "Create a new post",
"token": "0x123",
"type": RewardType.Badge
}
]
query getRewardsByUserAndRequirements($user: String!, $ids: [String!]) {
rewards(where: {user: $user, type_id_in: $ids}) {
id
app {
id
}
user {
id
}
token {
id
}
type
type_id
}
}
{
"user": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc",
"ids": [
"connect",
"connect_mission"
],
"app": "0x3ddc444466c0c5f28d973dbd4fd4b157707b2356"
}
Codesandbox
Assumptions
Acceptance Criteria
For each item below, please provide a summary of a link to a codesandbox validating your research.