Edgar's blog

29 Mar 2021

Pending transactions, ABI decoding uniswap in golang

This is really a blog post adding more description and explanation of my uniswap-tui repo and I go a bit fast.

Getting pending transactions in golang

ethclient has a TODO for pending transaction subscriptions but we can do some cool golang hacks to grab the right subscription anyway.

client, _ := ethclient.Dial(client_dial)
v := reflect.ValueOf(client).Elem()
f := v.FieldByName("c")
rf := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem()
concrete_client, _ := rf.Interface().(*rpc.Client)
something := make(chan common.Hash)
concrete_client.EthSubscribe(
	context.Background(), something, "newPendingTransactions",
)

for hsh := range something {
  txn, is_pending, _ := client.TransactionByHash(context.Background(), hsh)
}

…And now you have the txn and whether its pending (hint: use that parameter)

note this way might break at any moment, aka upstream changes "c"

Decoding ABI methods

Okay - so now we have a pending transaction - but what we usually want are parameters!

Let’s look at pending swaps on uniswap v2


var (
  UNISWAP_ROUTER = common.HexToAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D")
  swapExactETHForTokens           = [4]byte{0x7f, 0xf3, 0x6a, 0xb5}
  // the plain ABI - I just have mine compiled but you grab it from etherscan
  router_abi, _                   = abi.JSON(strings.NewReader(router.RouterABI))
  method_swapExactETHForTokens, _ = router_abi.MethodById(swapExactETHForTokens[:])
)

	type t struct {
		AmountOutMin *big.Int
		Path         []common.Address
		Deadline     *big.Int
		To           common.Address
	}

			if is_pending {
				_, _ = signer.Sender(txn)

				if data := txn.Data(); data != nil {

					to := txn.To()

					if to != nil {

						bytecode, _ := client.CodeAt(
							context.Background(), *to, nil,
						)

						isContract := len(bytecode) > 0
						if isContract {

							if *to == UNISWAP_ROUTER {

								if len(data) < 4 {
									continue
								}

								buf := [4]byte{}
								copy(buf[:], data[:4])

								switch buf {
								case swapExactETHForTokens:
									var something t

									if err := method_swapExactETHForTokens.Inputs.Unpack(
										&something, data[4:],
									); err != nil {
										log.Fatal(err)
									}

cool - so now you have the args that a competitor is using for a swap - happy front running