BLOG — Developer Tutorials
Gabriel - Gelato Ambassador

Gabriel - Gelato Ambassador

·

489 days ago

Connect Smart Contracts to Web2 API with Gelato Web3 Functions

Gelato Web3 Functions offers a powerful solution to connect on-chain smart contracts with off-chain Data. The on-chain component consists of the smart contract that you want to automate with off-chain data (APIs / subgraphs, etc), while the off-chain element is the Web3 Function that accesses Web2 APIs and processes data.

By configuring the Web3 Function Task, you can seamlessly integrate the two, enabling greater control and automation for decentralized applications. Let's explore how to create and leverage Web3 Functions for enhanced efficiency and functionality in the Dapps. In this blog post, we will demonstrate 5 ways you can integrate web2 APIs with smart contracts, complete with code examples for a hands-on understanding.

Update Oracle Smart Contract using CoinGecko Price API

Coingecko is a platform specializing in asset indexing, allowing users to track the prices of thousands of cryptocurrencies and tokens. In addition to providing real-time price information, Coingecko also offers data on market capitalization, trading volume, historical price trends, and more.

Thanks to the Web3 Functions, it is feasible to fetch the current price of a specific asset from Coingecko in real-time. For our example, we want to know the current price of Ethereum (ETH) you can use a Web3 Function to query Coingecko's API and retrieve the updated price.

Once you have obtained the desired price, you can use it on-chain to update smart contracts, trigger automated actions based on the price, or even create sophisticated financial mechanisms based on this external data.

Implementation

The first step involves writing a script containing the logic to return the execution call data from the Web3Function instance. This web3 function periodically updates an Oracle smart contract with pricing data, utilizing information retrieved from CoinGecko's price API.

Code Link

import { Web3Function, Web3FunctionContext } from "@gelatonetwork/web3-functions-sdk";
import { Contract, ethers } from "ethers";
import ky from "ky"; // we recommend using ky as axios doesn't support fetch by default

const ORACLE_ABI = [
  "function lastUpdated() external view returns(uint256)",
  "function updatePrice(uint256)",
];

Web3Function.onRun(async (context: Web3FunctionContext) => {
  const { userArgs, gelatoArgs, multiChainProvider } = context;
  const provider = multiChainProvider.default();

  // Retrieve Last oracle update time
  const oracleAddress = "0x71b9b0f6c999cbbb0fef9c92b80d54e4973214da";
  const oracle = new Contract(oracleAddress, ORACLE_ABI, provider);
  const lastUpdated = parseInt(await oracle.lastUpdated());
  console.log(`Last oracle update: ${lastUpdated}`);

  // Check if it's ready for a new update
  const nextUpdateTime = lastUpdated + 300; // 5 min
  const timestamp = (await provider.getBlock("latest")).timestamp;
  console.log(`Next oracle update: ${nextUpdateTime}`);
  if (timestamp < nextUpdateTime) {
    return { canExec: false, message: `Time not elapsed` };
  }

  // Get current price on coingecko
  const currency = "ethereum";
  const priceData: any = await ky
    .get(
      `https://api.coingecko.com/api/v3/simple/price?ids=${currency}&vs_currencies=usd`,
      { timeout: 5_000, retry: 0 }
    )
    .json();
  price = Math.floor(priceData[currency].usd);
  console.log(`Updating price: ${price}`);

  // Return execution call data
  return {
    canExec: true,
    callData: [{
      to: oracleAddress,
      data: oracle.interface.encodeFunctionData("updatePrice", [price])
  }],
  };
});

Automated Trades with Web3 Functions

Automated trading enables traders to implement complex strategies without constantly monitoring markets. Gelato Web3 Functions serve as powerful tools in this process, allowing for the execution of smart contracts based on specific conditions. We are using Uniswap SDK to monitor the price.

