# 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.

- 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"
}
```