open-format / GetStarted

This starter is an introduction into a new decentralised world and what we believe to be the starting point for 90% of all future applications.
https://docs.openformat.tech/
MIT License
1 stars 0 forks source link

Interface Research #1

Closed tinypell3ts closed 1 year ago

tinypell3ts commented 1 year ago

Codesandbox

Assumptions

  1. Web2 authentication is going to be handle by individual dApps.
  2. To use the same authentication across multiple apps, the developer will use the same database.
  3. The first version will be built with React.

Acceptance Criteria

For each item below, please provide a summary of a link to a codesandbox validating your research.

3rdBlock commented 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);
    }```
tinypell3ts commented 1 year ago

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:

  1. Create a new service class named RewardService:
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;
  1. Modify the Action class to use the new 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;
  }
}
  1. Use the refactored Action class:
    
    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.
tinypell3ts commented 1 year ago

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 },
      ],
    });
3rdBlock commented 1 year ago

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?

tinypell3ts commented 1 year ago

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"

tinypell3ts commented 1 year ago

@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
  }
]
tinypell3ts commented 1 year ago

Validated subgraph query

Query

query getRewardsByUserAndRequirements($user: String!, $ids: [String!]) {
  rewards(where: {user: $user, type_id_in: $ids}) {
    id
    app {
      id
    }
    user {
      id
    }
    token {
      id
    }
    type
    type_id
  }
}

Variables

{
  "user": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc",
  "ids": [
    "connect",
    "connect_mission"
  ],
  "app": "0x3ddc444466c0c5f28d973dbd4fd4b157707b2356"
}