Sequential asynchronous calls

This will be a ReactJS question about how to make two or more API calls in sequence.

First post: calling Moralis.Web3.getAllERC20(). This seems to work well. It gets called incessantly. But it holds its state and only calls the API once, returning the same data repeatedly until the user logs out and isAuthenticated refreshes.

import { useEffect, useState } from "react";
import { useMoralis } from "react-moralis";

const emptyList = [];

export const usePositions = () => {
  const { isAuthenticated, Moralis } = useMoralis();
  const [positions, setPositions] = useState(emptyList);
  const [isLoading, setIsLoading] = useState(true);

  console.groupCollapsed("usePositions");
  console.log("Recieved isAuthenticated: ", isAuthenticated);

  useEffect(() => {
    let newRecord = {};
    let newSet = {};

    if (isAuthenticated) {
      // Bring back a list of all tokens the user has
      console.log("Calling getAllERC20()...");
      Moralis.Web3.getAllERC20({ usePost: true }).then((allPositions) => {
        console.log("All position data:", allPositions);
        newSet = allPositions.map((record) => {
          // Copy Moralis.io position object data.
          newRecord = record;
          // Calculate tokens from balance and decimals.
          newRecord.tokens = newRecord.balance / 10 ** newRecord.decimals;
          // Fix 'ether' -> 'ethereum'
          newRecord.name = newRecord.name.toLowerCase();
          newRecord.name =
            newRecord.name === "ether" ? "ethereum" : newRecord.name;
          return newRecord;
        });
        setPositions(newSet);
        setIsLoading(false);
      });
    } else {
      console.log("Unauthenticated.  Returning: ", emptyList);
      setPositions(emptyList);
    }
  }, [Moralis.Web3, isAuthenticated]);
  
  console.log("Returning positions: ", positions);
  console.log("Returning isLoading:", isLoading);
  console.groupEnd();

  return { positions, isLoading };
};

But then: if you give a user their positions, they’re going to ask for prices…

So I want to create another custom hook that takes the first custom hook’s portfolio object array and looks up prices from CoinGecko.com’s API. So I’m calling a hook from inside a hook. This…isn’t working for reasons I’m too ReactJS noob to understand. Near as I can tell the isLoading flag isn’t being processed correctly or even at all.

import { useEffect, useState } from "react";
import { usePositions } from "./usePositions";

const emptyList = [];
const geckoHead =
  "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=";
const geckoTail = "&order=market_cap_desc&per_page=100&page=1&sparkline=false";

