Biconomy Gasless SDK (EOA)
DashboardMediumWebsite
  • 🚀Getting Started
  • Introduction
    • 🤘Why Biconomy?
    • 🙂How we simplify transactions
  • Products
    • 💸Gasless Transactions
      • Choose An Approach to Enable Gasless
        • Standard EIP 2771 Approach
          • 1. Register Artifacts on the Dashboard
          • 2. Code Changes
            • Using SDK
            • Using API
        • Custom Implementation Approach
          • 1. Register Artifacts on the Dashboard
          • 2. Code Changes
            • Using SDK
            • Using API
        • Smart Contract Wallet Approach
          • Gnosis
        • Network Agnostic Transactions
          • EIP-2771 Approach
          • Custom Approach
      • Conditional Whitelisting
      • Gasless SDK (EOA) 3
      • Networks Supported
    • ↔️Hyphen - Instant Cross-Chain Transfers
      • SDK
        • DepositManager
        • TransferManager
        • TokenManager
      • APIs
      • Helper Methods
      • Migrating from Hyphen V1
      • Contract Addresses
      • Hyphen Widget
  • Guides
    • 💻Dashboard
      • DApp Statistics
    • ⛽Gas Tank Deposits
      • Via Dashboard
      • Via Smart Contract
  • api
    • 🔧Native Meta Transaction
      • Get Retried Hashes
    • 🌎Dashboard APIs
    • ⚪Whitelist API
      • Whitelist Destination Address
      • Whitelist Proxy Contracts
    • 〰️ Check Limits
    • 💿Biconomy Data API
      • 👨‍🚀Unique User Data
      • 🧑‍🔧Per User Limits Data
      • ⛽Gas Tank Balance Data
  • SDK
    • 📙Gasless SDK (EOA)
      • Configuration
  • Tutorials
    • 🔰Native Meta Transactions
      • How To Build Your First DApp
        • Write Your First Smart Contract
        • Initialize Web3 on Client Side
        • Executing First Blockchain Transaction
      • Enable Native Meta Transactions
        • Smart Contract
          • Describe Your Structs
          • Declare Your Variables
          • Modify Respective Function
        • Client Side
          • Design Your JSON structure
          • Design Your Domain Separator
          • Design Data Types
          • Define Data To Sign
          • Generate Signatures
      • Integrate Biconomy
        • Register On Dashboard
        • Integrate Gasless SDK (EOA)
      • Summary
  • BICO Staking
    • 🪁Safety Module
  • Get in touch
    • 👥Contact Us
  • Misc
    • 🧩Projects on Biconomy
    • 🌐Supported Networks
    • 📫Contract Addresses
    • ✔︎ Smart Contracts Audit
    • ❓FAQs
Powered by GitBook
On this page

Was this helpful?

  1. Products
  2. Gasless Transactions
  3. Choose An Approach to Enable Gasless
  4. Custom Implementation Approach
  5. 2. Code Changes

Using API

Use native meta transaction APIs to easily implement meta transactions!

PreviousUsing SDKNextSmart Contract Wallet Approach

Last updated 3 years ago

Was this helpful?

Before using this API, make sure your smart contracts inherits from one of the contracts mentioned in , and you have removed the dependency on msg.sender property from your smart contracts by replacing it with msgSender() method.

/api/v2/meta-tx/native

POST https://api.biconomy.io/api/v2/meta-tx/native

Our API lets you relay transactions directly to your smart contract without having the end user paying the transaction/gas fee. Just register your dApp on the developer dashboard, upload your smart contracts with meta transaction type as Custom and select executeMetaTransaction method under Manage API section.

Headers

Name
Type
Description

x-api-key

string

API key present on your dashboard for your DApp after DApp registration. This is specific to DApp registered.

Request Body

Name
Type
Description

gasLimit

string

Gas limit to be set in the transaction. It can be a decimal number or hexadecimal string. If omitted web3 estimateGas method will be used to calculate the gasLimit for the transaction.

to

string

Target Contract Address. In case of SCW, It represents user's proxy wallet address.

apiId

string

API id corresponding to the method you want to call in your smart contract. Select executeMetaTransaction method on the dashboard under 'Manage APIs' section or other methods that support native meta transaction like DAI permit method.

