1

tl;dr: If I execute a swap transaction between two tokens (native or ERC-20 on either side) that is subject to fees/slippage, is there a deterministic way to tell how much the user received from that swap after it has happened?

My app offers swaps from a few different sources. We check to see if Paraswap or 1inch support the trading pairs, and if not, we fall back to Uniswap v2. The issue is, whether from fees or slippage, we never know how much the user will actually receive from the swap until after it has happened.

Our current implementation checks the user's balance before and after the transaction to determine how much he received, but this could technically be thwarted by a race condition where the user's balance changes for unrelated reasons between the checks.

Is there a way to poll an RPC for balances on a particular block or ways to decode the result of a transaction? Something else that I'm not aware of?

2 Answers 2

1

Edited

You can check for events emitted during the swap. For instance take a look at this transaction here. A Transfer Event is emitted when a pair transfers tokens to an address. You can setup an event listener for the desired address and the interacting pair. You can find an example here on listening to contract events.

2
  • Sorry, perhaps I wasn't clear. I don't mean to predict the amount I will receive before validation. I would like to observe the result and determine how much I received after. I can hack this together using balance checks before and after, but that's not a good solution. May 4, 2023 at 17:20
  • I've updated the question title and body to try to clarify. May 4, 2023 at 17:21
1

With a little extra help from @Willbrand's answer, I managed to write what I needed via ethers. This example uses a few project-specific utils, but I think it's pretty readable and will hopefully help others who need to do the same:

import { BIGNUMBERS } from "common/constants/numbers";
import ERC20_ABI from "common/contracts/ERC20.json";
import { Address } from "common/types/Address";
import { TokenStub } from "common/types/TokenDetails";
import getProvider from "common/utils/getProvider";
import { BigNumber } from "ethers";
import { Interface } from "ethers/lib/utils";

export default async function getAmountReceivedFromSwap(
  token: TokenStub,
  transactionHash: string,
  recipient: Address
): Promise<BigNumber> {
  const provider = getProvider(token.chain);

  const receipt = await provider.getTransactionReceipt(transactionHash);

  const erc20Interface = new Interface(ERC20_ABI);

  let totalReceived = BIGNUMBERS.ZERO;

  for (const log of receipt.logs) {
    if (log.address.toLowerCase() === token.address.toLowerCase()) {
      const data = erc20Interface.parseLog(log);

      if (data.name === "Transfer") {
        // If the user is sending the token for some reason, subtract it from
        // the total.
        if (data.args[0].toLowerCase() === recipient.toLowerCase()) {
          totalReceived = totalReceived.sub(data.args.value);
        }

        // If the user receives the token, add it to the total.
        if (data.args[1].toLowerCase() === recipient.toLowerCase()) {
          totalReceived = totalReceived.add(data.args.value);
        }
      }
    }
  }

  return totalReceived;
}

See this Q&A for a more full explanation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.