We'll be focusing on two trading strategies:

  1. Trailing Stop: This strategy will automatically push up the stop loss as the price rises, offering protection from downside risk. When the stop loss is hit, the trade will be exited, ensuring a level of security within the volatile market.

  2. Bounce Entry: With this approach, if the price begins to move upward beyond a specified threshold and we're not already in a trade, we'll enter the trade. This capitalizes on market momentum and can be an effective way to engage with trending assets.

Implementation

Smart contract- The Solidity Smart Contract ‘MockSwap’ does the "fake" swapping between USDC and WETH. It allows users to set operators, deposit funds, and perform buy or sell swaps between USDC and WETH.

Web3 Functions

  1. Trailing Stop Strategy

This strategy automatically pushes up the stop loss as the price rises, offering protection from downside risk. When the stop loss is hit, the trade will be exited, ensuring a level of security within the volatile market.

Here's how the Trailing Stop is implemented in the code:

  • If in a trade, and the price is increasing, it updates the maximum price (lastMax) to the current price.
  • If the price has not changed, it does nothing.
  • If the price has decreased but less than the exit threshold, it does nothing.
  • If the price decrease is greater than the exit threshold, it exits the trade by calling the swap function with the sell parameters.

Code Link

if (activeTrade) {

    /////  ==== We are IN TRADE ===================== /////
    ///  *****************************************************  ///
    if (lastMax == 0) {
      await storage.set("lastMax", price.toString());
      return { canExec: false, message: "Initiatig Price Exit" };
    }

    let diff = lastMax - price;
    if (diff < 0) {
      ///  *****************************************************  ///
      ///  Price is going up, update to new max
      ///  *****************************************************  ///
      await storage.set("lastMax", price.toString());
      console.log(
        `Old lastMax: ${lastMax.toString()}, New lastMax: ${price.toString()}`
      );
      return { canExec: false, message: "No Trade Exit ---> Price UP " };
    } else if (diff == 0) {
      ///  *****************************************************  ///
      ///  Price not moving doing Nothing
      ///  *****************************************************  ///
      return {
        canExec: false,
        message: `No Trade Exit ---> No Price change: ${price.toString()} `,
      };
    } else if (diff / lastMax < EXIT / 100) {
      ///  *****************************************************  ///
      ///  Price decrease too small, doing Nothing
      ///  *****************************************************  ///
      console.log(
        `Current lastMax: ${lastMax.toString()}, currentPrice: ${price.toString()}`
      );
      return {
        canExec: false,
        message: `No Trade Exit ---> Price decrease Small ${(
          (diff / lastMax) *
          100
        ).toFixed(2)} %`,
      };
    } else {
      ///  *****************************************************  ///
      ///  Price Decrease Greater than threshold ---> EXIT THE TRADE
      ///  *****************************************************  ///
      await storage.set("lastMin", price.toString());
  
      console.log(
        `Trade Exit---> Price Decrease ${((diff / lastMax) * 100).toFixed(
          2
        )} greater than ${EXIT} %`
      );

      const payload = await returnCalldata(price,mockSwapContract,user,false)
      return  payload;
    }
  }
  1. Bounce Entry Strategy

With the Bounce Entry approach, if the price begins to move upward beyond a specified threshold and we're not already in a trade, we'll enter the trade. This capitalizes on market momentum.

Here's how the Bounce Entry is implemented in the code:

  • If not in a trade and the price is going down, it updates the minimum price (lastMin) to the current price.
  • If the price has not changed, it does nothing.
  • If the price has increased but less than the entry threshold, it does nothing.
  • If the price increase is greater than the entry threshold, it enters a trade by calling the swap function with the buy parameters.
