gasLimit too low using Opensea Plugin

Hey @tungtien

Regarding the errors:

  • at gas:22000 , it show error: transaction reverted: out of gas. This is because the gas you manually set was not enough to run the transaction.

  • Error: {code: -32602, message: ‘Invalid parameters: must provide an Ethereum address.’} This comes from your web3 instance. You are providing address x but the transaction is signed with address y.
    This is not related with the plugin itself.

Adding a custom gas seems to work as the gas param is correctly set.
Another solution might be not to run the transaction automatically but instead return and manually use the triggers from the plugin (something that might be discussed in the future).

How do I sign the transaction with the same x ?
Will this works if I call it from cloud function?

I suggest you to disable automatic triggers inspect / run them yourself.
In order to disabled the automatic execution of a plugin trigger you need to set the option object when you call the endpoint.
For example:

const createBuyOrder = async () => {
  try {
    const options = { disableTriggers: true }; // Option object
    const result = await Moralis.Plugins.opensea.createBuyOrder(
      {
        network: 'testnet',
        tokenAddress: '0xdf524d78dc60e185ba674eb64d04a8a45d228fba',
        tokenId: '421',
        amount: 0.0005,
        userAddress: web3Account.address,
        tokenType: 'ERC1155',
        paymentTokenAddress: '0xc778417e063141139fce010982780140aa0cd5ab',
        gas: 50000,
      },
      options // Option is passed as 2nd parameter when your call a plugin endpoint
    );
    console.log('Got parameters to create a new buy order::\n', result);
  } catch (error) {
    new Error(error.message || error);
  }
};

If you check the console, you will see that the result from the plugin is logged.

This is what the plugin sends back to the Moralis SDK.

Check the trigger array at position 0.

You see a web3Transaction trigger.

Now you can take code from the SDK and, after changing few thing, you can use your own account (instead of metamask) to run the triggers.

Moralis SDK: https://github.com/MoralisWeb3/Moralis-JS-SDK

I have done for you the web3Transaction trigger. You can play with the SDK to find out how to run new triggers such as web3Sign.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
    <script src="https://unpkg.com/moralis/dist/moralis.js"></script>
  </head>
  <body>
    <script>
      const MORALIS_APP_ID = '';
      const MORALIS_SERVER_URL = '';
      const PK = '';
      const MORALIS_SPEEDY_NODE = '';

      // Connect to Moralis server
      const web3 = new Web3(MORALIS_SPEEDY_NODE);
      let web3Account;

      const init = async () => {
        await Moralis.start({ serverUrl: MORALIS_SERVER_URL, appId: MORALIS_APP_ID });
        await Moralis.enable();
        await Moralis.initPlugins();
        web3Account = await web3.eth.accounts.wallet.add(PK);
        createBuyOrder();
      };

      const createBuyOrder = async () => {
        try {
          const options = {
            disableTriggers: true,
          };
          const [user] = await web3.eth.getAccounts();
          const result = await Moralis.Plugins.opensea.createBuyOrder(
            {
              network: 'testnet',
              tokenAddress: '0xdf524d78dc60e185ba674eb64d04a8a45d228fba',
              tokenId: '421',
              amount: 0.0005,
              userAddress: web3Account.address,
              tokenType: 'ERC1155',
              paymentTokenAddress: '0xc778417e063141139fce010982780140aa0cd5ab',
              gas: 50000,
            },
            options
          );
          console.log('Got parameters to create a new buy order::\n', result);

          handleTriggers(result.triggers, result);
        } catch (error) {
          new Error(error.message || error);
        }
      };

      const ERROR_WEB3_MISSING =
        'Missing web3 instance, make sure to call Moralis.enableWeb3() or Moralis.authenticate()';

      const handleTriggers = async (triggersArray, payload) => {
        function ensureWeb3IsInstalled() {
          return web3 ? true : false;
        }
        if (!triggersArray) return;
        let response;
        for (let i = 0; i < triggersArray.length; i++) {
          switch (triggersArray[i]?.name) {
            case 'web3Transaction':
              if (!ensureWeb3IsInstalled()) throw new Error(ERROR_WEB3_MISSING);

              // Trigger a web3 transaction (await)
              if (triggersArray[i]?.shouldAwait === true)
                response = await web3.eth.sendTransaction(triggersArray[i]?.data);

              // Trigger a web3 transaction (does NOT await)
              if (triggersArray[i]?.shouldAwait === false) response = web3.eth.sendTransaction(triggersArray[i]?.data);

              // Return payload and response
              if (triggersArray[i]?.shouldReturnPayload === true) return { payload: payload, response: response };

              // Only return response
              if (triggersArray[i]?.shouldReturnResponse === true) return response;
              break;
            default:
              throw new Error(`Unknown trigger: "${triggersArray[i]?.name}"`);
          }
        }
      };
      init();
    </script>
  </body>
