5. Interaction

Contents

Intro

Ideally, interacting with your protected smart contract would be in conjunction with the frontend and backend - the user presses a button (i.e. does a transaction to your protected smart contract) which initiates a backend call, the frontend receives payload from it and proceeds to submit it to the blockchain.

But for the sake of simplicity, let's just create a simple nodejs application that executes a transaction on our protected function.

1. Creating cube3_transaction_executer nodejs application

Let's start by creating a nodejs project using typescript.

mkdir cube3_transaction_executer
cd cube3_transaction_executer
npm init -y
npm install typescript --save-dev
npm install ts-node typescript --save-dev

echo '{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "esModuleInterop": true
  }
}' > tsconfig.json

create src/index.ts file where we will write our implementation. Initially, add this inside:

async function generatePayload() {
  //all of the code will go here
}

// Wrapping the call in an IIFE
(async () => {
  await generatePayload();
})();

We will need to access your protected contracts ABI. So paste the file containing ABI it into cube3_transaction_executer/src.

2. Installing necessary packages

Install CUBE3 packages:

npm i @cube3/sdk-nodejs
npm i @cube3/sdk-ui

For blockchain interactions, we will use ethers.js:

npm install ethers@6.9.0

3. Setup contract object

First, import all necessary functions, Cube3ProtectedErc20 and setup constants:

import {getBytes, ethers, Wallet, JsonRpcProvider } from "ethers";
import { constructCube3Validation } from "@cube3/sdk-ui";
import { cube3ValidateTransaction, cube3SaveTransaction } from "@cube3/sdk-nodejs";
import Cube3ProtectedErc20 from "./Cube3ProtectedErc20.json"

const validationUrl = "https://validation-api.cube3.ai/api/v1/transaction/validate"
const saveTransactionUrl = "https://validation-api.cube3.ai/api/v1/transaction"
const validationApiKey = "API-KEY"

const yourProtectedContractAddress = "0x<...>"

const payloadLength = 352;
const emptyByteArray: Uint8Array = new Uint8Array(payloadLength);

const mintTokensTo = "MINT-TOKENS-TO-ADDRESS";
const mintAmount = 1;

const yourWalletPrivateKey = ""

const rpcURL = ""

To generate a payload, you need to make an HTTP request to the CUBE3 validation service. For that, we will use CUBE3 sdk. Let's begin.

For CUBE3 to generate a payload, it needs transaction data. For this, we will need to create a transaction object and pass it to CUBE3 sdk.

The code below goes into generatePayload() function.

For any interaction with a smart contract, we need to create a Wallet and Contract objects.

const provider = new JsonRpcProvider(rpcURL);
const signer = new Wallet(yourWalletPrivateKey, provider)

const cube3TokenContract = new ethers.Contract(yourProtectedContractAddress, Cube3ProtectedErc20.abi, signer);

At this point, we have the ability to call our protected contract. Let's move to payload generation.

4. Payload generation

These are the steps:

  1. Before we send an HTTP request to the CUBE3 validation endpoint, we need to generate transaction data without actually executing the transaction. For this, we are going to utilize ethers populateTransaction to create the transaction object which stores information about the transaction.

  2. Pass it into constructCube3Validation function which will extract the necessary data for payload generation.

  3. Send an HTTP request to the CUBE3 validation endpoint using cube3ValidateTransaction function, which will return us whether this transaction is safe and the payload along with other information (to learn more about what the validation service returns, read more here). If CUBE3 finds that transaction to be unsafe/malicious, the payload will not be provided and the transaction will not be possible.

//1st step
const mintFuncData = await cube3TokenContract.transferBlockByParameter.populateTransaction(
    mintTokensTo,
    mintAmount,
    emptyByteArray,
    { //specify gas limit explicitly
      gasLimit: BigInt(400000)
    }
);

const mintTxData = await signer.populateTransaction(mintFuncData)
mintTxData.chainId = BigInt(11155111) //sepolia by default
mintTxData.value = BigInt(0) //we are not sending any eth

//2nd step
const cube3TransactionData = await constructCube3Validation(mintTxData, false, true);

//3rd step
const validation = await cube3ValidateTransaction(validationUrl, validationApiKey, cube3TransactionData, "")

//generated payload
console.log(validation.payload)

5. Interacting with your protected functions

In the previous step, we generated the payload for our protected mintCube3Protected function. Now all that is left is to execute the transaction on the blockchain by passing the payload into the function call.

if (validation.valid) {
  const mintTxReceipt = await cube3TokenContract.mintCube3Protected(
    mintTokensTo,
    mintAmount,
    validation.payload,
    {
      gasLimit: BigInt(400000), //this gas limit value is just an example and doesn't have to be 400000
    }
  );

  console.log("mintTxReceipt", mintTxReceipt)

  //lastly, save your transaction to view it on panorama dashboard
  await cube3SaveTransaction(saveTransactionUrl, validationApiKey, mintTxReceipt.hash, validation.cube3TxId)
}

From the project root directory, in cmd run:

 npx ts-node src/index.ts

And the transaction receipt will appear. Copy the transaction hash and paste it into etherscan (for example sepolia) https://sepolia.etherscan.io/tx/<your-hash> on which you should see a successful transaction, for example:

Lastly, you can analyze any protected transaction on the Panorama dashboard:

To learn more about the transaction dashboard, read here.

Last updated