# Contract Features: The contract should have these requirements: - SBT token standards. - Contract management (With 2 role: Owner & Admin). - Add admin - Remove admin - Trasferable, Burnable (Admin only) - Allow to update token metadata (Admin only) including baseURI & tokenURI for future migration to use IPFS if needed. # Contract Functions: Predefine field: - Default token holder = `contract address` #### For server operations: - `safeMint`: Mint a single token. Receive tokenID + address -> mint the token to the given address. - We need to upload token metadata to our server first, then pass the tokenURI + user's address to call this function (replace user's address with default holder address if user don't have). - For the tokenID number, we can either input as parameter or use autoincrement number in the contract. - By using autoincrement number in the contract, it will never fail due to duplicated id. But we only sure about the id of the token after the tx is confirmed -> need additional check for post-confirmed process to map to creds token in database with the id just minted. **(1)** - Predefine the tokenID in the parameter will help to reduce the post-confirmed check. Only need to listen for tx confirmed and update status. The token data & id mapping should be easiy predefine. This solution could make the tx failed if we mint a duplicated id but should not a big issue if we handle it well in our DB. **(2)** *-> I'm more inclined towards the option (2)* - `batchMint`: Mint multiple tokens. - Similar to `safeMint` but receive a list of (tokenInfo + address) (+tokenID if we choose (2)) - `batchMintToDefaultHolder`: Mint multiple tokens to default holder. - `safeTransferFrom`: Transfer token, inherit from ERC721, override to be transfered by the admin only. - Do we want admin to transfer ANY tokens or only the tokens that owned by default token holder address? **(3)** - `safeBatchTransferFrom`: Transfer multiple tokens, ERC721 doesn't support this function in original but we should add it to our contract as our system scales up, there will be a need to transfer many tokens simultaneously to prevent bottlenecks. The logic should similar with `safeTransferFrom` except for it will receive multiple tokens parameter. - Receive a list of (tokenID + recipient address). Validate the owner of each token should be contract owner before transfer. - `burn`: Burn a single token. override to be burned by admin only. - `batchBurn`: Burn a multiple tokens. called by admin only. #### For migration: - Migrate tokens: - `setBaseURI`: Update token baseURI - Migrate owner: - `transferOwnership`: transfer ownership of the contract - `setDefaultTokenHolder`: update default token holder (should transfer all tokens to the new holder first). #### For owner to grant permission: - `addAdmin`: add a new admin, only Owner can execute - `removeAdmin`: remove an existing admin, only Owner can execute #### To be confirmed: - `authorizedTransfer`: get user's signature & admin execute only to migrate/transfer token. - Only need if in (3) we choose the option to only transfer default holder's tokens. # Interaction flows: These flows indicate client + server + contract interaction flow. ***NOTE: To avoid conflicts when sending multiple transactions at the same time, each flow should have a SINGLE admin wallet responsible for executing the processes.*** ### Mint flow: ```mermaid sequenceDiagram autonumber actor User as User participant Client as Client Side participant Backend as Creds Server rect rgb(191, 223, 255) note right of User: Client request to mint User ->>+ Client : Mint Client ->>+ Backend : Submit token mint request Backend ->> Backend : Validate metadata Backend ->> Backend : Upload token metadata Backend ->>+ Database : Insert pending mint request Database -->>- Backend : OK Backend -->>- Client : token info Client -->>- User: Pending token end rect rgb(223, 191, 255) note right of Database: Background minting process Background ->>+ Background: Start CronJob Background ->>+ Database: get mint token requests Database -->>- Background: pending mint token requests Background ->> Background: Prepare transaction info Background ->>- Blockchain : Call `batchMint` contract function Blockchain ->> Blockchain : confirm & contract storage updated Blockchain ->>+ Background : Event token minted Background ->>- Database : Update token status to minted/confirmed end rect rgb(255, 223, 191) note right of User: Client get token info User ->>+ Client : Get token info Client ->>+ Backend: Pull token data Backend ->>+ Database: Pull token data Database -->>- Backend: Token data + status Backend -->>- Client : token info Client -->>- User: Show token end ``` **Why splitting into background and run cronjob?** - Avoid many transtions at the same time -> dropped/failed/rejected by blockchain. - Optimize performance & fee by collecting multiple tokens to mint batch at the same time. - Easier to handle failures and retries by setting up another similar job to check. - Ability to scale & reduce stress on the main server. - Admin key will be stored in the background sevice only, so we can apply more restrictions on access this service to improve security. **What will the backgroud job do?** - Schedule every 30s-1m to collect mint requests that was inserted into the database by server. - Create the transaction for the these mint requests and submit to blockchain. - Monitor transaction status: - Update the mint requests to confirmed status for confirmed tx. - Move to retry job to send it again for failed tx. ### Transfer flow (Creds 2.0): ```mermaid sequenceDiagram autonumber actor User as User participant Client as Client Side participant Backend as Creds Server rect rgb(191, 223, 255) note right of User: Client request to transfer to wallet User ->>+ Client : Transfer request Client ->>+ Backend : Submit token transfer to wallet request Backend ->> Backend : Validate user & token available Backend ->>+ Database : Insert pending transfer request Database -->>- Backend : OK Backend -->>- Client : OK Client -->>- User: OK end rect rgb(223, 191, 255) note right of Database: Background transfer process Background ->>+ Background: Start CronJob Background ->>+ Database: get transfer token requests Database -->>- Background: pending transfer token requests Background ->> Background: Prepare transaction info Background ->>- Blockchain : Call `safeBatchTransferFrom` contract function Blockchain ->> Blockchain : transaction confirmed Blockchain ->>+ Background : Event token transfered Background ->>- Database : Update token status end ``` ### Burn flow (Creds 2.0): ```mermaid sequenceDiagram autonumber actor User as User participant Client as Client Side participant Backend as Creds Server rect rgb(191, 223, 255) note right of User: Client request to burn token User ->>+ Client : Burn request Client ->>+ Backend : Submit burn request Backend ->> Backend : Validate user & token Backend ->>+ Database : Insert pending burn request Database -->>- Backend : OK Backend -->>- Client : OK Client -->>- User: OK end rect rgb(223, 191, 255) note right of Database: Background burn process Background ->>+ Background: Start CronJob Background ->>+ Database: get burn token requests Database -->>- Background: pending burn token requests Background ->> Background: Prepare transaction info Background ->>- Blockchain : Call `batchBurn` contract function Blockchain ->> Blockchain : transaction confirmed Blockchain ->>+ Background : Event token updated Background ->>- Database : Update token status end ``` # Token metadata Reference: https://docs.opensea.io/docs/metadata-standards #### Token metadata should contains these following fields: - `name`: Token's name - `description`: Token's description - `image`: the thumnailURl of the token - `animation_url`: URL of the original artwork (video/webpage) *We are free to add more custom fields to support displaying better in our website (See my second example).* #### URL format: - It would be good if we can make the URL can be interfered from tokenID, something like `https://token.creds.life/${tokenID}`. In this way, we could eliminate the need to send the `tokenURI` to the smart contract when minting, as it can be interpolated. If we don't want to, that's still acceptable; we can handle the mapping (`tokenID` -> `tokenURI`) within the smart contract. -> We will use `https://collectible.creds.life/${tokenID}` #### Example of some other token uris: - https://nft.blockgames.com/dice/3907 - https://token.artblocks.io/483000162 # Database design ### `tokens` table. | Name | Type | Description | | -------- | -------- | -------- | | id | BigInt | unique token id could be an autoincrement number | | token_uri | Text | token metadata uri | | user_id | Text | foreign key to link with user table | | address | Text | the address holding the token.* | ... | ... | other fields of the token | (*) The `address` that holding the token should be updated after the transaction was confirmed by the background job. -> We can determine the token status directly by the holder's `address` field (No need to join the `transaction_requests` table to check): - `address` = `null` or `empty` -> token waiting for minting. - `address` = `default holder address` minted to creds's address - `address` = `any other address` successfully transfered to user's wallet. - `address` = `null address` (`0x0000000000000000000000000000000000000000`)token was burned. ### `transaction_requests` table I want to abstract the table to support any trasaction requests (open to support `transfer` + `burn` in the future). | Name | Type | Description | | -------- | -------- | -------- | | id | Text | auto generated id, for internal management | | contract_address | Text | Creds's contract address | | network | Text | blockchain network | | token_id | BigInt | token id | | action | Text | contract action we want to call (mint/transfer/burn) | | payload | JSON | the params of the action in json format | | tx_hash | Text | transaction id after submiting transaction | | tx_status | Text | Status of request | | retries | Number | Number of retries | To ensure that transactions do not unexpectedly fail due to duplicate transaction requests or minting of duplicate token IDs, we should implement appropriate constraints from beginning: - Should create unique index for (`tokenID` + `action`) then when we insert a mint request with same token id -> failed when insert. Example data of a mint transaction request: ```json { "id": "63ecf2bf-cfbe-4ace-bbfd-1fb8707cce8e", "contract_address": "0x....", "token_id": 1, "action": "mint", "payload": { "token_id": 1, "address": "0xB272c2B1770111Fc3e58d3bB6Cf8C1bc50c86Cf6" // address we want to mint the token to (user's address or default holder) }, "tx_status": "pending" } ``` **NOTE**: Another solution can be applied for our case (I don't strong recommend but want to mention to reference): By creating only 1 single talbe to manage `mint_requests`. It may worked but not clean because of combining the transaction info & the token info, and cannot extend for future use (Need to create another table for `trasfer_requests` & `burn_requests`). # Gnosis Safe & Security: ### Why using Gnosis Safe - Enhanced Security: Gnosis Safe allows for the creation of multi-signature wallets, requiring multiple signatures (approvals) to execute transactions. This adds an extra layer of security compared to a single account where a compromised private key could result in unauthorized access and transactions. - Risk Distribution: Multi-signature wallets distribute the risk associated with private key management across multiple parties. This reduces the likelihood of a single point of failure compromising the entire wallet. - Multi-Owner Control: Gnosis Safe supports multiple owners, each with their own set of private keys. This enables joint control and decision-making, making it suitable for group funds or organizations where consensus is required for transactions. **For example:** We have a Gnosis Safe with a 2-of-3 multi-signature setup. Three people with three different wallets collaborate to create a new wallet. Every transaction requires the agreement of at least two people to be sent. As a result, even if one person is hacked, and their key is stolen, the Gnosis Safe wallet remains secure since the hacker cannot execute any transactions with it. ### Deploy contract with Gnosis Safe - Deploy contract using Gnosis Safe requires extra setup & the deployer need to control the keys to deploy the contract. This is not the ultimate solution. - The alternative way is we can deploy using a normal wallet, then transfer the owner to the Gnosis safe account. ### Execute contract functions with Gnosis Safe - We can execute the contract functions using the `Transaction Builder` feature provided by Gnosis safe website/app. ![Screenshot 2023-12-08 at 13.56.10](https://hackmd.io/_uploads/B1KzRExUT.png) - After deploying we will have the contract address and ABI of the contract. Input these 2 values then we will able to choose the function to execute using gnosis safe. ***NOTE: My suggestion is only use this Master Wallet for granting admin permissions. All other operations should be executed by the admin. We SHOULD NOT use it for any other transactions, keeping it private and offline as much as possible.*** # Deployment ### Testnet https://testnet-zkevm.polygonscan.com/address/0xedBd926df1A2A99d84B456f3616F94592fFCBf4c ### Mainnet deployment process: - Create Deployment Wallet & deposit MATIC - Using Deployment Wallet to deploy a new contract -> Contract Address. - Sign in to polyscan & connect to the Deployment Wallet to update contract information on polyscan. - Using Deployment Wallet to transfer ownership & update default token holder to Gnosis Safe wallet. - After all, Gnosis Safe wallet will become the owner of the contract, we can use this wallet to grant admin permission for other accounts. *** Set up Gnosis Safe Wallet. Could be 2 of 3 or 2 of 2 Wallet. - Matt's account. - Hien's account. - Deployment's account. ## Submit mint request ### Flow ```mermaid sequenceDiagram autonumber actor User as User participant Client as Client Side participant Backend as Creds Server User ->>+ Client: View art order detail User ->> Client: Click mint for user Client ->>+ Backend: call POST /mint-by-superadmin Backend ->> Backend: Validate request & user permission Backend ->>+ Database: Get art order by ID Database -->>- Backend: art order detail Backend ->> Backend: Generate tokenID rect rgb(223, 191, 255) note right of S3: metadata upload Backend ->> Backend: Create artwork metadata json Backend ->>+ S3: upload json metadata S3 -->>- Backend: uri link end Backend ->>+ Database: Create cred token Database -->>- Backend: OK Backend ->>+ Database: Create transaction request Database -->>- Backend: OK Backend -->>- Client: 200 OK Client -->>- User: Success ``` *Note: - Mapping 1-1 `art order` & `cred token`. - `token metadata uri` = `base uri` + `tokenId` - `metadata upload` process can be done later. ### API `POST /mint-by-superadmin`: Submit a mint request - Request Body ```jsonld { artOrderId: "abc", walletId: "def", //nullable platformUserId: "xyz" } ```