export const usePrices = (props) => {
  const { positions, isLoading } = usePositions();
  const { waiting, setWaiting } = useState(true);
  const { portfolio, setPortfolio } = useState(emptyList);
  const { totalValue, setTotalValue } = useState(0);

  console.groupCollapsed("usePrice");
  console.log(
    isLoading ? "...waiting for Moralis load." : "Received position data: ",
    positions
  );

  useEffect(() => {
    if (!isLoading && portfolio) {
      setWaiting(true);
      let runningTotal = 0;
      let tempPortfolio = positions.map((position) => {
        const url = [geckoHead + position.name + geckoTail];
        fetch(url).then((marketData) => {
          position.image = marketData.image;
          position.price = marketData.current_price;
          position.value = position.tokens * position.price;
          runningTotal += position.value;
          position.valueString = [
            position.tokens +
              " @ " +
              position.price +
              "/" +
              position.symbol +
              "= $" +
              position.value,
          ];
          return position;
        });
        return position;
      });
      setPortfolio(tempPortfolio);
      setTotalValue(runningTotal);
      setWaiting(false);
    } else {
      setWaiting(true);
      setPortfolio(emptyList);
      setTotalValue(0);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [positions, isLoading]);

  console.log("Returning portfolio:", portfolio);
  console.log("Returning totalValue: $", totalValue);
  console.groupEnd();

  return { portfolio, waiting, totalValue };
};

And its firing nothing but blanks and errors:
image

So I’m wondering what’s the solution? useContext? Did I screw something stupid up in ReactJS? Thoughts?

1 Like

Hello @TheBubbleGuy

I am glad to see your progress in ReactJs. Keep up the enthusiasm! :muscle:

  1. I recommend you to combine two custom hooks into one.
  2. After receiving the positions you should extract them using something like:
let tokenSymbols= [];
positions.forEach((position) => {
  tokenSymbols.push(position.symbol);
});
  1. Take a look on Github code from Moralis Zerion Clone . To recive token price you should paste theirs coinGeckoIds instead of symbols. The function bellow will parse coinGecko token ids.
const ids = tokens
        .map((token) => coinGeckoList[token.symbol.toLowerCase()]?.id)
        .filter((id) => Boolean(id))
        .join(",");
      const url = `${coinGeckoApiUrl}?vs_currency=usd&ids=${ids}`;
      console.log("url:", url);
  1. Intead of using two hooks combine them using .then(()=> {}) . This article will help you with it.

I hope I helped you.
I will be glad to answer your questions if necessary. :slightly_smiling_face:

Well so much for separation of concerns. Figured ReactJS for cleaner than that. Alright…one moment…

1 Like

Everything will work out! We are always ready to help!

#BUIDL it! :smiley:

Alright…so when I combine concerns: my working Moralis call gets infected with whatever ails my CoinGecko call. The whole thing goes dumb and starts firing blanks.

import { useEffect, useState } from "react";
import { useMoralis } from "react-moralis";

const emptyList = [];
const geckoHead =
  "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=";
const geckoTail = "&order=market_cap_desc&per_page=100&page=1&sparkline=false";

export const usePositions = () => {
  const { isAuthenticated, Moralis } = useMoralis();
  const [positions, setPositions] = useState(emptyList);
  const [totalValue, setTotalValue] = useState(0);
  const [isLoading, setIsLoading] = useState(true);

  console.groupCollapsed("usePositions");
  console.log("Received isAuthenticated: ", isAuthenticated);

  useEffect(() => {
    let newRecord = {};
    let newSet = {};

    if (isAuthenticated) {
      // Bring back a list of all tokens the user has
      console.log("Calling getAllERC20()...");
      let runningTotal = 0;
      Moralis.Web3.getAllERC20({ usePost: true }).then((allPositions) => {
        console.log("All position data:", allPositions);
        newSet = allPositions.map((record) => {
          // Copy Moralis.io position object data.
          newRecord = record;
          // all symbols should be upper case.
          newRecord.symbol = newRecord.symbol.toUpperCase();
          // Calculate tokens from balance and decimals.
          newRecord.tokens = newRecord.balance / 10 ** newRecord.decimals;
          // Fix 'ether' -> 'ethereum'
          newRecord.name = newRecord.name.toLowerCase();
          newRecord.name =
            newRecord.name === "ether" ? "ethereum" : newRecord.name;
          // prep endpoint for API call:
          let url = [geckoHead + newRecord.name + geckoTail];
          // Call CoinGecko API:
          return fetch(url)
            .then((response) => response.json())
            .then((marketData) => {
              newRecord.image = marketData.image;
              newRecord.price = marketData.current_price;
              newRecord.value = newRecord.tokens * newRecord.price;
              runningTotal += newRecord.value;
              newRecord.valueString = [
                parseFloat(newRecord.tokens).toPrecision(3) +
                  " @ $" +
                  parseFloat(marketData.current_price).toFixed(2) +
                  "/" +
                  newRecord.symbol +
                  " = $" +
                  parseFloat(newRecord.value).toFixed(2),
              ];
            });
        });
        console.log("useEffect newSet:", newSet);
        console.log("useEffect runningTotal: $", runningTotal);
        setPositions(newSet);
        setTotalValue(runningTotal);
        setIsLoading(false);
      });
    } else {
      console.log("Unauthenticated.  Returning: ", emptyList);
      setPositions(emptyList);
      setIsLoading(true);
    }
  }, [Moralis.Web3, isAuthenticated]);

  console.log("Returning positions: ", positions);
  console.log("Returning isLoading:", isLoading);
  console.groupEnd();

  return { positions, isLoading, totalValue };
};

image

Not only that but the isLoading flag is bogus. I’m conditionally referencing the portfolio array switching on it in my TokenTable component. TokenTable shouldn’t even be trying to report a returned portfolio to console until there is one. Its running this component code:

import { usePositions } from "../../hooks/usePositions";

export const TokenTable = () => {
  const { portfolio, waiting, totalValue } = usePositions();

  console.groupCollapsed("TokenTable");
  console.log(
    waiting ? "Waiting for data." : "Received portfolio: ",
    portfolio
  );
  console.groupEnd();

  return (
    <VStack borderWidth={2} borderRadius={10} width="100%" padding={5}>
      {!waiting && <Text>Total Value: ${totalValue}</Text>}
      <Accordion allowToggle width="100%">
        {!waiting &&
          portfolio.map((position) => (
            <AccordionItem key={position.name} width="100%">
              <AccordionButton>
                <Flex

Thoughts?

Hey @TheBubbleGuy

I have optimized your hook code. I hope everything will work correctly now.

import { useEffect, useState } from "react";
import { useMoralis } from "react-moralis";
import coinGeckoList from "../data/coinGeckoTokenList.json";
const emptyList = [];
const geckoHead =
  "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=";
const geckoTail = "&order=market_cap_desc&per_page=100&page=1&sparkline=false";

export const usePositions = () => {
  const { isAuthenticated, Moralis } = useMoralis();
  const [positions, setPositions] = useState(emptyList);
  const [totalValue, setTotalValue] = useState(0);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    if (isAuthenticated) {
      // Bring back a list of all tokens the user has
      console.log("Calling getAllERC20()...");
      Moralis.Web3.getAllERC20().then((allPositions) => {
        console.log("All position data:", allPositions);
        const ids = allPositions
          .map((token) => coinGeckoList[token.symbol.toLowerCase()]?.id)
          .filter((id) => Boolean(id))
          .join(",");
        const url = `${geckoHead}?vs_currency=usd&ids=${ids}` + geckoTail;

        // Call CoinGecko API:
        fetch(url)
          .then((response) => response.json())
          .then((data) => {
            const marketData = {};
            data.forEach((d) => (marketData[d.symbol.toUpperCase()] = d));
            return marketData;
          })
          .then((data) => {
            let runningTotal = 0;
            const newList = allPositions.map((token) => {
              const output = { ...token };
              const tokenData = data[token.symbol.toUpperCase()];
              output.image = tokenData?.image;
              output.price = tokenData?.current_price;
              output.value = output.price ? token.balance * output.price : 0;
              runningTotal += output.value;
              //   output.valueString = [
              //     parseFloat(output.tokens).toPrecision(3) +
              //       " @ $" +
              //       parseFloat(tokenData?.current_price).toFixed(2) +
              //       "/" +
              //       output.symbol +
              //       " = $" +
              //       parseFloat(output.value).toFixed(2),
              //   ];
              return output;
            });
            setPositions(newList);
            setTotalValue(runningTotal);
          });

      });
    } else {
      console.log("Unauthenticated.  Returning: ", emptyList);
      setPositions(emptyList);
      setIsLoading(true);
    }
    setIsLoading(false);
  }, [Moralis.Web3, isAuthenticated]);

  console.log("Returning positions: ", positions);
  console.log("Returning isLoading:", isLoading);
  console.log("totalValue:", totalValue);

  return { positions, isLoading, totalValue };
};

1 Like

Please note that the tokens balances are in Wei. You should to convert them.

I didn’t check TokenTable function. I think it’ll work correctly now after repairing the hook.

I hope I helped you :slightly_smiling_face:

Well…I see you’ve pushed it towards the Zerion example code quite a bit.
But that demo’s control structures are quite different. The demo pulls the server call outside the useEffect() then triggers the CoinGecko API on token updates inside the useEffect() block…

As you’ve listed above doesn’t even fire the hook.
image

Another round of adjusting the control structure to be more like the Zerion demo hook code, essentially calling Moralis.Web3.getAllERC20() at component init and relying on useEffect’s dependency on tokens to re-render:

import { useEffect, useState } from "react";
import { useMoralis } from "react-moralis";
import coinGeckoList from "../data/coinGeckoTokenList.json";
const emptyList = [];
const geckoHead =
  "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=";
const geckoTail = "&order=market_cap_desc&per_page=100&page=1&sparkline=false";

export const usePositions = () => {
  const { Moralis } = useMoralis();
  const [totalValue, setTotalValue] = useState(0);
  const [isLoading, setIsLoading] = useState(true);
  const [positions, setPositions] = useState({});

  console.groupCollapsed("usePositions");
  console.log("calling Moralis.web3.getAllERC20()...");
  const tokens = Moralis.Web3.getAllERC20();

  useEffect(() => {
    console.log("Triggered on change to token data:", tokens);
    if (tokens?.length) {
      console.log("Augmenting with price...");
      // Bring back a list of all tokens the user has
      const ids = tokens
        .map((token) => coinGeckoList[token.symbol.toLowerCase()]?.id)
        .filter((id) => Boolean(id))
        .join(",");
      const url = `${geckoHead}${ids}` + geckoTail;
      console.log("fetching from: ", url);

      // Call CoinGecko API:
      fetch(url)
        .then((response) => response.json())
        .then((data) => {
          const marketData = {};
          data.forEach((d) => (marketData[d.symbol.toUpperCase()] = d));
          return marketData;
        })
        .then((data) => {
          let runningTotal = 0;
          const newList = tokens.map((token) => {
            const output = { ...token };
            const tokenData = data[token.symbol.toUpperCase()];
            output.price = tokenData?.current_price;
            output.image = tokenData?.image;
            output.value = output.price ? token.balance * output.price : 0;
            runningTotal += output.value;
            output.valueString = [
              parseFloat(output.tokens).toPrecision(3) +
                " @ $" +
                parseFloat(tokenData?.current_price).toFixed(2) +
                "/" +
                output.symbol.toUpperCase() +
                " = $" +
                parseFloat(output.value).toFixed(2),
            ];
            return output;
          });
          setPositions(newList);
          setTotalValue(runningTotal);
        });
    } else {
      console.log("Token list blank.  Skipping CoinGecko call. ");
      setPositions(emptyList);
      setTotalValue(0);
      setIsLoading(true);
    }
    setIsLoading(false);
  }, [tokens]);

  console.log("Returning positions: ", positions);
  console.log("Returning isLoading:", isLoading);
  console.log("totalValue:", totalValue);

  return { positions, isLoading, totalValue };
};

At least now it’s bothering to call the hook. Next steps are I need a .then() and .error() on the Moralis call to see what’s going on. Seems like it’s not returning.

image

So I toss that catch in there:

console.groupCollapsed("usePositions");
  console.log("calling Moralis.web3.getAllERC20()...");
  const tokens = Moralis.Web3.getAllERC20().then((response) => {
    console.log("Receved Moralis response: ", response);
  });

And I get some data coming back…but the useEffect() isn’t triggering and I get errors south of that in the TokenTable component complaining about the blank portfolio it’s still receiving from the hook. Because the hook returns the augmented portfolio array, not the raw tokens array from Moralis.

Hey @TheBubbleGuy

Thank you for your patience. Indeed, the hook worked a little wrong. I fixed it.

About debugging in the console. The console sometimes displays irrelevant information. Therefore, it is not always a good idea to use it especially for asynchronous functions. I also want to note that sometimes the use of the console.log() inside hooks can provoke its repeated call.

Here is the fixed hook:

import { useEffect, useState } from "react";
import { useMoralis } from "react-moralis";
import coinGeckoList from "../data/coinGeckoTokenList.json";
const emptyList = [];
const geckoHead =
  "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=";
const geckoTail = "&order=market_cap_desc&per_page=100&page=1&sparkline=false";

export const usePositions = () => {
  const { isAuthenticated, Moralis } = useMoralis();
  const [positions, setPositions] = useState(emptyList);
  const [totalValue, setTotalValue] = useState(0);
  const [isLoading, setIsLoading] = useState(true);
  const [allPositions, setAllPositions] = useState(emptyList);

  useEffect(() => {
    if (isAuthenticated) {
      // Bring back a list of all tokens the user has
      console.log("Calling getAllERC20()...");
      Moralis.Web3.getAllERC20({ chain: "bsc" }).then((allPositions) => {
        console.log("All position data:", allPositions);
        const ids = allPositions
          .map((token) => coinGeckoList[token.symbol.toLowerCase()]?.id)
          .filter((id) => Boolean(id))
          .join(",");
        const url = `${geckoHead}?vs_currency=usd&ids=${ids}` + geckoTail;
        console.log(url);
        // setAllPositions(allPositions);
        // Call CoinGecko API:
        fetch(url)
          .then((response) => response.json())
          .then((data) => {
            const marketData = {};
            data.forEach((d) => (marketData[d.symbol.toUpperCase()] = d));
            return marketData;
          })
          .then((data) => {
            let runningTotal = 0;
            const newList = allPositions.map((token) => {
              const output = { ...token };
              const tokenData = data[token.symbol.toUpperCase()];
              output.image = tokenData?.image;
              output.price = tokenData?.current_price;
              output.value = output.price ? token.balance * output.price : 0;
              runningTotal += output.value;
              //   output.valueString = [
              //     parseFloat(output.tokens).toPrecision(3) +
              //       " @ $" +
              //       parseFloat(tokenData?.current_price).toFixed(2) +
              //       "/" +
              //       output.symbol +
              //       " = $" +
              //       parseFloat(output.value).toFixed(2),
              //   ];
              return output;
            });
            setPositions(newList);
            setTotalValue(runningTotal);
            setIsLoading(false);
            // console.log("Returning positions: ", positions);
            // console.log("Returning isLoading:", isLoading);
            // console.log("totalValue:", totalValue);
          });
      });
    } else {
      // console.log("Unauthenticated.  Returning: ", emptyList);
      setPositions(emptyList);
      setIsLoading(true);
    }
  }, [Moralis.Web3, isAuthenticated]);

  return { positions, isLoading, totalValue };
};

Here is analog of your Table. For me it works correctly. Try it and give me feedback.

const { positions, isLoading, totalValue } = usePositions();

return (
    <div>
      <ul>
        {!isLoading &&
          positions.map((position) => (
            <li key={position.name}>
              {position.price}
            </li>
          ))}
      </ul>
    </div>
  );

I hope this time we did it :grinning:

Ok. After a 20 minute rant about “observability”, in the control systems engineering sense of the word, I have this question: “Is there a way to generate debug output from a hook without triggering a re-render?”

Anyway after some fiddling around and cleanup my hook code looks like this:

import { useEffect, useState } from "react";
import { useMoralis } from "react-moralis";
import coinGeckoList from "../data/coinGeckoTokenList.json";
const emptyList = [];
const geckoHead =
  "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=";
const geckoTail = "&order=market_cap_desc&per_page=100&page=1&sparkline=false";

export const usePositions = () => {
  const { isAuthenticated, Moralis } = useMoralis();
  const [positions, setPositions] = useState(emptyList);
  const [totalValue, setTotalValue] = useState(0);
  const [isLoading, setIsLoading] = useState(true);
  // const [allPositions, setAllPositions] = useState(emptyList);

  useEffect(() => {
    if (isAuthenticated) {
      // Bring back a list of all tokens the user has
      console.log("Calling getAllERC20()...");
      Moralis.Web3.getAllERC20().then((allPositions) => {
        console.log("All position data:", allPositions);
        const ids = allPositions
          .map((token) => coinGeckoList[token.symbol.toLowerCase()]?.id)
          .filter((id) => Boolean(id))
          .join(",");
        const url = `${geckoHead}?vs_currency=usd&ids=${ids}` + geckoTail;
        console.log(url);
        // Call CoinGecko API:
        fetch(url)
          .then((response) => response.json())
          .then((data) => {
            // Convert to a 'dictionary' array of objects.
            const marketData = {};
            data.forEach((d) => (marketData[d.symbol.toUpperCase()] = d));
            return marketData;
          })
          .then((data) => {
            let runningTotal = 0;
            const newList = allPositions.map((token) => {
              // Merge position data with market data and augment.
              const output = { ...token };
              const tokenData = data[token.symbol.toUpperCase()];
              output.image = tokenData?.image;
              output.price = tokenData?.current_price;
              output.tokens = token.balance
                ? token.balance / 10 ** token.decimals
                : 0;
              output.value = output.price ? output.tokens * output.price : 0;
              runningTotal += output.value;
              output.valueString = [
                parseFloat(output?.tokens).toPrecision(3) +
                  " @ $" +
                  parseFloat(tokenData?.current_price).toFixed(2) +
                  "/" +
                  output?.symbol.toUpperCase() +
                  " = $" +
                  parseFloat(output?.value).toFixed(2),
              ];
              return output;
            });
            // Done.  Report back to states.
            setPositions(newList);
            setTotalValue(runningTotal);
            setIsLoading(false);
          });
      });
    } else {
      // No authentication.  Report blanks.
      setPositions(emptyList);
      setIsLoading(true);
    }
  }, [Moralis.Web3, isAuthenticated]);

  return { positions, isLoading, totalValue };
};

And my component code looks like this:

import { Avatar, Flex, Text, VStack } from "@chakra-ui/react";
import {
  Accordion,
  AccordionItem,
  AccordionButton,
  AccordionPanel,
  AccordionIcon,
} from "@chakra-ui/react";
import { usePositions } from "../../hooks/usePositions";

export const TokenTable = () => {
  const { positions, isLoading, totalValue } = usePositions();

  console.groupCollapsed("TokenTable");
  console.log(!isLoading && positions);

  return (
    <VStack borderWidth={2} borderRadius={10} width="100%" padding={5}>
      {!isLoading && <Text>Total Value: ${totalValue}</Text>}
      <Accordion allowToggle width="100%">
        {!isLoading &&
          positions.map((position) => (
            <AccordionItem key={position.name} width="100%">
              <AccordionButton>
                <Flex
                  width="100%"
                  alignItems="left"
                  justifyContent="space-between"
                >
                  <Avatar
                    name={position.symbol}
                    src={position.image}
                    size="sm"
                  />
                  <Text ml={2}>{position.name}</Text>
                  <Text ml={2}>{position.valueString}</Text>
                </Flex>
                <AccordionIcon />
              </AccordionButton>
              <AccordionPanel pb={4}>
                <Text>Transaction list should go here.</Text>
              </AccordionPanel>
            </AccordionItem>
          ))}
      </Accordion>
    </VStack>
  );
};

And together they run like this:

And this:
image

So in the hierarchy of programming needs:

  • Does it compile? Yes.
  • Does it run? Yes.
  • Does it retrieve the data it is supposed to retrieve? Yes.
  • Does it process the data as we expect? Yes.
  • Does it return the right data? Yes.
  • Does it display the right data? Yes.
  • Is the data numerically correct? No.

Almost there. As you can see it’s returning some bogus token for Uniswap. I think it’s a problem with coinGeckoTokenList.json, but I haven’t tracked it down yet. There are multiple tokens with the same symbol in the table. How do we sort out which the user really has? Can I get contract addresses returned from getAllERC20()? Keying off addresses would solve the problem, but then we’d need a table with addresses to key on.

The other way to “fix” it would be to combine token.name and token.symbol and key off THAT. But that’s not robust against scam copy tokens.

image

Thoughts?

Solution of inccorect token symbols:

  1. Update your coinGeckoTokenList.json on CoinGecko API V3 by way as below

  1. You will need to restructure the token list as it was before using the function:
let map = {};
coinGeckoTokenList.forEach((element) => {
  map[element.platforms.CHAIN_YOU_WANT] = element;
});

console.log(map); 

OR

let map = {};
coinGeckoTokenList.forEach((element) => {
  map[element.name] = element;
});

console.log(map); 
  1. Now you need to parse not through the symbol, but through the token address or name.

As I said it problems with console.log() happen “sometimes”, but honestly seldom. Usually this problem occurs when you are trying to call console.log with a value that hasn’t been fetched yet. But in your particular case, this should not happen. And if it does, you will immediately notice it :wink:

Ok, nothing is that simple. Let’s speak in complete solutions.

  1. Download the list from the CoinGecko API test site: check.
  2. Restructure the token list “as it was before using the function”. Ok, but modifying files switches us from the browser to running a NodeJS script more like:
const fs = require("fs");
let newmap = {};

console.log("Loading data...");
fs.readFile("./rawNewList.json", "utf8", (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log("Parsing data...");
  data = JSON.parse(data);

  console.log("Refactoring Data...");
  data.forEach((element) => {
    let index = element.name.toLowerCase();
    newmap[index] = element;
    newmap[index].address = element.platforms.ethereum;
    newmap[index].symbol = element.symbol.toUpperCase();
    newmap[index].name = element.name.toLowerCase();
  });

  console.log("Serializing new data...");
  const filedata = JSON.stringify(newmap);
  fs.writeFile("./coinGeckoTokenList.json", filedata, (err) => {
    if (err) {
      console.error(err);
      return;
    }
    console.log("File written successfully.");
  });
});

It occurs to me that using contract address as the index is a many-to-one relation if we’re prepping for multi-chain operations. So we’re stuck with a bit of a vulnerability. Even so using the name instead of symbol is a big improvement. So the following tweak to the hook merging code to switch

     const ids = allPositions
          .map((token) => coinGeckoList[token.symbol.toUpperCase()]?.id)
          .filter((id) => Boolean(id))
          .join(",");

to

     const ids = allPositions
          .map((token) => coinGeckoList[token.name.toLowerCase()]?.id)
          .filter((id) => Boolean(id))
          .join(",");

…gets that job done.

But that leaves us with a little matter of “Ether” -> “ethereum”. So I bare-knuckled it like so:

.map((token) =>
            token.name.toLowerCase() === "ether"
              ? coinGeckoList["ethereum"].id
              : coinGeckoList[token.name.toLowerCase()]?.id
          )

Typical data cleaning. I’m open to cleaner ideas.

Anywho, NOW I’m cooking with gas:
image

And in the spirit of this thread, now that we’ve got 2 API calls running in sequence…
Its time to start asking about how do you run…THREE…
image

So now I need to retrieve all the current user’s transactions in another hook, and filter them by token…OR…should I just chain that into the current hook?

Thoughts?

Hey @TheBubbleGuy, awesome job :muscle:

In my opinion, using the full name is the best idea for filtering tokens in this case.

This time you need to make two different hooks.

1 Like