Private transactions with Autotasks

The docs show it's possible to perform private transactions (flashbots) with a relayer

Is it possible to do this with autotasks, and if so what's the best way?

:computer: Environment
executing a transaction with Autotasks

:memo:Details

Doc examples (copy pasta below) show you can just use the signer as a regular ethers signer, however this doesn't take any of the DefenderTransactionRequest params such as

    isPrivate?: boolean;
  1. If I wish to make private, do i need to use signer.sendTransaction() instead?
  2. Does the "Send Private Transactions by default" option in the relayer affect transactions via Autotask too?
  3. The example from docs has a note to intentionally not wait for the tx.wait()
    1. At what point is the tx actually mined, and what is the flow of the monitoring/resubmitting?
    2. I need to pull a value off-chain once the tx has been mined, so in this case I need to wait. Is that correct?

:1234: Code to reproduce

// Entrypoint for the Autotask
exports.handler = async function(event) {
  // Load value provided in the webhook payload (not available in schedule or sentinel invocations)
  const { value } = event.request.body;

  // Compare it with a local secret
  if (value !== event.secrets.expectedValue) return;

  // Initialize defender relayer provider and signer
  const provider = new DefenderRelayProvider(event);
  const signer = new DefenderRelaySigner(event, provider, { speed: 'fast' });

  // Create contract instance from the signer and use it to send a tx
  const contract = new ethers.Contract(ADDRESS, ABI, signer);
  if (await contract.canExecute()) {
    const tx = await contract.execute();
    console.log(`Called execute in ${tx.hash}`);
    return { tx: tx.hash };
  }
}
1 Like

Hey @frontier, thanks for asking with enough details :smiley:

Indeed it’s possible to send transactions using the isPrivate key via our ethers implementation, you can take a look here. I’m wondering if there might be a Typescript issue we didn’t addressed when the client update with this was released. Please let us know if that’s the case.

Regarding your other questions:

  1. If I wish to make private, do i need to use signer.sendTransaction() instead?’

Both the ethers implementation or the sendTransaction endpoint from the relay client will accept the isPrivate flag to mark transactions as private

  1. Does the "Send Private Transactions by default" option in the relayer affect transactions via Autotask too?

Yeah, policies affect to your relayer as well. An Autotask is just a environment in which a function is executed, but this one is also sending transactions to the same infrastructure as the API or the UI would do.

  1. The example from docs has a note to intentionally not wait for the tx.wait()

Yeah, but this is because your Autotask will timeout otherwise, if the transaction is not mined in time. We generally recommend this for every transaction in an Autotask environment and it’s the same case for both private and public transactions.

  1. At what point is the tx actually mined, and what is the flow of the monitoring/resubmitting?

This is complex, but we run an internal infra for querying current gas prices on every supported network, and depending on the speed you set, we price according to market conditions and we check each few minutes (also depending on the speed) if the transaction was mined, otherwise we resubmit based on repricing rules we defined.

A transaction will be mined as soon as a node provider picks it. We also have a proxy that makes sure a transaction is broadcasted to every possible provider.

  1. I need to pull a value off-chain once the tx has been mined, so in this case I need to wait. Is that correct?

Yes, that’s correct, but I’d suggest to think in an async flow. For example, sending a transaction via Autotask webhook, then setting up a sentinel to listen for events emitted when the transaction is mined, and then wire your sentinel to another Autotask that pulls the value.

Hope this helps, best!

Thanks for the quick response @ernestognw

Perhaps there's a bit more nuance to the private transactions question. The example in the docs (within Autotask) just uses

  const signer = new DefenderRelaySigner(event, provider, { speed: 'fast' });
  const contract = new ethers.Contract(ADDRESS, ABI, signer);
  const tx = await contract.execute();
  return { tx: tx.hash };

And there's no option to provide the isPrivate flag when doing this that I can see (although raw ethers.Contract just takes any so hard to know exactly what params it takes)

So I think I need to use something like:

     const signer = new DefenderRelaySigner(event, provider, { speed: 'fast' });
     const tx = await signer.sendTransaction({
       ADDRESS, "0x0", "encoded fn...", gasLimit, speed: 'fast'
     });
     return { tx: tx.hash };

Also note, I'm successfully using typechain (with rollup) and that's working quite well. In that case, I think I can:

  const signer = new DefenderRelaySigner(event, provider, { speed: 'fast' });
  const contract = MyContract__factory.connect(ADDRESS, signer);
  const populatedTx = await contract.execute();
  const tx = await signer.sendTransaction({
    ...populatedTx,
    speed: 'fast',
    isPrivate: true,
  });
  return { tx: tx.hash };

Is there a cleaner way?

Is there a good way to check in goerli if the tx was indeed private when looking in etherscan?

I replied with a few more points/questions, but askimet bot filtered it :frowning:

1 Like

Another thing to check is when the Autotask was created and does it contain latest dependencies versions that are needed for sending private transaction.

Best way to test it is to create a new Autotask(it will include latest dependencies by default) and use above example.

Other way to check is to open specific Autotask settings page and check is Dependencies Layer set to version 'v2022-11-09'.

Best,
Zeljko

Sorry for the inconveniences @frontier, I removed the spam filter for your post.

Thanks for adding details. In fact, there’s no way currently of sending it the wait you’re proposing (with execute), however, have you tried using .execute({ isPrivate: true })? Usually ethers take override parameters after the last function argument. We adapted our defender-client signer and there’s a chance that the ethers will take the flag,

When trying, make sure you’re following the version guidelines @zeljko has shared previously.

If that doesn’t work, yes, the only way to send a transaction is the second example you share, by encoding the tx data and sending it through signer.sendTransaction.

I don’t think it’s difficult to add support if enough users request it.

Is there a good way to check in goerli if the tx was indeed private when looking in etherscan?

Yes, you can query the Flashbots Status API endpoint, but consider it will only work for mainnet transactions since they don’t have an API for goerli.

Etherscan also uses the same API to show the status, and you’ll see a “Private transactions via Flashbots” banner at the top of transaction info. But, unfortunately, for Goerli, there’s no way to see the status, the transaction just magically appears mined in etherscan because it was never on the mempool.

Hope this is clear enough.
Best!

Thanks for the tips - I got this working as below, and see the tx was private when executed in mainnet.

It would be even better if the types could be extended so we didn't need to use the underlying populatedTransaction and sendTransaction.

    async callMyFn(signer: DefenderRelaySigner): Promise<void> {
        const myContract: MyContract = MyContract__factory.connect(ADDR, signer);
        const populatedTx = await myContract.populateTransaction.myFn();
        const tx = await this.sendTransaction(signer, populatedTx);
        console.log(`Created Transaction: ${tx.hash}`);
    }

    sendTransaction(
        signer: DefenderRelaySigner, 
        populatedTx: ethers.PopulatedTransaction
    ): Promise<ethers.providers.TransactionResponse> {
        // Use the `sendTransaction()` call provided by Defender, such that
        // we can use isPrivate=true
        const defenderPopulatedTx: DefenderTransactionRequest = {
            ...populatedTx,
            isPrivate: true,
        };
        
        return this.signerOrProvider.sendTransaction(defenderPopulatedTx);
    }
    

Separately, the defender-autotask-utils.EthReceipt type also needs updating to include effectiveGasPrice and gasUsed

export interface TransactionReceipt extends EthReceipt {
    effectiveGasPrice: string,
    gasUsed: string,
};