params

array

Array of all the parameters required to call the method in the same order they appear in your Smart Contract.

from

string

User client wallet public address who is supposed to be sending the transaction eg. metamask wallet address.

{
    "txHash": "0x19847abea387b5086e4ff35a5e27cc18b72e04c29e81a85dd27316b05c27b818",
    "message": "Meta transaction sent to blockchain",
    "retryDuration": 158, 
    "flag": 200
}
//retryDuration : time in seconds after which you 
//can check resubmitted? endpoint for new hash in case of
//potential resubmits.
{
    "message": "Api does not exist",
    "code": 404
}
{
  "code": 150,
  "message": "DApp limit reached",
  "responseCode": 409,
  "limit": {
    "allowed": false,
    "type": 1,
    "limitLeft": -6,
    "resetTime": 1608940800000
  },
  "allowed": false
}
{
    "message":"Error while gas estimation with message Returned error: The execution failed due to an exception.",
    "code":417
}

Example Curl Request

curl 
--request POST 'https://api.biconomy.io/api/v2/meta-tx/native'
--header 'x-api-key: <api_key_from_dashboard>'
--header 'Content-Type: application/json'
--data-raw '{ 
"userAddress": "<user_public_address>",
"apiId": "<api_id_from_dashboard>", 
"params": [<param1>,<param2>,...... ],
"gasLimit":"0xF4240" 
}'

Example Code Snippets

let sigUtil = require("eth-sig-util"); // additional dependency 

// This web3 instance is used to get user signature from connected wallet
let walletWeb3 = new Web3(window.ethereum);

// Initialize constants
const domainType = [
    { name: "name", type: "string" },
    { name: "version", type: "string" },
    { name: "verifyingContract", type: "address" },
    { name: "salt", type: "bytes32" },
];
const metaTransactionType = [
    { name: "nonce", type: "uint256" },
    { name: "from", type: "address" },
    { name: "functionSignature", type: "bytes" }
];
// replace the chainId 42 if network is not kovan
let domainData = {
    name: "TestContract",
    version: "1",
    verifyingContract: config.contract.address,
    // converts Number to bytes32. pass your chainId instead of 42 if network is not Kovan
    salt : '0x' + (42).toString(16).padStart(64, '0')
};

let userAddress = <selected address>;
let contract = new web3.eth.Contract(
            <Your Contract ABI>,
            <Your Contract Address>
          );
  
let nonce = await contract.methods.getNonce(userAddress).call();
// Create your target method signature.. here we are calling setQuote() method of our contract
let functionSignature = contract.methods.setQuote(newQuote).encodeABI();
let message = {};
message.nonce = parseInt(nonce);
message.from = userAddress;
message.functionSignature = functionSignature;

const dataToSign = JSON.stringify({
  types: {
    EIP712Domain: domainType,
    MetaTransaction: metaTransactionType
  },
  domain: domainData,
  primaryType: "MetaTransaction",
  message: message
});


web3.currentProvider.send(
  {
    jsonrpc: "2.0",
    id: 999999999999,
    method: "eth_signTypedData_v4",
    params: [userAddress, dataToSign]
  },
   function (error, response) {
          console.info(`User signature is ${response.result}`);
          if (error || (response && response.error)) 
           {
            showErrorMessage("Could not get user signature");
           }
           else if (response && response.result) 
           {
             let { r, s, v } = getSignatureParameters(response.result);
             sendTransaction(userAddress, functionSignature, r, s, v);
           }
  }
);

///////////
//helpers//

const sendTransaction = async (userAddress, functionData, r, s, v) => {
        if (web3 && contract) {
            try {
                fetch(`https://api.biconomy.io/api/v2/meta-tx/native`, {
                    method: "POST",
                    headers: {
                      "x-api-key" : <BICONOMY_DAPP_API_KEY>,
                      'Content-Type': 'application/json;charset=utf-8'
                    },
                    body: JSON.stringify({
                      "to": config.contract.address,
                      "apiId": <METHOD_API_ID>,
                      "params": [userAddress, functionData, r, s, v],
                      "from": userAddress
                    })
                  })
                  .then(response=>response.json())
                  .then(async function(result) {
                    console.log(result);
                    showInfoMessage(`Transaction sent by relayer with hash ${result.txHash}`);
          
                    let receipt = await getTransactionReceiptMined(result.txHash, 2000);
                    setTransactionHash(result.txHash);
                    showSuccessMessage("Transaction confirmed on chain");
                    getQuoteFromNetwork();
                  }).catch(function(error) {
                      console.log(error)
                    });
            } catch (error) {
                console.log(error);
            }
        }
    };
    
