> Can you walk us through the technical implementation? ## The technical setup Blackbelt consists of three elements: our server, the Metamask snap, and our website front-end. The server retrieves data and calculates the smart contract risk scores. The Metamask snap as well as our website call the server and supply the specific contract address within the https request. The full implementation of our project can be found [here](https://github.com/s-a-malik/blackbelt). ## The server Our server consists of a flask back-end that returns security scores and metadata as json. When a `GET` request is initiate to the route `/security_score`, the function `security_score()` is called. First, all supplied parameters, such as contract address, user address, chain id, and caller origin, will be unpacked and then partially parsed into the function `compute_security_score(contract_address, chain)` which will retrieve on-chain data and computes security scores for multiple variable (e.g., contract age). To speed up data retrieval inside Metamask Snaps, we provide cached results if available when the server is called via Snaps (see variable `is_snaps`). ```python def security_score(): """ Returns the security score and the metadata used to compute it for a given contract address. Saves result to ipfs and local storage. Params: - user_address: the address of the user to check - contract_address (str): eth address of the contract - chain_id (int): chain to check the contract on Returns: - score (int): The security score (0-100) - contract_info (dict): """ user_address = request.args.get('user_address', type=str) contract_address = request.args.get('contract_address', type=str) chain_id = request.args.get('chain', default=1, type=int) is_snaps = request.args.get('is_snaps', default=False, type=str) #Check if chached results should be provided is_snaps = True if is_snaps == "true" else False if is_snaps and contract_address in contract_to_score: #Retrieve from cache and return return contract_to_score[contract_address][0] else: chain = "mainnet" if chain_id == 1 else "goerli" #Calculate new security score output = compute_security_score(contract_address, chain) if output["status"] != "ok": return output #Add to the server cache contract_to_score[contract_address].append(output) user_to_transactions[user_address].append(output) return output ``` The `compute_security_score` function (in the `logic.py` file) calculates a heuristic risk score based on on-chain data from Coinbase Cloud APIs, etherscan APIs, and our own community-curated server-side database of reported malicious addresses. A few examples of the type of factors used to compute the score is shown in the snippet below. For example, we check if the contract is declared as verified and audited on etherscan, and how old the contract is. ``` # in the compute_security_score function: # ... verified = is_verified(contract_address, chain) audited = is_audited(contract_address, chain) transactions, users, deployed_date_unix = numberOfTransactionsAndUsersAndAge(contract_address) min_age_of_contract_in_days = (time.time() - deployed_date_unix) / 86400 num_times_reported = blacklist_dict[contract_address] # ... ``` In the future, we plan to integrate more sources of information that could help identify malicious contracts (such as automated verification tools and existing blacklist databases), and also employ more nuanced computation of the risk score, for example, using machine learning methods. ## The snap For the snap we use the transactions insights API which is triggered whenever a transaction is happening. The snap consists of four steps. First, the transaction key details are retrieved from the transaction struct. Second, the tx details are send to our server and we retrieve the security score plus metadata. Third, we check the request status and handle any errors. Fourth, we return the security score and metadata as json object within Metamask snaps. ```typescript export const onTransaction: OnTransactionHandler = async ({ transaction, chainId, }) => { // Get tx data const user_address = transaction.from; const contract_address = transaction.to; const chain_id = chainId.split(":")[1]; // Call api for risk assessment const url = 'https://blackbelt.xyz/security_score?contract_address='+contract_address+'&user_address='+user_address+'&chain='+chain_id+'&is_snaps=true'; let x = await fetch(url); let data = await x.json(); // Cases for returning insights if (data.status == "ok"){ const individual_scores = stringify_json('individual_scores', data) const individual_details = stringify_json('contract_info', data) return { insights: {"Risk Assessment": data.risk_level, "Security Score": data.security_score, "Recommendation": data.recommendation, "Details": individual_details, "Individual Scores": individual_scores, "Assessment Timestamp": data.risk_assessment_timestamp, "IPFS Storage Hash": data.ipfs_hash}, }; } else if(data.status =='error, not a contract address'){ return { insights: {"No Score Available": "No interaction with a smart contract detected.", "Assessment Timestamp": data.risk_assessment_timestamp}, }; } else if(data.status =='error, unsupported chain'){ return { insights: {"Chain Not Supported": "We are currently supporting Ethereum and Goerli only. We are working on adding more networks."}, }; } else { return { insights: {"Unknown Error": "An unknown error occured. Please contact the team and try again later."}, }; } }; ``` ## The website The website is written in react and is used to quickly test our feature before adding it to Metamask. Users can try our feature by supplying a contract address and pressing "Rate now". If users like our application, they can add it to their Metamask by clicking "Add Metamask Snap" at the top right corner. ![](https://i.imgur.com/JIbf7E2.jpg)