🤝Recommendation Engine

Learn how to build a recommendation engine with Airstack APIs and integrate it into your React application.

You will focus on building a recommendation engine based on addresses that have interacted with the user in token transfers.

To get all the addresses, you will need the TokenTransfers API to fetch the historical token transfers from the user’s wallet.

Prerequisites

Step 1: Create a Query Using AI

In this example, suppose the user is 0xd8da6bf26964af9d7eed9e03e53415d37aa96045.

Therefore, in the prompt, type the following text to get all the token transfers from 0xd8da6bf26964af9d7eed9e03e53415d37aa96045:

For 0xd8da6bf26964af9d7eed9e03e53415d37aa96045, get all token transfers

The Airstack AI will return the following query and response:

query GetTokenTransfers {
  Wallet(input: {identity: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", blockchain: ethereum}) {
    tokenTransfers {
      id
      chainId
      blockchain
      from {
        identity
        blockchain
      }
      to {
        identity
        blockchain
      }
      type
      tokenAddress
      operator {
        identity
        blockchain
      }
      amount
      formattedAmount
      tokenId
      amounts
      tokenIds
      tokenType
      transactionHash
      blockTimestamp
      blockNumber
      tokenNft {
        id
        address
        tokenId
        blockchain
        chainId
        type
        totalSupply
        tokenURI
        contentType
        contentValue {
          image {
            original
          }
        }
        metaData {
          name
          description
          image
          attributes {
            trait_type
            value
            displayType
            maxValue
          }
          imageData
          backgroundColor
          youtubeUrl
          externalUrl
          animationUrl
        }
        rawMetaData
        lastTransferHash
        lastTransferBlock
        lastTransferTimestamp
      }
      token {
        id
        address
        chainId
        blockchain
        name
        symbol
        type
        totalSupply
        decimals
        logo {
          small
          medium
          large
          original
          external
        }
        contractMetaDataURI
        contractMetaData {
          name
          description
          image
          externalLink
          sellerFeeBasisPoints
          feeRecipient
        }
        rawContractMetaData
        baseURI
        lastTransferTimestamp
        lastTransferBlock
        lastTransferHash
        projectDetails {
          collectionName
          description
          externalUrl
          twitterUrl
          discordUrl
          imageUrl
        }
        tokenTraits
      }
    }
  }
}

Use Airstack AI's Best Practices to get the best and most accurate result for your query.

These are a lot of data gained from single query, but not every field is needed. Thus, in the next step, you’ll modify the query to trim down some of the fields and only have the one necessary for building the contacts recommendation feature.

Step 2: Modify Your Query

The fields that are most important for the recommendations engine feature are the from(sender) and to(receiver) fields

Therefore, you can reduce the query:

query GetTokenTransfers {
  Wallet(
    input: {identity: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", blockchain: ethereum}
  ) {
    tokenTransfers {
      from {
        identity
      }
      to {
        identity
      }
    }
  }
}

From this response, either the sender or the receiver will have a different address to 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 that you can utilize and format as a list of contact recommendations for your user.

Despite getting the necessary fields, this query only fetches token transfers from Ethereum.

Instead of making a multiple requests to index each blockchains, you can use Airstack to construct a cross-chain query:

query GetTokenTransfers {
  ethereum: Wallet(input: {identity: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", blockchain: ethereum}) {
    tokenTransfers {
      from {
        identity
      }
      to {
        identity
      }
    }
  }
  polygon: Wallet(input: {identity: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", blockchain: polygon}) {
    tokenTransfers {
      from {
        identity
      }
      to {
        identity
      }
    }
  }
}

Step 3: Add Variables to Your Query

Adding variables to your constructed Airstack query is very simple.

Simply replace the input with a variable called $address:

query GetTokenTransfers($address: Identity!) {
  ethereum: Wallet(input: {identity: $address, blockchain: ethereum}) {
    tokenTransfers {
      from {
        identity
      }
      to {
        identity
      }
    }
  }
  polygon: Wallet(input: {identity: $address, blockchain: polygon}) {
    tokenTransfers {
      from {
        identity
      }
      to {
        identity
      }
    }
  }
}

Step 4: Create Your React Project (Optional)

First, create an empty React project if you don’t have any. Otherwise, you can skip this part and jump to getting your API key below.

First, use Vite to generate a new React project:

npm create vite@latest airstack-demo -- --template react

After the process is successfully completed, an airstack-demo folder containing the empty React project will be created. Now, you can enter the folder and download some dependencies:

cd airstack-demo && npm install

Step 5: Get Your API Key

First, login to your Airstack account and go to Settings. Under the Default Key, you can find and copy your API key.

Then, create a .env file if you don’t have any by running the following command:

touch .env

And, paste your API key to your environment variable .env file.

VITE_AIRSTACK_API_KEY=YOUR_API_KEY

Step 6: Integrate Airstack into Your React App

To use Airstack in your React project, install the Airstack React SDK.

npm install @airstack/airstack-react

Once the installation is complete, you should see that it has been added to your package.json as a dependency and is ready to be imported.

{
  "dependencies": {
    "@airstack/airstack-react": "^0.3.3"
    // other dependencies
  }
  // other config
}

Now that all the basic setups are complete, let’s go to src/App.jsx file and add Airstack to your React application.

First, let’s delete all the original template contents inside src/App.jsx and add new code for Airstack integration.

To use the Airstack React SDK, first, initialize the SDK by adding the following code in App.jsx.

import { init, useQuery } from "@airstack/airstack-react";

init(import.meta.env.VITE_AIRSTACK_API_KEY);

Then, add a basic component with useQuery hook added to call the query that you constructed in previous sections to build contact recommendations based on historical token transfers:

import { init, useQuery } from "@airstack/airstack-react";

init(import.meta.env.VITE_AIRSTACK_API_KEY);

function App() {
  const query = `
  query GetTokenTransfers($address: Identity!) {
    ethereum: Wallet(input: {identity: $address, blockchain: ethereum}) {
      tokenTransfers {
        from {
          identity
        }
        to {
          identity
        }
      }
    }
    polygon: Wallet(input: {identity: $address, blockchain: polygon}) {
      tokenTransfers {
        from {
          identity
        }
        to {
          identity
        }
      }
    }
  }  
  `;
  const variables = {
    address: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
  };

  const { data, loading, error } = useQuery(query, variables, { cache: false });

  if (loading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  return <>{JSON.stringify(data)}</>;
}

export default App;

With this simple code, you essentially just integrated Airstack APIs that fetch all the addresses that interacted with the 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 address in token transfers across both Ethereum and Polygon. The app will output the JSON result on the UI and will look like this:

At its current state, you have just successfully loaded Airstack and its data responses to the UI. However, it is a very bare minimum and the data is loaded still in its JSON stringified format.

As an improvement, let’s add an input box to provide the user address and some basic `div` elements to render the list of recommended contacts.

To do so, modify the components code as follows:

import { init, useLazyQuery } from "@airstack/airstack-react";
import { Fragment, useMemo, useState } from "react";

init(import.meta.env.VITE_AIRSTACK_API_KEY);

function App() {
  const query = `
  query GetTokenTransfers($address: Identity!) {
    ethereum: Wallet(input: {identity: $address, blockchain: ethereum}) {
      tokenTransfers {
        from {
          identity
        }
        to {
          identity
        }
      }
    }
    polygon: Wallet(input: {identity: $address, blockchain: polygon}) {
      tokenTransfers {
        from {
          identity
        }
        to {
          identity
        }
      }
    }
  }  
  `;

  const [fetchTokenTransfers, { data, loading }] = useLazyQuery(
    query,
    {},
    { cache: false }
  );
  const [identity, setIdentity] = useState("");

  const recommendationsArray = useMemo(() => {
    const { ethereum, polygon } = data || {};
    const { tokenTransfers: ethereumTokenTransfers = [] } = ethereum || {};
    const { tokenTransfers: polygonTokenTransfers = [] } = polygon || {};
    return [...ethereumTokenTransfers, ...polygonTokenTransfers]
      .map((transfer) => {
        if (
          transfer.from.identity !== identity &&
          transfer.from.identity !==
            "0x0000000000000000000000000000000000000000"
        ) {
          return transfer.from.identity;
        } else if (
          transfer.to.identity !== identity &&
          transfer.to.identity !== "0x0000000000000000000000000000000000000000"
        ) {
          return transfer.to.identity;
        } else {
          return;
        }
      })
      .filter(Boolean);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data]);

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        width: "100vw",
      }}
    >
      <form
        onSubmit={(e) => {
          e.preventDefault();
          fetchTokenTransfers({ address: identity });
        }}
      >
        <input
          value={identity}
          onChange={(e) => {
            e.preventDefault();
            setIdentity(e.target.value);
          }}
          placeholder="Enter Ethereum address"
          style={{
            width: "300px",
            height: "30px",
            borderRadius: "5px",
            padding: "5px",
          }}
        />
        <button type="submit">Resolve</button>
      </form>
      <h1>Recommendations</h1>
      <p>
        {loading ? (
          "Loading..."
        ) : (
          <Fragment>
            {recommendationsArray?.map((recommendation, key) => (
              <div
                key={key}
                style={{
                  margin: ".5rem",
                  padding: "1rem",
                  border: "1px solid white",
                }}
              >
                {recommendation}
              </div>
            ))}
          </Fragment>
        )}
      </p>
    </div>
  );
}

export default App;

The new UI looks as follows:

As you can see, aside from having a component that renders the addresses that can be recommended based on the input address, you also added several new aspects.

First, since the app needs to call the API manually instead of every first render, it is essential to replace useQuery with useLazyQuery, which provides you with an additional function that is named fetchTokenTransfers to call the API on each button clicked.

In order to make sure that the fetchTokenTransfers is called accordingly to the user input, the input value in the input element is toggled to the identity React state such that each time the value in the input box changes the state will be changed.

Thus, when the user clicks the button, fetchTokenTransfers will always call the Airstack API with the variables provided by the user input directly.

Once the fetchTokenTransfers is called and the data variable is changed to the latest response, the recommendations list will be generated and formatted based on the data variable. Here, recommendationsArray is going to have data as a dependency, and therefore the value will be updated when data is updated.

Thus, with the recommendationsArray value that contains the list of contact recommendations address, it can be rendered in the component wrapped in a simple div element. However, if you’re building for your application, feel free to modify the UI as you’d like.

Congratulations! 🎉You’ve just learned how to use Airstack to build a simple contact recommendation feature for and integrate the APIs into your React application.

Last updated

#300: add-user-details

Change request updated