Skip to content

Fee sponsorship via merchant server seems broken #1516

@bnolens

Description

@bnolens

My transactions return a 300 status when I route them via my merchant server.

When I call wallet_prepareCalls on my merchant server and call wallet_sendPreparedCalls (including the feeSignature, signature, etc) on Porto's relay, the transaction fails.
But when I send both calls to Porto's relay, the transaction succeeds.

The wallet I'm using in the client is a funded Porto Account.
The wallet on my merchant server is also a funded Porto Account.
I'm testing on Celo.

Here's a simplified version of what I'm doing in Typescript.

import { createClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

// Configuration
const MERCHANT_URL = "https://xxxx.ngrok-free.app/porto/merchant";
const RELAY_URL = "https://rpc.porto.sh";
const CHAIN_ID = 42220; // Celo

// User Credentials (from .env)
const USER_PRIVATE_KEY = process.env.USER_PRIVATE_KEY as `0x{string}`;
const account = privateKeyToAccount(USER_PRIVATE_KEY);
const USER_ADDRESS = account.address;

// Call Data (USDT Transfer)
const USDT_ADDRESS = "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e";
const CALL_DATA =
  "0xa9059cbb0000000000000000000000009afe6f1417638dd734596cf7af6fcc99c4bd96650000000000000000000000000000000000000000000000000000000000002710";

async function main() {
  // 1. Prepare Calls (Merchant)
  const merchantClient = createClient({ transport: http(MERCHANT_URL) });
  const relayClient = createClient({ transport: http(RELAY_URL) });

  console.log("\n1. Requesting wallet_prepareCalls from Merchant...");

  let prepareResponse;
  try {
    prepareResponse = await merchantClient.request({
      method: "wallet_prepareCalls" as any,
      params: [
        {
          from: USER_ADDRESS,
          chainId: `0x${CHAIN_ID.toString(16)}`,
          calls: [
            {
              to: USDT_ADDRESS,
              data: CALL_DATA,
              value: "0x0",
            },
          ],
          capabilities: {
            feeToken: USDT_ADDRESS,
            meta: {},
          },
        },
      ],
    });
    console.log("Prepare Success. Digest:", prepareResponse.digest);
    const quote = (prepareResponse as any).context?.quote?.quotes?.[0];
    if (quote) {
      console.log("Payment Token:", quote.intent?.paymentToken);
    }
  } catch (error) {
    console.error("Prepare Failed:", error);
    process.exit(1);
  }

  // 2. Sign Digest (User)
  console.log("\n2. Signing digest with User Private Key...");
  const signature = await account.sign({ hash: prepareResponse.digest });
  console.log("Signature generated.");

  // 3. Send Prepared Calls (Relay)
  console.log("\n3. Sending wallet_sendPreparedCalls to Relay...");

  let bundleId;
  try {
    const sendResponse = await relayClient.request({
      method: "wallet_sendPreparedCalls" as any,
      params: [
        {
          context: prepareResponse.context,
          capabilities: prepareResponse.capabilities, // Includes feeSignature from merchant
          signature: signature,
        },
      ],
    });
    bundleId = sendResponse.id;
    console.log("Send Success! Bundle ID:", bundleId);
  } catch (error) {
    console.error("Send Failed:", error);
    process.exit(1);
  }

  // 4. Check Status
  console.log(`\n4. Checking status for Bundle ID: ${bundleId}...`);
  await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2s

  try {
    const status = await relayClient.request({
      method: "wallet_getCallsStatus" as any,
      params: [bundleId],
    });
    console.log("Bundle Status:", JSON.stringify(status, null, 2));
  } catch (error) {
    console.error("Check Status Failed:", error);
  }
}

main();

My merchant server looks like this:

import { Router, Route } from "porto/server";

const porto = Router({ basePath: "/porto" }).route(
  "/merchant",
  Route.merchant({
    address: process.env.MERCHANT_ADDRESS as `0x{string}`,
    key: process.env.MERCHANT_PRIVATE_KEY as `0x{string}`,
  }),
);

Bun.serve({
  ...porto,
  port: 3000,
});

console.log("Server running on http://localhost:3001/porto/merchant");

Am I doing something wrong here?
The lack of error messages coming from Relay makes it very hard to debug. Any plans to work on this?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfeesrpc

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions