Validation API

How to integrate the validation API for Real-Time Transaction Monitoring

To integrate CUBE3 validation API into your javascript/typescript project please ensure you have completed the following prerequisites:

  1. Get CUBE3 SDK api-key from RISK API page in https://panorama.cube3.ai/ and insert validation key where api-key is referenced here:

  1. Setup CUBE3 SDK for dApp frontend, to install using npm:

    • If you are using npm: npm i @cube3/sdk-ui

  2. Setup CUBE3 SDK for dApp backend, to install using npm:

    • If you are using npm: npm i @cube3/sdk-nodejs

We have two use cases for the CUBE3 SDK:

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

  1. 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);
  1. Send to Backend

Send CUBE3 data to the dApp (your) backend with cube3TransactionData in the request body.

				FRONTEND	
																FRONTEND
const headers = {
  "Content-type": "application/json",
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, POST, PATCH, PUT, DELETE, OPTIONS",
  "Access-Control-Allow-Headers": "Origin, Content-Type, X-Auth-Token"
}

const requestParams = {
  method: "POST",
  body: JSON.stringify(cube3TransactionData),
  headers: headers
}

const validationResponse = await fetch('https://validation.cube3.ai/api/validation', requestParams)

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 and validationUrl.

				BACKEND	
														BACKEND
import { cube3ValidateTransaction, Cube3ValidationData } from '@cube3/sdk-nodejs'
import { ethers } from "ethers";

export class ValidationController {
  public static async validateTransaction(req: Request<{}, {}, Cube3TransactionData>, res: Response) {
    const apiKey = "<...>"
    const validationUrl = "https://<...>/api/transaction/validate"
    
    const cube3ValidationData: Cube3ValidationData = {
      cube3TransactionData: req.body,
      clientIp: req.ip
    }

    const cube3ValidationResponse = await cube3ValidateTransaction(validationUrl, apiKey, cube3ValidationData)

    return res.status(200).send(JSON.stringify(cube3ValidationResponse))
  }
}

This is what validation returns:

interface Cube3ValidationResponse {
  valid: boolean
  payload?: string
  error?: string
  combinedScore: number
  cube3TxId: string
}

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)
}
  1. 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.

				  FRONTEND												FRONTEND

import { cube3SaveTransaction } from '@cube3/sdk-nodejs'

if (cube3ValidationResponse.valid === true) {
  //<...>
  const receipt = await signer.sendTransaction(txData);

  const saveTransactionData = {
    transactionId: receipt.hash,
    cube3TxId: cube3ValidationResponse.cube3TxId
  }

  const requestParams = {
    method: "POST",
    body: JSON.stringify(saveTransactionData)
  }
	
  await fetch('<...>/api/saveTransaction', requestParams)  
}

Now, let's save dApps transaction. For that, you need to set your apiKey and saveTransactionUrl.

                                  BACKEND

import { cube3ValidateTransaction, Cube3ValidationData } from '@cube3/sdk-nodejs'
import { ethers } from "ethers";

export class ValidationController {
  public static async saveTransaction(req: Request, res: Response) {
    const apiKey = "<...>"
    const saveTransactionUrl = "https://<...>/api/transaction"

    const transactionId = req.body.transactionId
    const cube3TxId = req.body.cube3TxId
		
    const response = await cube3SaveTransaction(saveTransactionUrl, apiKey, transactionId, cube3TxId)
    return res.status(200).send(JSON.stringify(response))
  }
}

Finished implementation of transaction validation for dApp frontend and dApp backend

				   FRONTEND												FRONTEND

import { constructCube3Validation } from '@cube3/sdk-ui'
import { ethers, BigNumber } from "ethers";
import { Cube3ValidationResponse } from '@cube3/sdk-nodejs'

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);

const txData = await connectedTokenContract.populateTransaction.transfer("to", amount)
txData.chainId = 5;
txData.value = BigNumber.from(0);

const cube3TransactionData = await constructCube3Validation(txData, true);

const requestParams = {
  method: "POST",
  body: JSON.stringify(cube3TransactionData)
}

const cube3ValidationResponse = await fetch('your-backend/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);

  //Save transaction
  const saveTransactionData = {
    transactionId: receipt.hash,
    cube3TxId: cube3ValidationResponse.cube3TxId
  }
  
  const requestParams = {
    method: "POST",
    body: JSON.stringify(saveTransactionData)
  }

  await fetch('<...>/api/saveTransaction', requestParams)  
}
				 BACKEND													BACKEND

import { cube3ValidateTransaction,
         Cube3ValidationData
         cube3SaveTransaction } from '@cube3/sdk-nodejs'
import { ethers } from "ethers";

export class ValidationController {
  public static async validateTransaction(req: Request<{}, {}, Cube3TransactionData>, res: Response) {
    const apiKey = "<...>"
    const validationUrl = "https://<...>/api/transaction/validate"
    
    const cube3ValidationData: Cube3ValidationData = {
      cube3TransactionData: req.body,
      clientIp: req.ip
    }

    const cube3ValidationResponse = await cube3ValidateTransaction(validationUrl, apiKey, cube3ValidationData)

    return res.status(200).send(JSON.stringify(cube3ValidationResponse))
  }

  public static async saveTransaction(req: Request, res: Response) {
    const apiKey = "<...>"
    const saveTransactionUrl = "https://<...>/api/transaction"

    const transactionId = req.body.transactionId
    const cube3TxId = req.body.cube3TxId
		
    const response = await cube3SaveTransaction(saveTransactionUrl, apiKey, transactionId, cube3TxId)
    return res.status(200).send(JSON.stringify(response))
  }
}

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.

//<...> previous code
import { Cube3TransactionData } from '@cube3/sdk-ui'
import { Cube3ValidationData } from '@cube3/sdk-nodejs'

const apiKey = "<...>"
const validationUrl = "http:/<...>/api/transaction/validate"
const saveTransactionUrl = "https://<...>/api/transaction"
const userAgent = req.headers['user-agent']

const cube3Transaction: Cube3Transaction = {
  to: txData.to,
  from: txData.from,
  nonce: txData.nonce,
  data: txData.data,
  valueHex: txData.value._hex,
  chainId: txData.chainId
}

const cube3TransactionData : Cube3TransactionData = {
  cube3Transaction: cube3Transaction,
  walletAddress: "0x<...>",
  trackNonce: true,
  userAgent: userAgent
}

const cube3ValidationData: Cube3ValidationData = {
  cube3TransactionData: cube3TransactionData,
  clientIp: req.ip
}

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,
      walletAddress: "0x<...>",
      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)
    }
  }
}

Last updated