else {

    /////  ==== We are NOT in a trade ===================== /////
    ///  *****************************************************  ///
    if (lastMin == 0) {
      await storage.set("lastMin", price.toString());
      console.log("Initiatig Price Entry")

      // do initial transacrion
      const payload = await returnCalldata(price,mockSwapContract,user,true)
      return  payload;
    }

    let diff = price - lastMin;

    if (diff < 0) {
      ///  *****************************************************  ///
      ///  Price is going down, update to new min
      ///  *****************************************************  ///
      console.log(
        `Old lastMin: ${lastMin.toString()}, New lastMin: ${price.toString()}`
      );
      await storage.set("lastMin", price.toString());
      return { canExec: false, message: "No Trade Entry ---> Price Down " };
    } else if (diff == 0) {
      ///  *****************************************************  ///
      ///  Price not moving doing Nothing
      ///  *****************************************************  ///
      return {
        canExec: false,
        message: `No Trade Entry ---> No Price change: ${price.toString()} `,
      };
    } else if (diff / lastMin < ENTRY / 100) {
      ///  *****************************************************  ///
      ///  Price Increate too small, doing Nothing
      ///  *****************************************************  ///
      console.log(
        `Current lastMin: ${lastMin.toString()}, currentPrice: ${price.toString()}`
      );
      return {
        canExec: false,
        message: `No Trade Entry ---> Price Increase too small ${(
          (diff / lastMin) *
          100
        ).toFixed(2)} %`,
      };
    } else {
      ///  *****************************************************  ///
      ///  Price Increate Greater than threshold ---> Enter a TRADE
      ///  *****************************************************  ///

      await storage.set("lastMax", price.toString());

      const payload = await returnCalldata(price,mockSwapContract,user,true)

      return  payload;
    }
  }
});

Sports Betting Platform

When building a Sports betting platform, imagine you have a smart contract that facilitates the betting process. Users can place bets on various sports events, and the smart contract holds the funds until the outcome is determined. However, the smart contract itself cannot directly access real-world data, such as the result of a sports match. This is where Web3 Functions come into play.

Implementation

To determine the winning bets and trigger the payout process, you'll need to use Web3 Functions to connect your platform to external APIs that provide real-time sports data.

These APIs will supply the outcome of the sports events, and based on that data, the Web3 Function will execute transactions on the Blockchain. These transactions will handle the distribution of funds to the users who placed winning bets.

Once the sports event concludes, the Web3 Function fetches the relevant data from the API, processes it, and interacts with the smart contract to initiate the payout process. This automation ensures that winning bettors receive their rewards without the need for any centralized authority or manual intervention.

Web3Function.onRun(async (context: Web3FunctionContext) => {
  // Call SportsDB API to get game status & score
  const gameId = (context.userArgs.gameId as string) ?? "Argentina_vs_France";
  let gameEvent;
  try {
    const api = `https://www.thesportsdb.com/api/v1/json/3/searchevents.php?e=${gameId}`;
    const res = await axios.get(api);
    gameEvent = res.data.event[0];
  } catch (err) {
    return { canExec: false, message: `SportDb API call failed` };
  }

  // Wait for match to be finished
  console.log("Game details:", gameEvent);
  if (gameEvent.strStatus !== "Match Finished") {
    return { canExec: false, message: `Match not finished yet` };
  }

// Prepare the CallData to be executed  
  const BETTING_CONTRACT_ABI = ["function updateGameScore(string, uint16, uint16)"];
  const bettingInterface = new utils.Interface(BETTING_CONTRACT_ABI);

// Pushing the callData to the callData Array
  const callData = []
  callData.push({
    to: targetContract,
    data:bettingInterface.encodeFunctionData("updateGameScore",[
      gameId,
      parseInt(gameEvent.intHomeScore),
      parseInt(gameEvent.intAwayScore),
       ]) 
})
// Publish final score on-chain

  return { 
    canExec: true,
    callData: callData
    };
 });

Automated content creation on Lens using ChatGPT

The combination of AI and web3 has the potential to address existing challenges in the field of AI while also unlocking numerous innovative opportunities in web3.

Gelato’s Web3 Functions enable smart contracts to leverage the power of AI by connecting them to OpenAI ChatGPT API to generate creative content based on a given prompt. In our application, Gelato’s Web3 Functions help us to connect the off-chain Open AI API to generate creative content and automate it to schedule every 8 hours on Lens.