const getTransactionReceiptMined = (txHash, interval) => {
        const self = this;
        const transactionReceiptAsync = async function(resolve, reject) {
          var receipt = await web3.eth.getTransactionReceipt(txHash);
          if (receipt == null) {
              setTimeout(
                  () => transactionReceiptAsync(resolve, reject),
                  interval ? interval : 500);
          } else {
              resolve(receipt);
          }
        };
    
        if (typeof txHash === "string") {
            return new Promise(transactionReceiptAsync);
        } else {
            throw new Error("Invalid Type: " + txHash);
        }
      };
      
      
const getSignatureParameters = signature => {
        if (!web3.utils.isHexStrict(signature)) {
            throw new Error(
                'Given value "'.concat(signature, '" is not a valid hex string.')
            );
        }
        var r = signature.slice(0, 66);
        var s = "0x".concat(signature.slice(66, 130));
        var v = "0x".concat(signature.slice(130, 132));
        v = web3.utils.hexToNumber(v);
        if (![27, 28].includes(v)) v += 27;
        return {
            r: r,
            s: s,
            v: v
        };
    };      
let abi = require('ethereumjs-abi'); //dependency

// This web3 instance is used to get user signature from connected wallet
let walletWeb3 = new Web3(window.ethereum);

// Initialize constants
let contract = new web3.eth.Contract(
            <Your Contract ABI>,
            <Your Contract Address>
          );

let nonce = await contract.methods.getNonce(userAddress).call();
let functionSignature = contract.methods.setQuote(newQuote).encodeABI();

let messageToSign = constructMetaTransactionMessage(nonce, 
                    <CHAIN_ID>, functionSignature,
                    <YOUR_CONTRACT_ADDRESS>);
                    
// NOTE: We are using walletWeb3 here to get signature from connected wallet
const signature = await walletWeb3.eth.personal.sign(
                "0x" + messageToSign.toString("hex"),
                userAddress
               );

let { r, s, v } = getSignatureParameters(signature);
sendTransaction(userAddress, functionSignature, r, s, v);

///////////
//helpers//

const sendTransaction = async (userAddress, functionData, r, s, v) => {
        if (web3 && contract) {
            try {
                fetch(`https://api.biconomy.io/api/v2/meta-tx/native`, {
                    method: "POST",
                    headers: {
                      "x-api-key" : <BICONOMY_DAPP_API_KEY>,
                      'Content-Type': 'application/json;charset=utf-8'
                    },
                    body: JSON.stringify({
                      "to": config.contract.address,
                      "apiId": <METHOD_API_ID>,
                      "params": [userAddress, functionData, r, s, v],
                      "from": userAddress
                    })
                  })
                  .then(response=>response.json())
                  .then(async function(result) {
                    console.log(result);
                    showInfoMessage(`Transaction sent by relayer with hash ${result.txHash}`);
          
                    let receipt = await getTransactionReceiptMined(result.txHash, 2000);
                    setTransactionHash(result.txHash);
                    showSuccessMessage("Transaction confirmed on chain");
                    getQuoteFromNetwork();
                  }).catch(function(error) {
                      console.log(error)
                    });
            } catch (error) {
                console.log(error);
            }
        }
    };
    
const getTransactionReceiptMined = (txHash, interval) => {
        const self = this;
        const transactionReceiptAsync = async function(resolve, reject) {
          var receipt = await web3.eth.getTransactionReceipt(txHash);
          if (receipt == null) {
              setTimeout(
                  () => transactionReceiptAsync(resolve, reject),
                  interval ? interval : 500);
          } else {
              resolve(receipt);
          }
        };
    
        if (typeof txHash === "string") {
            return new Promise(transactionReceiptAsync);
        } else {
            throw new Error("Invalid Type: " + txHash);
        }
      };
      
      
