7. Powering bots with webhooks

Let's make a Farcaster bot powered by on-chain data emitted through webhooks

Goal

People love bots, but creating a bot with the existing blockchain infrastructure solutions out there is hard. Even just getting access to the raw on-chain data can be hard. Luckily, these are problems of the past when using sim.

In this example, we show how we created a simple Farcaster bot that posts a cast upon any whale transfer (Ether or any ERC20). We use Uniswap V3 as the price oracle--recall we used Uniswap V2 in another example--and emit a webhook whenever the USD value of the transfer is above a threshold.

If you want to skip to the end, here's the canvas that already has everything built.

Steps

Here's a high-level overview of the pipeline we are going to build:

the pipeline

Before you start

You need a place to run your server that receives the webhooks and casts to Farcaster. We are using Heroku, but you can use any sort of provider that allows webhooks. We often use https://webhook.site to prototype our webhooks.

Setting up the pipeline

Adding components

  1. Start by adding a Data source component. We want our pipeline to run at the tip, so we leave To blank and set From to something close to the latest block number.
  2. Add an EVM Lambda component and connect it to the right handle of the data source. For now, leave it as is. We'll come back to it in a bit.
  3. Then we add a Schema with the metadata we need for each cast. Draw a connection from the lambda to the schema component and populate the schema as follows:
NameType
txn_hashbytes32
from_addressaddress
from_resolved_namestring
token_namestring
usd_amountuint256
is_contractbool
  1. Finally we add a Webhook component. Point it to the server instance you have running and set a rate limit if you want. Then connect the schema to the webhook component.
  2. You're all set! 🎉

Writing the code

We want to hook on transfers of ether and ERC20s. Within both of those callbacks we want to look up the USD value of the transfers. If it is above our threshold, we emit a message to the webhook.

  1. We add a Transaction hook from the Global hook section and a transfer hook from the Predefined ERC-> ERC20 hook section. Make them post hooks and call them postTransaction and postErc20Transfer, respectively.
  2. The callback is very similar for the two hooks. Here's postTransaction.
CallContext storage ctx = getCallContext();
uint256 transferEthValueUsd = getUsdcTokenPrice(WRAPPED_ETH, ctx.value); 

if (WHALE_ALERT_LIMIT_USD < transferEthValueUsd){
  simEmitToSchema_whale_alert(
      SchemaWhale_alertColumns({
          txn_hash: ctx.txn.hash(),
          from_address: ctx.txn.from(),
          from_resolved_name: getEnsName(ctx.txn.from()),
          token_name: "ETH",
          usd_amount: transferEthValueUsd,
          is_contract: isAddressContract(ctx.txn.from())
      })
  );
}

The getUsdcTokenPrice function fetches the price of the token in terms of USD by calculating the price using a Uniswap V3 pool. If the amount is greater than our WHALE_ALERT_LIMIT_USD threshold we emit a Webhook message to the schema.

  1. That's basically it! Check the canvas for the full code. You'll see that we also added some logic to use the ENS reverse resolver to find a name, where one exists, for the transferrer. We got a little carried away wanting the bot to be hip and cool. Please follow it...

A server to handle the webhooks

When a Webhook request reaches the server, we need to inspect the data and cast to Farcaster.

  1. Here is the high-level version of the code. In short, we have a flask server running waiting for a request, we validate the signature on a request, and then post a constructed message to Farcaster.
import os
from farcaster import Warpcast
from flask import Flask, request

# [... helper functions etc.]

mnemonic = os.environ.get("MNEMONIC")
assert mnemonic, "Mnemonic seed not found."
client = Warpcast(mnemonic=mnemonic)
cache = MessageCache()

app = Flask(__name__)

@app.route('/post', methods=["POST"])
def post():
    # Verify that the message is from our sim-studio instance
    provided_signature = request.headers["svix-signature"].split(",")[1]
    generated_signatured = get_webhook_signature()
    assert provided_signature == generated_signatured, "We only post authenticated messages"

    text = format_message(request.json)
    response = client.post_cast(text=text)
    return response.cast.hash
  1. The other function worth sharing is the one that validates the request. Since you are exposing the server on the internet, it is recommended to have this validation in place to prevent misuse. You can read more about the svix validation here.
def get_webhook_signature():
    svix_id = request.headers["svix-id"]
    svix_timestamp = request.headers["svix-timestamp"]
    body = request.get_data(as_text=True)
    signed_content = f"{svix_id}.{svix_timestamp}.{body}"
    secret = os.environ.get("WEBHOOK_SECRET")
    assert secret is not None, "missing webhook secret"
    secret_bytes = base64.b64decode(secret.split('_')[1])

    signature = hmac.new(secret_bytes, msg=signed_content.encode('utf-8'), digestmod=hashlib.sha256).digest()
    signature_base64 = base64.b64encode(signature).decode('utf-8')

    return signature_base64

Key takeaways

You have seen how easy it is to write a bot using sim. Now think of what's possible:

  1. Protocol monitoring, e.g., detecting abnormal activity. Call a webhook whenever the parameters of a protocol cross some threshold.
  2. Seeking alpha, e.g., be notified through a webhook on transfers from high profile accounts or when they interact with some recently deployed smart contract.
  3. Other on-chain events, e.g., get a notification each day about new nouns minted through Nouns DAO.

Build your own bot, or some other application of webhooks. It probably won't be as cool as whalewatch, but go ahead and try. Join our Telegram to brag about it.


What’s Next