Implementation

We have LensGelatoGPT smart contract that handles the management of prompts for automated content creation using AI.

Gelato’s Web3 Functions code then invokes the OpenAI API to generate creative content, guided by the prompts obtained from the LensGPT Smart Contract. Following this, the Lens Publication Metadata is obtained and validated using the LensClient. Once validated, the content is encapsulated in the metadata, and uploaded to IPFS using the Web3Storage API.

Finally, the postData object is created which is then encoded into the call data for the LensHub contract's "post" function & then published on the Lens Protocol.

The culmination of this entire process is the appearance of your content on Lenster, a social media web application built using Lens Protocol.

Code Link

Generate AI Text Response and Metadata Validation

    const openai = new OpenAIApi(
       new Configuration({ apiKey: SECRETS_OPEN_AI_API_KEY })
     );
     const response = await openai.createCompletion({
       model: "text-davinci-003",
       prompt: ` ${contentURI} in less than 15 words.`,
      …
     });
     let text = response.data.choices[0].text as string;

 
  /// Build and validate Publication Metadata
       const uuid = uuidv4();
       const pub = {
       
         content: text,
       ... more code
       };

       // Checking Metadata is Correct
       ... code here

       // Upload metadata to IPFS
       const storage = new Web3Storage({ token: WEB3_STORAGE_API_KEY! });
       const myFile = new File([JSON.stringify(pub)], "publication.json");
       const cid = await storage.put([myFile]);
       contentURI = `https://${cid}.ipfs.w3s.link/publication.json`;

       console.log(`Publication IPFS: ${contentURI}`);
```

**Prepare Data for Blockchain Transaction**

```js
const postData = {
 profileId: prompt.profileId,
 contentURI,
 collectModule: collectModuleAddress,
 referenceModule: "0x0000000000000000000000000000000000000000",
 referenceModuleInitData: "0x",
};

const iface = new utils.Interface(lensHubAbi);

callDatas.push({
 to: lensHubAddress,
 data: iface.encodeFunctionData("post", [postData]),
});
```

NFT creation using Stable Diffusion API

In this use case, AI and blockchain work together to create a unique NFT experience. Gelato's Web3 Functions connect to StabilityAI's Stable Diffusion tool to generate NFT images from a given prompt. These images are then used as part of NFTs, linking the on-chain world of blockchain with the off-chain world of AI. The process simplifies the creation of visually appealing NFTs, making it more accessible and enhancing the overall user experience.

Implementation

GelatoNft smart contract: You'll learn how to mint new NFTs with this smart contract, maintaining an element of suspense until their final reveal.

Stable Diffusion: This AI tool from StabilityAI generates unique images that become the metadata for your NFTs. You'll explore how to integrate AI-driven imagery into your NFTs.

Gelato’s Web3 Functions: Serving as a conduit between your NFT smart contract and the Stable Diffusion API off-chain, these functions automate the assignment of AI-created images to your NFTs. By the end of this tutorial, you'll grasp how to use Gelato's Web3 Functions to streamline your NFT creation process.

Code Link

Identifying Unrevealed NFTs for Processing

This code identifies unrevealed Non-Fungible Tokens (NFTs) for processing. It checks the smart contract for NFTs that haven't been revealed and adds them to a batch for processing. Attributes like time are used internally to customize the NFT's properties, such as specifying whether the scene depicted is set at night or sunset.

// Retreive current state
const nft = new Contract(nftAddress as string, NFT_ABI, provider);
const lastProcessedId = parseInt(
  (await storage.get("lastProcessedId")) ?? "0"
);
const currentTokenId = (await nft.tokenIds()).toNumber();
console.log("currentTokenId", currentTokenId);
if (currentTokenId === lastProcessedId) {
  return { canExec: false, message: "No New Tokens" };
}

// Get batch of next token ids to process in parallel
const tokenIds: number[] = [];
let tokenId = lastProcessedId;
let nbRpcCalls = 0;
const MAX_RPC_CALLS = 30;
const MAX_NFT_IN_BATCH = 5;
while (
  tokenId < currentTokenId &&
  tokenIds.length < MAX_NFT_IN_BATCH &&
  nbRpcCalls < MAX_RPC_CALLS
) {
  // Check if token needs to be revealed or is already revealed
  tokenId++;
  const tokenURI = await nft.tokenURI(tokenId);
  if (tokenURI === NOT_REVEALED_URI) {
    tokenIds.push(tokenId);
  } else {
    console.log(`#${tokenId} already revealed!`);
  }
  nbRpcCalls++;
}

