How to decode the return values after executing forwarder.execute() in Defender?

In OpenZeppelin Defender, how can I retrieve the return values from the target business contract?

For example, in my registry.sol, I have added the getAllData() function, and in relay.js, I can see:

const res = await forwarder.execute(request, signature, { gasLimit });

How can I obtain the list of data returned by the getAllData() function?

In relay.js, the result returned after executing the forwarder.execute() function is as follows; is the result of the business contract call contained in the data field? How can it be decoded?

  gasPrice: BigNumber { _hex: '0x05f65e06', _isBigNumber: true },
  chainId: 11155111,
  hash: '0x7172f65ab5b0d6d2ccded5745f4d686ae4323e3c11971bcdb9067919f6e1138d',
  transactionId: '8d7036e6-4214-454a-96c9-a89cfdbd1288',
  value: BigNumber { _hex: '0x00', _isBigNumber: true },
  gasLimit: BigNumber { _hex: '0x100590', _isBigNumber: true },
  to: '0x170EbAFb7c0b4F88309C5067BB6dd64B8Dc77bDb',
  from: '0xb676ad369e95d86435dacd64097e6586b42ed015',
  data: '0x47153f820000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000014000000000000000000000000040d4cc1a0dcb9bfb0b717a1d822ae5cbeae316b6000000000000000000000000728fabce41646c80abcdab3eb361d014363ff135000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000042d10fa28000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041553c5f5d3d434c25986297a9af223ad7331635c59de1d21bb28edda92978ed2012bf84e7dfb8042468ee6bdc2a178b53d7bbd0d5f67b0ec72d16cec01aa283291b00000000000000000000000000000000000000000000000000000000000000',
  nonce: 35,
  status: 'sent',
  speed: 'fast',
  validUntil: '2023-12-04T10:44:11.302Z',
  createdAt: '2023-12-04T02:44:11.929Z',
  sentAt: '2023-12-04T02:44:11.929Z',
  pricedAt: '2023-12-04T02:44:11.929Z',
  isPrivate: false,
  wait: [Function (anonymous)]

Attached: The content of the registry.sol contract is as follows:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import "@openzeppelin/contracts/metatx/MinimalForwarder.sol";

contract Registry is ERC2771Context {  
  event Registered(address indexed who, string name);

  mapping(address => string) public names;
  mapping(string => address) public owners;

  constructor(MinimalForwarder forwarder) // Initialize trusted forwarder
    ERC2771Context(address(forwarder)) {

  function register(string memory name) external {
    require(owners[name] == address(0), "Name taken");
    address owner = _msgSender(); // Changed from msg.sender
    owners[name] = owner;
    names[owner] = name;
    emit Registered(owner, name);
    string[] public users;

    function addString(string memory newValue) external {

    function getArrayLength() external view returns (uint256) {
        return users.length;

function getAllData() external view returns (string[] memory) {
    return users;


Hi! Not sure if I understood your issue. I think what you need is to instantiate Registry contract and call getAllData() funtion. But that seems out of the scope of Defender.


I had to do some digging into the very little information that you provided, but here it is...

First of all, what network does all of this take place on?

The chainId: 11155111 attribute in your transaction's receipt answers this question.

After googling it, the network turns out to be Ethereum sepolia testnet.

Then, I took the data attribute, and I split it into the function-selector (first 4 bytes) followed by sections of 64 bytes each:


The only information which seems immediately meaningful is in the following two sections:


Each of these sections potentially holds an address (the first 24 bytes are all zero, and the remaining 40 bytes are potentially an address).

Address 0x40d4cc1a0dcb9bfb0b717a1d822ae5cbeae316b6 is an externally-owned account.

Address 0x728fabce41646c80abcdab3eb361d014363ff135 is a smart-contract account.

The only transaction on the smart contract is its creation, executed by the externally-owned account.

This contract is not verified (its source code is not visible), so it is hard to say anything further.

Regardless of all of that, perhaps the remaining sections in the data field which I have listed above can shed some more light here, but you'll need to provide more context, such as the relevant part in your contract's code, and specifically that of function getAllData...

1 Like

Hi @barakman, I still don't see how this is related to Defender, I would suggest to ask this question to contracts team.


Dude, I'm not the one who asked this question, I just responded to it with a bit of insight which I've been able to dig out of the given information.

1 Like

@barakman Apologies, I thought you were the author, thanks so much for the answer :slight_smile:

1 Like