In both use cases we will validate a smart contract function transfer, in other words, we will validate a transfer of ERC-20 tokens.
Before we start, here is an example transfer method function call in solidity that we will call to validate:
transfer(address to, uint256 amount) public {
address owner = _msgSender();
_transfer(owner, to, amount);
}
Use Case #1: validate a transaction of transfer of ERC-20 tokens using dApp frontend and dApp backend
Initialize Contract
In the dApp frontend, initialize a contract and connect it to a signer.
FRONTEND
import { ethers } from "ethers";
const provider = new ethers.providers.Web3Provider(
(window as any).web3.currentProvider
);
const signer = provider.getSigner();
const yourTokenContract = new ethers.Contract(
yourContractAddress,
yourContractAbi,
signer
);
const connectedTokenContract = yourTokenContract.connect(signer);
Now to validate a transaction, we need to get the information about a transaction that is about to happen without actually executing it. And for that, we can use the ethers.js contract.populateTransaction function that returns populatedTransaction object to represent the transaction that would need to be signed and submitted to the network for execution without actually executing it.
Let’s populate a transaction with a transfer method for validation. First, import our CUBE3 frontend library and create CUBE3 validation object. For that - use constructCube3Validation method that takes in two parameters - populatedTransaction object (Make sure to set chainId and value field, where value field is amount of ether being interacted with in the blockchain) and a boolean trackNonce used to determine whether to track nonce.
FRONTEND
FRONTEND
import { constructCube3Validation } from '@cube3/sdk-ui'
import { BigNumber } from "ethers"
//Our previous code <...>
const txData = await connectedTokenContract.populateTransaction.transfer("to", amount)
//make sure to have your chainId and value defined inside this object
txData.chainId = 5;
txData.value = BigNumber.from(0);
const cube3TransactionData = await constructCube3Validation(txData, true);
Send to Backend
Send CUBE3 data to the dApp (your) backend with cube3TransactionData in the request body.
Now let’s review the dApps backend code. We just sent cube3TransactionData in the body to the dApps backend. From here we can ask CUBE3 to validate transaction for us using cube3ValidateTransaction method from @cube3/sdk-nodejs library. For that, you need to set your apiKey andvalidationUrl.
Now we can get a validation response from the dApps backend and check if our transaction (token transfer) was valid or not. At this point we are done with our detection.
Another method of checking would be inspecting your browser:
Inspect > Network > Validate > Response
FRONTEND
import { Cube3ValidationResponse } from '@cube3/sdk-nodejs'
const cube3ValidationResponse: Cube3ValidationResponse = await fetch('https://validation.cube3.ai/api/validation', requestParams).then(
response => response.json() as Promise<Cube3ValidationResponse>
)
if (cube3ValidationResponse.valid === true) {
*//Validation is done, now you can safely do a transaction on a blockchain*
const receipt = await signer.sendTransaction(txData);
console.log(receipt)
}
Save to Cube
After sending the transaction to the chain, we will save the transaction ID to the system. To do so, you will need a transaction ID and an internal CUBE3 transaction ID. However, we cannot do that on frontend because our API-KEY would get exposed. So, we will call dApps backend with receipt hash and internal CUBE3 transaction ID in body.
Use Case #2: Validate a transfer of ERC-20 tokens in the backend only
The code is going to be very similar to our previous use case.
To interact with a contract we will again use ethers.js to initialize a wallet, provider, and get a signer. To initialize a provider we can use any blockchain API (Alchemy, QuickNode, etc.) but in this use case, we are gonna use Infura(RPC). After that, we are going to populate transaction. Again, don’t forget to set chainId and transaction value.
import { cube3ValidateTransaction, cube3SaveTransaction } from '@cube3/sdk-nodejs'
import { ethers, BigNumber } from "ethers";
export class ValidationController {
public static async validateTransaction(req: Request, res: Response) {
const privateKey = "<...>"
const wallet = new ethers.Wallet(privateKey);
const provider = new ethers.providers.JsonRpcProvider("https://<testnet>.infura.io/v3/project-id");
const signer = wallet.connect(provider);
const yourTokenContract = new ethers.Contract(
yourContractAddress,
yourContractAbi,
signer
);
const txData = await yourTokenContract.populateTransaction.transfer("to", amount)
txData.chainId = 5;
txData.value = BigNumber.from(0);
}
}
In the previous use case, we used constructCube3Validation that returned Cube3TransactionData from @cube3/sdk-ui. In this case - we are going to construct CUBE3 data ourselves. For this, we will still use @cube3/sdk-ui library simply for accessing Cube3Transaction object that in the previous use case was generated for us.
Now we can validate using @cube3/sdk-nodejs libraries cube3ValidateTransaction method. At this point, we are done with our detection.
const cube3ValidatedTransaction = await cube3ValidateTransaction(validationUrl, apiKey, cube3ValidationData)
if (cube3ValidatedTransaction.valid === true) {
//Validation is done, now you can safely do a transaction on a blockchain
const receipt = await signer.sendTransaction(txData);
console.log(receipt)
}
After sending the transaction to the chain, we save the transaction ID to the system. To do so, you will need a transaction ID and an internal CUBE3 transaction ID.
import { cube3SaveTransaction } from '@cube3/sdk-nodejs'
if (cube3ValidatedTransaction.valid === true) {
//<...>
await cube3SaveTransaction(saveTransactionUrl, apiKey, receipt.hash, cube3ValidatedTransaction.cube3TxId)
}
Finished implementation of transaction validation - backend only
import { cube3ValidateTransaction, cube3SaveTransaction } from '@cube3/sdk-nodejs'
import { ethers, BigNumber } from "ethers";
import { Cube3Transaction, Cube3TransactionData } from '@cube3/sdk-ui'
import { Cube3ValidationData } from '@cube3/sdk-nodejs'
export class ValidationController {
public static async validateTransaction(req: Request, res: Response) {
const privateKey = "<...>"
const wallet = new ethers.Wallet(privateKey);
const provider = new ethers.providers.JsonRpcProvider("https://goerli.infura.io/v3/project-id");
const signer = wallet.connect(provider);
const yourTokenContract = new ethers.Contract(
yourContractAddress,
yourContractAbi,
signer
);
const txData = await yourTokenContract.populateTransaction.transfer("to", amount)
txData.chainId = 5;
txData.value = BigNumber.from(0);
const apiKey = "<...>"
const validationUrl = "http:/<...>/api/transaction/validate"
const saveTransactionUrl = "https://<...>/api/transaction"
const userAgent = req.headers['user-agent']
const transaction: Cube3Transaction = {
to: txData.to,
from: txData.from,
nonce: txData.nonce,
data: txData.data,
valueHex: txData.value._hex,
chainId: txData.chainId
}
const cube3TransactionData: Cube3TransactionData = {
transactionData: transaction,
trackNonce: true,
userAgent: userAgent,
clientIp: req.ip
}
const cube3ValidatedTransaction: Cube3ValidationData = {
transactionData: cube3TransactionData,
clientIp: req.ip
}
const validation = await cube3ValidateTransaction(validationUrl, apiKey, cube3ValidatedTransaction)
if (validation.valid === true) {
//Validation is done, now you can safely do a transaction on a blockchain
const receipt = await signer.sendTransaction(txData);
await cube3SaveTransaction(saveTransactionUrl, apiKey, receipt.hash, validation.cube3TxId)
console.log(receipt)
}
}
}