Home > Back-end >  How to turn a list of transactions into an object of token quantities?
How to turn a list of transactions into an object of token quantities?

Time:08-19

So let's say I have a table of transaction data that is shaped like so:

{
  tokenAddress: string; // Address of token
  to: string; // Address of wallet receiving token
  from: string; // Address of wallet sending token
  quantity: number; // Number of tokens sent
}

I'd like to perform an aggregation that transforms this data like so

{
  tokenAddress: string; // Address of token
  walletAddress: string; // Each wallet has a row
  quantity: number; // Number of tokens in wallet
}

I am doing this currently by pulling the flat transaction data out and performing a pretty complex reduce in the application code.

export const getAddressesTokensTransferred = async (
  walletAddresses: string[]
) => {
  const collection = await getCollection('tokenTransfers');

  const result = await collection
    .find({
      $or: [
        { from: { $in: walletAddresses } },
        { to: { $in: walletAddresses } },
      ],
    })
    .toArray();

  return result.reduce((acc, { tokenAddress, quantity, to, from }) => {
    const useTo = walletAddresses.includes(to);
    const useFrom = walletAddresses.includes(from);
    let existingFound = false;
    for (const existing of acc) {
      if (existing.tokenAddress === tokenAddress) {
        if (useTo && existing.walletAddress === to) {
          existingFound = true;
          existing.quantity  = quantity;
          break;
        } else if (useFrom && existing.walletAddress === from) {
          existingFound = true;
          existing.quantity -= quantity;
          break;
        }
      }
    }

    if (!existingFound) {
      if (useTo) {
        acc.push({ tokenAddress, walletAddress: to, quantity });
      }
      if (useFrom) {
        acc.push({
          tokenAddress,
          walletAddress: from,
          quantity: quantity * -1,
        });
      }
    }

    return acc;
  }, [] as { tokenAddress: string; walletAddress: string; quantity: number }[]);
};

I feel like there MUST be a better way to do this within MongoDB, but I'm just not experienced enough with it to know how. Any help is greatly appreciated!


Edit - Adding some sample documents:

Input walletAddresses:

[
  '0x72caf7c477ccab3f95913b9d8cdf35a1caf25555', 
  '0x5b6e57baeb62c530cf369853e15ed25d0c82a866'
]

Result from initial find:

[
  {
    to: "0x123457baeb62c530cf369853e15ed25d0c82a866",
    from: "0x4321f7c477ccab3f95913b9d8cdf35a1caf25555",
    quantity: 5,
    tokenAddress: "0x12129ec85eebe10a9b01af64e89f9d76d22cea18",
  },
  {
    to: "0x123457baeb62c530cf369853e15ed25d0c82a866",
    from: "0x0000000000000000000000000000000000000000",
    quantity: 5,
    tokenAddress: "0x12129ec85eebe10a9b01af64e89f9d76d22cea18"
  },
  {
    to: "0x4321f7c477ccab3f95913b9d8cdf35a1caf25555",
    from: "0x0000000000000000000000000000000000000000",
    quantity: 5,
    tokenAddress: "0x12129ec85eebe10a9b01af64e89f9d76d22cea18"
  },
  {
    to: "0x4321f7c477ccab3f95913b9d8cdf35a1caf25555",
    from: "0x0000000000000000000000000000000000000000",
    quantity: 5,
    tokenAddress: "0x12129ec85eebe10a9b01af64e89f9d76d22cea18"
  }
]

This is a small sample with just two wallets (the other 0x000, and any others not in the walletAddresses array can be discarded essentially), and a single token (there would be many, we would want a row for each of them that have a transaction with the wallets)

The desired result would be

[
  {
    tokenAddress: '0x86ba9ec85eebe10a9b01af64e89f9d76d22cea18',
    walletAddress: '0x72caf7c477ccab3f95913b9d8cdf35a1caf25555',
    quantity: 5
  },
  {
    tokenAddress: '0x86ba9ec85eebe10a9b01af64e89f9d76d22cea18',
    walletAddress: '0x5b6e57baeb62c530cf369853e15ed25d0c82a866',
    quantity: 10
  }
]

CodePudding user response:

One option is to "duplicate" the transactions and keep them temporarily per walletAddress. This way we can group them by walletAddress:

db.collection.aggregate([
  {
    $project: {
      data: [
        {walletAddress: "$from",
          quantity: {$multiply: ["$quantity", -1]},
          tokenAddress: "$tokenAddress"},
        {walletAddress: "$to",
          quantity: "$quantity",
          tokenAddress: "$tokenAddress"}
      ]
    }
  },
  {$unwind: "$data"},
  {$group: {
      _id: "$data.walletAddress",
      quantity: {$sum: "$data.quantity"},
      tokenAddress: {$first: "$data.tokenAddress"}
  }},
  {$match: {
      _id: {$in: [
          "0x4321f7c477ccab3f95913b9d8cdf35a1caf25555",
          "0x123457baeb62c530cf369853e15ed25d0c82a866"
        ]
      }
  }}
])

See how it works on the playground example

  • Related