const getSignatureParameters = signature => {
        if (!web3.utils.isHexStrict(signature)) {
            throw new Error(
                'Given value "'.concat(signature, '" is not a valid hex string.')
            );
        }
        var r = signature.slice(0, 66);
        var s = "0x".concat(signature.slice(66, 130));
        var v = "0x".concat(signature.slice(130, 132));
        v = web3.utils.hexToNumber(v);
        if (![27, 28].includes(v)) v += 27;
        return {
            r: r,
            s: s,
            v: v
        };
    };    
    
const constructMetaTransactionMessage = (nonce, salt, functionSignature, contractAddress) => {
        return abi.soliditySHA3(
            ["uint256","address","uint256","bytes"],
            [nonce, contractAddress, salt, toBuffer(functionSignature)]
        );
    }  
let walletProvider, walletSigner;

walletProvider = new ethers.providers.Web3Provider(window.ethereum);
walletSigner = walletProvider.getSigner();

// Initialize Constants
const domainType = [
    { name: "name", type: "string" },
    { name: "version", type: "string" },
    { name: "verifyingContract", type: "address" },
    { name: "salt", type: "bytes32" },
];
const metaTransactionType = [
    { name: "nonce", type: "uint256" },
    { name: "from", type: "address" },
    { name: "functionSignature", type: "bytes" }
];
// replace the chainId 42 if network is not kovan
let domainData = {
    name: "TestContract",
    version: "1",
    verifyingContract: config.contract.address,
    // converts Number to bytes32. Change 42 to your chainId if network is not Kovan
    salt: ethers.utils.hexZeroPad((ethers.BigNumber.from(42)).toHexString(), 32)
};

let contract = new ethers.Contract(<CONTRACT_ADDRESS>,
              <CONTRACT_ABI>, biconomy.getSignerByAddress(userAddress));
let contractInterface = new ethers.utils.Interface(<CONTRACT_ABI>);

 /*
  This provider is linked to your wallet.
  If needed, substitute your wallet solution in place of window.ethereum 
 */
walletProvider = new ethers.providers.Web3Provider(window.ethereum);
walletSigner = walletProvider.getSigner();

let nonce = await contract.getNonce(userAddress);
let functionSignature = contractInterface.encodeFunctionData("setQuote", [newQuote]);
                
let message = {};
message.nonce = parseInt(nonce);
message.from = userAddress;
message.functionSignature = functionSignature;

const dataToSign = JSON.stringify({
  types: {
    EIP712Domain: domainType,
    MetaTransaction: metaTransactionType
  },
  domain: domainData,
  primaryType: "MetaTransaction",
  message: message
});

/*Its important to use eth_signTypedData_v3 and not v4 to get EIP712 signature 
because we have used salt in domain data instead of chainId*/
// Get the EIP-712 Signature and send the transaction
let signature = await walletProvider.send("eth_signTypedData_v3", [userAddress, dataToSign])
let { r, s, v } = getSignatureParameters(signature);
sendTransaction(userAddress, functionSignature, r, s, v);

///////////
/*helpers*/

const getSignatureParameters = signature => {
        if (!ethers.utils.isHexString(signature)) {
            throw new Error(
                'Given value "'.concat(signature, '" is not a valid hex string.')
            );
        }
        var r = signature.slice(0, 66);
        var s = "0x".concat(signature.slice(66, 130));
        var v = "0x".concat(signature.slice(130, 132));
        v = ethers.BigNumber.from(v).toNumber();
        if (![27, 28].includes(v)) v += 27;
        return {
            r: r,
            s: s,
            v: v
        };
    };
    
