chainlink

Chainlink Function calling Alpaca API multiple times instead of once


I'm running into an issue with Chainlink Functions that call the Alpaca API. The function completes successfully but seems to execute multiple times, causing 3 orders to be placed instead of just 1. This does not happen in the simulation; it's only reproducible on-chain.

Expected Behavior: The function should place a single market order on Alpaca. Actual Behavior: The contract invocation creates multiple identical orders (exactly 3, in my case).

Observation: This behavior does not occur when running the Chainlink Functions simulation locally. It only appears on-chain (Base Sepolia).

Question: Is it possible that _sendRequest is retrying after some internal timeout or error condition? Or could there be some other reason that multiple calls get made?

Context

DON ID: fun-base-sepolia-1 Deployed: Contract Request ID: 0x442676c1692e799bee3f14b6293e1185aa31cad6be7161ebbf792f7869ba0fee

Here is the JavaScript snippet used in the source code for the Chainlink Function:

async function main() {
  ethers = await import("npm:ethers@6.10.0");

  const symbol = args[0];
  const qty = symbolPrecision(symbol, args[1]); // Convert to 18 decimals

  console.log(`Buying ${qty} ${symbol}...`);

  _checkKeys();

  const alpacaBuyRequest = Functions.makeHttpRequest({
    method: "POST",
    url: "https://paper-api.alpaca.markets/v2/orders",
    headers: {
      accept: "application/json",
      content_type: "application/json",
      "APCA-API-KEY-ID": secrets.alpacaKey,
      "APCA-API-SECRET-KEY": secrets.alpacaSecret,
    },
    data: {
      side: side,
      type: "market",
      time_in_force: "gtc",
      symbol: symbol,
      qty: qty,
    },
  });

  // Trigger the order request
  const [response] = await Promise.all([alpacaBuyRequest]);
  const responseStatus = response.status;

  if (responseStatus !== 200) {
    throw new Error(
      `Unexpected response status ${responseStatus} ${response.Error}`
    );
  }

  // Wait for the order to fill before returning
  const response_data = await waitForOrderToFill(response.data.id);
  console.log(response_data); // e.g., filled_avg_price

  const filledAvgPrice = ethers.parseUnits(response_data.filled_avg_price, 6);
  const encodedData = ethers.AbiCoder.defaultAbiCoder().encode(
    ["string", "uint256"],
    [response_data.id, filledAvgPrice]
  );

  // Return encoded data
  return ethers.getBytes(encodedData);
}

Solution

  • The behavior of sending multiple API requests (3–4, varying depending on the network) is by design in Chainlink Functions. Each request sent to Chainlink Functions must reach consensus among the nodes in the Decentralized Oracle Network (DON). As a result, every node in the DON sends a separate request to the underlying API (e.g., Alpaca in your case).

    Why can't there be just one API call?

    Sending only one request would mean involving only a single node, which introduces reliance on a centralized node operator (providing the API). This would undermine the core principle of a trust-minimized decentralized environment.

    Answering your observation:

    This behavior does not occur when running the Chainlink Functions simulation locally. The simulation does not involve the targeted API being called via the DON. Instead, it is simply your computer or device making the call locally during the simulation.