</html>

thanks @dani, this is big for me… I will study it and keep you informed on result…

1 Like

I am a little confused on this: don’t know from where to get this Web3 ?
can I just use:

web3 = await Moralis.Web3.enable();

because as I see during installing Opensea plugin we provided 2 Speedynodes url for both mainnet and testnet; and we appointed ‘testnet’ in the parameters:

Moralis.Plugins.opensea.createBuyOrder(
        {
          network: "testnet",...

another question:
inside the SDK: MoralisWeb3.js I see there is a line:

import { run } from './Cloud';

How do I import this {run} function into my code? I mean, is there a better way than just copy paste all related files from SDK into my folders?

Web3 should be already available in your browser from using a line like this in your html header:
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>

that run function can be accessible with Moralis.Cloud.run, and it will run a cloud function on your Moralis server

1 Like

Web3 should be already available in your browser from using a line like this in your html header:
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>

I am using React Moralis Boiler plate, so the Web3 is not yet available as global Class. So I ve just added: (hope it works?)
import Web3 from 'web3'

that run function can be accessible with Moralis.Cloud.run, and it will run a cloud function on your Moralis server

I dont think this run function (from MoralisSDK/scr/cloud.js) is the one we are calling by Moralis.Cloud.run:


export function run(name: string, data: mixed, options: RequestOptions): Promise<mixed> {
  options = options || {};

  if (typeof name !== 'string' || name.length === 0) {
    throw new TypeError('Cloud function name must be a string.');
  }

  const requestOptions = {};
  if (options.useMasterKey) {
    requestOptions.useMasterKey = options.useMasterKey;
  }
  if (options.sessionToken) {
    requestOptions.sessionToken = options.sessionToken;
  }
  if (options.context && typeof options.context === 'object') {
    requestOptions.context = options.context;
  }

Why do you need the run function?

To handle “callPluginEndpoint”, inside this fie, line 300 and 316 I found the function ‘run’

and I am trying to handle these triggers:

triggers: [
    {
      name: "web3Sign",
      message:
        "0xb5d36fbad51ea35a6b504fc94df0165a954d5d0ef7738c38369a1e4410c85d16",
      signer: "0x255b7dabbbea98dd315f8f6e09b3f2c5871ddf75",
      shouldAwait: true,
      saveResponse: true,
    },
    {
      name: "callPluginEndpoint",
      pluginName: "opensea",
      endpoint: "postOrder",
      params: {
        message:
          "0xb5d36fbad51ea35a6b504fc94df0165a954d5d0ef7738c38369a1e4410c85d16",
        orderSide: 0,
      },
      useSavedResponse: true,
      savedResponseAs: "signature",
      savedResponseAt: [],
      shouldAwait: true,
      runResponseTrigger: true,
    },
  ],

Consider this:

 {
      name: "callPluginEndpoint",
      pluginName: "opensea",
      endpoint: "postOrder",
      params: {
        message:
          "0xb5d36fbad51ea35a6b504fc94df0165a954d5d0ef7738c38369a1e4410c85d16",
        orderSide: 0,
      },
      useSavedResponse: true,
      savedResponseAs: "signature",
      savedResponseAt: [],
      shouldAwait: true,
      runResponseTrigger: true,
    },

You can call an endpoint in two ways:

  • Moralis.Plugins.opensea.postOrder()
  • POST req to https://coefdbgftezm.moralishost.com:2053/server/functions/opensea_createBuyOrder
    Replace: https://coefdbgftezm.moralishost.com:2053 with your server url

How to use the postOrder() ? I dont have docs for that. I dont see that function on this page:

will this works? :slight_smile:
await Moralis.Plugins.opensea.postOrder(params)

hi @dani,

I came up with this code:

const MORALIS_SPEEDY_NODE =
  "https://speedy-nodes-nyc.moralis.io/535b0f87fd7f16e6e5e013bb/eth/rinkeby";

// Connect to Moralis server
let web3 = new Web3(MORALIS_SPEEDY_NODE);
let web3Account;

const init = async () => {
  web3Account = await web3.eth.accounts.wallet.add(_testBot("rinkeby"));
  await createBuyOrder();
};

const ERROR_WEB3_MISSING =
  "Missing web3 instance, make sure to call Moralis.enableWeb3() or Moralis.authenticate()";

const handleTriggers = async (triggersArray, payload) => {
  function ensureWeb3IsInstalled() {
    return web3 ? true : false;
  }
  if (!triggersArray) return;
  let response;
  for (let i = 0; i < triggersArray.length; i++) {
    switch (triggersArray[i]?.name) {
    
      // Handles `web3Sign` trigger
      case "web3Sign":
        if (!ensureWeb3IsInstalled()) throw new Error(ERROR_WEB3_MISSING);
        if (!triggersArray[i].message)
          throw new Error("web3Sign trigger does not have a message to sign");
        if (
          !triggersArray[i].signer ||
          !web3.utils.isAddress(triggersArray[i].signer)
        )
          throw new Error("web3Sign trigger signer address missing or invalid");

        // Sign a message using web3 (await)
        if (triggersArray[i]?.shouldAwait === true)
          response = await web3.eth.personal.sign(
            triggersArray[i].message,
            triggersArray[i].signer
          );

        // Sign a message using web3 (does NOT await)
        if (triggersArray[i]?.shouldAwait === false)
          response = web3.eth.personal.sign(
            triggersArray[i].message,
            triggersArray[i].signer
          );

        // Save response
        // if (triggersArray[i]?.saveResponse === true)
        //   memoryCard.save(response);

        // Return payload and response
        if (triggersArray[i]?.shouldReturnPayload === true)
          return { payload: payload, response: response };

        // Only return response
        if (triggersArray[i]?.shouldReturnResponse === true) return response;
        break;

      // Calls a given plugin endpoint
      case "callPluginEndpoint":
        if (!triggersArray[i].pluginName)
          throw new Error(
            "callPluginEndpoint trigger does not have an plugin name to call"
          );
        if (!triggersArray[i].endpoint)
          throw new Error(
            "callPluginEndpoint trigger does not have an endpoint to call"
          );

        // Call a plugin endpoint (await)
        if (triggersArray[i]?.shouldAwait === true) {

          let response = await Moralis.Plugins.opensea.postOrder(
            triggersArray[i].params
          );
          return console.log(
            " Moralis.Plugins.opensea.postOrder => response = ",
            JSON.stringify(response)
          );
        
        } else {
          ___.stopOnError("unhandled option..");
        }
    }
  }
};

const createBuyOrder = async () => {
  console.log("Placing offers from:", user.get("ethAddress"));
  const { tokenAddress, amount, fromId, toId, duration } = tx;
  const expirationTime = Math.round(Date.now() / 1000 + duration * 60 * 60);

  let triggerParams;
  try {
    const options = {
      disableTriggers: true,
    };
    triggerParams = await Moralis.Plugins.opensea.createBuyOrder(
      {
        network: "testnet",
        tokenAddress: tokenAddress, //0xCae0b79679dC38E045525077Aa949aacBCB17791
        tokenId: "11",
        tokenType: "ERC721",
        amount: amount,
        userAddress: web3Account.address, //user.get("ethAddress"), //"0x255b7DaBbBEa98dD315f8f6E09B3F2c5871dDf75",
        paymentTokenAddress: "0xc778417e063141139fce010982780140aa0cd5ab",
        expirationTime: expirationTime,
        gas: 50000,
      },
      options
    );
    console.log(
      "Got parameters to create a new buy order::\n",
      JSON.stringify(triggerParams)
    );
  } catch (er) {
    console.log("Error:", er);
  }
  await handleTriggers(triggerParams.triggers, triggerParams);
};

however it shows the error:

the method personal_sign does not exist/is not available

triggerParams logged out is like this:

triggerParams = {
  data: {
    sign: {
      data: {
        exchange: "0x5206e78b21ce315ce284fb24cf05e0585a93b1d9",
        maker: "0x255b7dabbbea98dd315f8f6e09b3f2c5871ddf75",
        taker: "0x0000000000000000000000000000000000000000",
        quantity: "1",
        makerRelayerFee: "0",
        takerRelayerFee: "250",
        makerProtocolFee: "0",
        takerProtocolFee: "0",
        makerReferrerFee: "0",
        waitingForBestCounterOrder: "false",
        feeMethod: "1",
        feeRecipient: "0x5b3256965e7c3cf26e11fcaf296dfc8807c01073",
        side: "0",
        saleKind: "0",
        target: "0xcae0b79679dc38e045525077aa949aacbcb17791",
        howToCall: "0",
        calldata:
          "0x23b872dd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000255b7dabbbea98dd315f8f6e09b3f2c5871ddf75000000000000000000000000000000000000000000000000000000000000000b",
        replacementPattern:
          "0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        staticTarget: "0x0000000000000000000000000000000000000000",
        staticExtradata: "0x",
        paymentToken: "0xc778417e063141139fce010982780140aa0cd5ab",
        basePrice: "111000000000000000",
        extra: "0",
        listingTime: "1635910730",
        expirationTime: "0",
        salt: "62835573254372216507758797343392650739275880710809955748296098887027741353954",
        hash: "0xb5d36fbad51ea35a6b504fc94df0165a954d5d0ef7738c38369a1e4410c85d16",
        assetId: "11",
        assetAddress: "0xcae0b79679dc38e045525077aa949aacbcb17791",
        tokenType: "ERC721",
      },
    },
  },
  triggers: [
    {
      name: "web3Sign",
      message:
        "0xb5d36fbad51ea35a6b504fc94df0165a954d5d0ef7738c38369a1e4410c85d16",
      signer: "0x255b7dabbbea98dd315f8f6e09b3f2c5871ddf75",
      shouldAwait: true,
      saveResponse: true,
    },
    {
      name: "callPluginEndpoint",
      pluginName: "opensea",
      endpoint: "postOrder",
      params: {
        message:
          "0xb5d36fbad51ea35a6b504fc94df0165a954d5d0ef7738c38369a1e4410c85d16",
        orderSide: 0,
      },
      useSavedResponse: true,
      savedResponseAs: "signature",
      savedResponseAt: [],
      shouldAwait: true,
      runResponseTrigger: true,
    },
  ],
};
{
      name: "web3Sign",
      message:
        "0xb5d36fbad51ea35a6b504fc94df0165a954d5d0ef7738c38369a1e4410c85d16",
      signer: "0x255b7dabbbea98dd315f8f6e09b3f2c5871ddf75",
      shouldAwait: true,
      saveResponse: true,
}

The trigger above requires to sign a message with your user.
Then you should post this object (including the signature param) to the postOrder endpoint.

I found that error thrown from here:

// Sign a message using web3 (await)

          if (triggersArray[i]?.shouldAwait === true) {

            response = await web3.eth.personal.sign(

              triggersArray[i].message,

              web3Account.address,  //Returned error: the method personal_sign does not exist/is not available

            );

          }

I also tried providing private as instructed in web3 docs, but it dont works either…

// Sign a message using web3 (await)

          if (triggersArray[i]?.shouldAwait === true) {

            response = await web3.eth.personal.sign(

              triggersArray[i].message,

              web3Account.address,  
              
             web3Account.privateKey, 
//Returned error: the method personal_sign does not exist/is not available
            );

          }

tried searching around the net but no use :sob:

You can sign using web3 or ether, check their docs for examples :slight_smile:

I see this in the docs, but don’t know what I did I missed? or maybe there is problem with my web3 instance provided by Speedynode?

Signing a message with personal.sign is not the same thing as signing a transaction, the seedy node will be used only to send the final signed transaction.

1 Like

Hey @tungtien

I made a full implementation of the OpenSea plugin in NodeJs.

1 Like

@dani This is awesome that you implemented this.
However, I tried to run the code and I get the following error when executing a buy order on the Rinkeby test network, I believe I have done everything correct and the error is given on line 67:

{ result: { status: 502, data: { error: true } } }

I would be very grateful if you could help me.

createBuyOrder(
‘testnet’,
‘0x16baF0dE678E52367adC69fD067E5eDd1D33e3bF’,
‘3353’,
‘ERC721’,
‘0x83d74eead25e3f4dd89400592c96c33c2f8d791d’,
0.1,
‘0xc778417e063141139fce010982780140aa0cd5ab’
)

Oddly enough it suddenly works for the testnet however at the same time when it is called to the mainnet I get the same error.