if (tokenIds.length === 0) {
  console.log(`All NFTs already revealed!`);
  await storage.set("lastProcessedId", tokenId.toString());
  return { canExec: false, message: "All NFTs already revealed" };
}

console.log("NFTs to reveal:", tokenIds);
const tokensData = await Promise.all(
  tokenIds.map(async (tokenId) => {
    // Generate NFT properties
    const isNight = await nft.nightTimeByToken(tokenId);
    const nftProps = generateNftProperties(isNight);
    console.log(
      `#${tokenId} Stable Diffusion prompt: ${nftProps.description}`
    );

Generating NFT Image with Stable Diffusion, Publishing Metadata on IPFS and updating smart contract

// Generate NFT image with Stable Diffusion
let imageUrl: string;
try {
  const stableDiffusionResponse = await fetch(
    "https://stablediffusionapi.com/api/v3/text2img",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        key: stableDiffusionApiKey,
        prompt: nftProps.description,
        // other parameters...
      }),
    }
  );
  const stableDiffusionData = await stableDiffusionResponse.json();
  imageUrl = stableDiffusionData.output[0] as string;
} catch (_err) {
  // error handling
}

// Publish NFT metadata on IPFS
const imageBlob = (await axios.get(imageUrl, { responseType: "blob" })).data;
const nftStorage = new NFTStorage({ token: nftStorageApiKey });
const imageFile = new File([imageBlob], `gelato_nft_${tokenId}.png`, {
  type: "image/png",
});
const metadata = await nftStorage.store({
  // metadata details...
});
console.log(`#${tokenId} IPFS Metadata ${metadata.url}`);

// Updating the smart contract
callDatas.push({
  to: nft.address,
  data: nft.interface.encodeFunctionData("revealNft", [
    token.id,
    token.url,
  ]),
});

About Gelato

Join our community and developer discussion on Discord.

Web3 Functions are available today in private beta. For more information, please check out the Web3 Functions documentation. To learn how to write, test, and deploy your own Web3 Functions, check out our in-depth tutorial.

Apply here to be one of the first to test Web3 Functions.

If you are reading about us for the first time

Gelato is web3’s decentralized backend empowering builders to create augmented smart contracts that are automated, gasless & off-chain aware on all major EVM-compatible blockchains including Ethereum, Arbitrum, Polygon, Optimism, zkSync, and many more. Over 400+ web3 projects rely on Gelato for years to power the execution of millions of transactions across DeFi, NFT, and Gaming. Gelato currently offers four services:

  • Web3 Functions: Connect your smart contracts to off-chain data & computation by running decentralized cloud functions.

  • Automate: Automate your smart contracts by executing transactions automatically in a reliable, developer-friendly & decentralized manner

  • Relay: Give your users access to reliable, robust, and scalable gasless transactions via a simple-to-use API

  • Gasless Wallet: A powerful SDK that enables developers to provide a world-class UX by combining Gelato Relay + Safe's Smart Contract Wallet, enabling Account Abstraction

Witness the ongoing journey towards a decentralized future led by Gelato!

Subscribe to our newsletter and turn-on your Twitter notifications to get the most recent updates about the Gelato ecosystem!

If you are interested in being part of Gelato team and build the future of the internet browse the open positions and apply here.

Gabriel - Gelato Ambassador

Gabriel - Gelato Ambassador