const sendTransaction = async (userAddress, functionData, r, s, v) => {
        if (ethersProvider && contract) {
            try {
                fetch(`https://api.biconomy.io/api/v2/meta-tx/native`, {
                    method: "POST",
                    headers: {
                      "x-api-key" : <BICONOMY_DASHBOARD_API_KEY>,
                      'Content-Type': 'application/json;charset=utf-8'
                    },
                    body: JSON.stringify({
                      "to": config.contract.address,
                      "apiId": <METHOD_API_ID>,
                      "params": [userAddress, functionData, r, s, v],
                      "from": userAddress
                    })
                  })
                  .then(response=>response.json())
                  .then(async function(result) {
                    console.log(result);
                    showInfoMessage(`Transaction sent by relayer with hash ${result.txHash}`);
                    let receipt = await ethersProvider.waitForTransaction(
                        result.txHash
                      );
                      console.log(receipt);
                    setTransactionHash(receipt.transactionHash);
                    showSuccessMessage("Transaction confirmed on chain");
                    getQuoteFromNetwork();
                  }).catch(function(error) {
                      console.log(error)
                    });
            } catch (error) {
                console.log(error);
            }
        }
    };
// Extra Dependencies
import abi from "ethereumjs-abi";
import {toBuffer} from "ethereumjs-util";

let walletProvider, walletSigner;

// Initialize Constants
let contract = new ethers.Contract(<CONTRACT_ADDRESS>,
              <CONTRACT_ABI>, biconomy.getSignerByAddress(userAddress));
let contractInterface = new ethers.utils.Interface(<CONTRACT_ABI>);

/*
  This provider is linked to your wallet.
  If needed, substitute your wallet solution in place of window.ethereum 
 */
 walletProvider = new ethers.providers.Web3Provider(window.ethereum);
 walletSigner = walletProvider.getSigner();


let nonce = await contract.getNonce(userAddress); //BigNumber
let functionSignature = contractInterface.encodeFunctionData("setQuote", [newQuote]);
                             
let messageToSign = constructMetaTransactionMessage(nonce.toNumber(), <CHAIN_ID> functionSignature, <YOUR_CONTRACT_ADDRESS>);             
const signature = await walletSigner.signMessage(messageToSign);
let { r, s, v } = getSignatureParameters(signature);
sendTransaction(userAddress, functionSignature, r, s, v);

//////////
/**helpers**/

const getSignatureParameters = signature => {
        if (!ethers.utils.isHexString(signature)) {
            throw new Error(
                'Given value "'.concat(signature, '" is not a valid hex string.')
            );
        }
        var r = signature.slice(0, 66);
        var s = "0x".concat(signature.slice(66, 130));
        var v = "0x".concat(signature.slice(130, 132));
        v = ethers.BigNumber.from(v).toNumber();
        if (![27, 28].includes(v)) v += 27;
        return {
            r: r,
            s: s,
            v: v
        };
    };             
    
const constructMetaTransactionMessage = (nonce, salt, functionSignature, contractAddress) => {
        return abi.soliditySHA3(
            ["uint256","address","uint256","bytes"],
            [nonce, contractAddress, salt, toBuffer(functionSignature)]
        );
      }

const sendTransaction = async (userAddress, functionData, r, s, v) => {
        if (ethersProvider && contract) {
            try {
                fetch(`https://api.biconomy.io/api/v2/meta-tx/native`, {
                    method: "POST",
                    headers: {
                      "x-api-key" : <BICONOMY_DASHBOARD_API_KEY>,
                      'Content-Type': 'application/json;charset=utf-8'
                    },
                    body: JSON.stringify({
                      "to": config.contract.address,
                      "apiId": <METHOD_API_ID>,
                      "params": [userAddress, functionData, r, s, v],
                      "from": userAddress
                    })
                  })
                  .then(response=>response.json())
                  .then(async function(result) {
                    console.log(result);
                    showInfoMessage(`Transaction sent by relayer with hash ${result.txHash}`);
                    let receipt = await ethersProvider.waitForTransaction(
                        result.txHash
                      );
                      console.log(receipt);
                    setTransactionHash(receipt.transactionHash);
                    showSuccessMessage("Transaction confirmed on chain");
                    getQuoteFromNetwork();
                  }).catch(function(error) {
                      console.log(error)
                    });
            } catch (error) {
                console.log(error);
            }
        }
    };

Congratulations 👏

You're now ready to use the custom approach and enable gasless transactions in your dApp using SDK and/or APIs.

for complete example code.

for complete example code.

for complete example code.

for complete example code.

💸
Custom Implementation
Check the repository here
Check the repository here
Check the repository here
Check the repository here