Skip to content

Farcaster Frames

Enable cross-chain and cross-token payments in Farcaster Frames using Glide

In this guide, we will create a Farcaster Frame that lets users mint an ETH NFT using USDC.

Here's the NFT contract we'll use: 0x70cd0c150bf...e55ba383003. The contract has a mintFor function that allows users to mint by sending ETH to the contract.

We'll use the Glide to let users pay for the mint using USDC on Base.

Setup

We'll use the Frog.fm framework to build the Farcaster Frame. But, you can use any framework you like. Glide works with the underlying Frames spec.

Steps

1. Create a frog app

If you haven't already, create a Frog app. You can learn more about Frog here.

npm
npm init frog

2. Install Glide & viem

npm
npm install @paywithglide/glide-js viem

3. Create a Glide config

import { createGlideConfig, chains } from "@paywithglide/glide-js";
 
export const config = createGlideConfig({
  projectId: "your project id",
 
  // Lists the chains where payments will be accepted
  chains: [chains.base],
});

4. Create the entrypoint frame

Create the entrypoint frame that shows the NFT and a button to begin the flow.

The Mint button has an action set to /mint, which we'll use to create the Glide session for minting the NFT.

app.frame("/", (c) => {
  return c.res({
    image:
      "https://storage.withfabric.xyz/loom/3daa8a0e-e808-4fa6-aa6a-7f6780c116ed.png",
    imageAspectRatio: "1:1",
 
    intents: [<Button action="/mint">Mint with USDC (Base)</Button>],
  });
});

5. Add Glide session to state

We need to track the Glide session across frames, so we'll add the Glide session id to the state.

type State = { 
  glideSessionId?: string; 
}; 
 
export const app = new Frog<{ State: State }>({
  title: "Glide Demo",
  initialState: {}, 
});

6. Add the Neynar middleware

We need a list of connected wallet addresses owned by the user. The NFT will be delivered to one of these addresses.

export const app = new Frog<{ State: State }>({
  title: "Glide Demo",
  initialState: {},
}).use(  
  neynar({  
    apiKey: "NEYNAR_FROG_FM",  
    features: ["interactor", "cast"],  
  }),  
);  

5. Handle the /mint route

When the user clicks the Mint button, we'll create a Glide session for minting the NFT.

This frame acts as our "confirmation" screen. We can show the final payment amount and the user's connected wallet address.

app.frame("/mint", async (c) => {
  // Get the connected addresses from the user (uses the Neynar middleware).
  // The NFT will be delivered to the first verified address.
  const ethAddresses = c.var.interactor?.verifiedAddresses.ethAddresses;
 
  if (!ethAddresses?.length) {
    throw new Error("No verified Ethereum addresses found.");
  }
 
  await c.deriveState(async (state) => {
    // Create a Glide session for minting the NFT
    const { sessionId } = await createSession(glideConfig, {
      paymentCurrency: currencies.usdc.on(chains.base),
 
      chainId: chains.base.id,
      abi: fabricAbi,
      address: "0x70cd0c150bf6c952b93ce6481b571e55ba383003",
      functionName: "mintFor",
      args: [ethAddresses[0], 3000000000000n],
      value: 3000000000000n,
    });
 
    // Store the Glide session ID in the state
    state.glideSessionId = sessionId;
  });
 
  return c.res({
    image:
      "https://storage.withfabric.xyz/loom/3daa8a0e-e808-4fa6-aa6a-7f6780c116ed.png",
    imageAspectRatio: "1:1",
 
    intents: [
      // A transaction button that will prompt the user to pay in their wallet
      <Button.Transaction target={`/pay`}>Pay now</Button.Transaction>
    ],
 
    action: "/tx-status",
  });
});

7. Handle the /pay route

When the user clicks "Pay now", we need to return the payment transaction hash to the user.

We do this by fetching the session from Glide and returning the payment transaction from it.

app.transaction("/pay", async (c) => {
  const glideSessionId = c.previousState.glideSessionId;
 
  if (!glideSessionId) {
    throw new Error("No Glide session ID found.");
  }
 
  const { unsignedTransaction } = await getSessionById(
    glideConfig,
    glideSessionId,
  );
 
  if (!unsignedTransaction) {
    throw new Error("No Glide transaction found.");
  }
 
  return c.send({
    chainId: `eip155:${chains.base.id}`,
    to: unsignedTransaction.to,
    data: unsignedTransaction.input,
    value: hexToBigInt(unsignedTransaction.value),
  });
});

8. Handle the /tx-status route

Finally, when the user has completed the payment, the /tx-status route will show the status of the transaction.

We also need to take the payment transaction hash from the user and use it to update the Glide session status.

If the minting is complete, we provide the user with a link to view the transaction. Otherwise, they can refresh the page to check the status.

app.frame("/tx-status", async (c) => {
  const {
    transactionId,
    buttonValue,
    previousState: { glideSessionId },
  } = c;
 
  // The payment transaction hash is passed with transactionId if the user just completed the payment.
  // If the user hit the "Refresh" button, the transaction hash is passed with buttonValue.
  const transactionHash = buttonValue || transactionId;
 
  if (!transactionHash || transactionHash === '0x' || !glideSessionId) {
    throw new Error("Missing payment transaction hash or Glide session ID");
  }
 
  // Update the Glide session with the payment transaction hash, if the user just completed the payment.
  if (transactionId && transactionId !== '0x') {
    await updatePaymentTransaction(glideConfig, {
      sessionId: glideSessionId,
      hash: transactionId,
    });
  }
 
  const { sponsoredTransactionHash, sponsoredTransactionStatus } =
    await getSessionById(glideConfig, glideSessionId);
 
  return c.res({
    image: (
      <div
        style={{
          display: "flex",
          justifyContent: "center",
          fontSize: 44,
          marginTop: "200px",
        }}
      >
        Status: {sponsoredTransactionStatus}
      </div>
    ),
 
    intents: [
      sponsoredTransactionHash ? (
        <Button.Link
          href={`https://basescan.org/tx/${sponsoredTransactionHash}`}
        >
          View on Explorer
        </Button.Link>
      ) : (
        <Button value={transactionHash} action="/tx-status">
          Refresh
        </Button>
      ),
    ],
  });
});