API

How to use the Forward API to enable your users to pay gas using ERC20 tokens

If you're trying to integrate Biconomy into an existing library for handling transactions or logic unique to your app, it may make sense to call Biconomy's API directly. This is especially true if your app is not made in Javascript or Typescript.

Using the API lets you relay the transaction directly to your smart contract by paying the gas fees in supported stable coins. It completely bypasses the Ethers/Web3 etc objects you may use to query contracts and or sign transactions.

Note:

Before using this API, make sure you have Trusted Forwarder support in your smart contracts i.e., you have removed the dependency on msg.sender property from your smart contracts and trusted forwarder is added.

post
/api/v2/meta-tx/native

https://api.biconomy.io/api/v2/meta-tx/native
This method is used to call Biconomy's relayers. Upon receiving a valid request, we will relay your transaction and return a response containing its transaction hash - if successful.
Request
Response
Request
Headers
x-api-key
required
string
API key present on your dashboard for your DApp after DApp registration. This is specific to DApp registered.
Body Parameters
signatureType
optional
string
Signature type chosen by the developer to sign the transaction message. Accepted values are EIP712_SIGN and PERSONAL_SIGN. If omitted personal signature will be assumed by the server and will expect only two parameters (signature and request) in params.
from
required
string
User client wallet public address who is supposed to be sending the transaction eg. metamask wallet address or portis wallet address.
params
required
array
Array of all the parameters required to call the execute methods of the erc20 forwarder depending on the signature type. Read more about this in below section
apiId
required
string
API id corresponding to the method you want to call in your smart contract. It can be found on the dashboard under 'Manage APIs' section.
to
required
string
Target Contract Address
gasLimit
optional
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.
Response
200: OK
{
"txHash": "0x84bc6f25b964f794f90c59b4f97a16aadc878ce53187703124b5e0ee52e15af9",
"log": "Meta transaction sent to blockchain",
"flag": 200
}
404: Not Found
Required data not found
{
"message": "Api does not exist",
"code": 404
}
409: Conflict
When Limits set on dashboard are reached and also when ERC20 token/ETH price has fallen below a certain threshold. Check the code field to see which limit is reached. See "Check Limit" under API section to see all possible values of code field. code = 150, when DApp limits are reached code = 151, when User limits are reached code = 152, when API per user limits are reached code = 153, when pre flight checks are not passed for token gas price
{
"code": 150,
"message": "DApp limit reached",
"responseCode": 409,
"limit": {
"allowed": false,
"type": 1,
"limitLeft": -6,
"resetTime": 1608940800000
},
"allowed": false
}
417: Expectation Failed
When gas estimation is failed due to any revert on your contracts due to any business logic. Usually you'll see the revert error message in response but to debug the issue, try sending the same transaction without Biconomy to see the actual error on explorer.
{
"log": "Error while executing Blockchain Transactions",
"error": "Error: Invalid number of parameters for \"methodName\". Got 1 expected 0!",
"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 '{ "from": "<user_public_address>", "apiId": "<api_id_from_dashboard>", "params": [<param1>,<param2>,...... ], "to": "<recipient_contract_address>","gasLimit":"0xF4240","signatureType":"EIP712Sign" }'

If signature type is EIP712 params are request, domainSeperator, signature. If signature type is personal sign (assumed default if signatureType parameter is omitted in API request body) params are request and signature.

Biconomy has made helpers to build above mentioned parameters from your dapp transaction. These have been written in JS, but you could easily port the functions if you're working in another language.

In order to enable paying gas fees in ERC20 tokens, below is example code how to use biconomy helpers JS to build request, domain separator and data to sign for each signature type and then send API request to the Biconomy servers where our relayer infrastructure will process your transaction.

Sample code

web3
ethers
web3

​Check the repository here for complete example code

/* this example demonstrates paying DAI tokens as gas fees*/
// token address would be DAI address for your provider network Id
​
import { helperAttributes,
erc20FeeProxyAddressMap,
getDomainSeperator,
getDataToSignForPersonalSign,
getDataToSignForEIP712,
buildForwardTxRequest,
getBiconomyForwarderConfig,
getTokenGasPrice,
getDaiPermit} from "./erc20ForwarderHelpers";
​
let provider = <ethereum provider>;
let address = <wallet public address>;
let networkId = <your provider networkId here>;
let tokenAddress = <address of token to pay with>;
​
// Initialize Contracts
let contract = new web3.eth.Contract(
<CONTRACT_ABI>,
<CONTRACT_ADDRESS>
);
​
let functionSignature = contract.methods.addRating(1, 5).encodeABI();
let gasLimit = await contract.methods.addRating(1, 5).estimateGas({from: address});
​
let forwarder = await getBiconomyForwarderConfig(networkId);
let forwarderContract = new web3.eth.Contract(
forwarder.abi,
forwarder.address
);
​
//const batchId = await forwarderContract.methods.getBatch(userAddress).call();
const batchNonce = await forwarderContract.methods.getNonce(address,0).call();
const gasLimitNum = Number(gasLimit);
​
// only required if user has not given permit earlier to spend dai
// all options are optional except network Id
const daiPermitOptions = {
spender: erc20FeeProxyAddressMap[networkId],
expiry: Math.floor(Date.now() / 1000 + 3600),
allowed: true,
networkId: networkId
};
​
await getDaiPermit(provider,address,daiPermitOptions);
​
let forwarder = await getBiconomyForwarder(provider,networkId);
​
//const batchId = await forwarder.getBatch(address);
const batchNonce = await forwarder.getNonce(address,0);
const tokenGasPrice = await getTokenGasPrice(networkId,tokenAddress);
​
let builtTx = await buildForwardTxRequest(networkId,{account:address,to:contract.address,gasLimitNum,batchId:0,batchNonce,tokenGasPrice:tokGasPrice,data:functionSignature,tokenAddress});
const req = builtTx.request;
console.log(req);
// Show your users fees!
const cost = builtTx.cost;
console.log(`paying ${cost} number of tokens as gas fees`);
​
​
/* If you wish to use EIP712 Signature type */
const domainSeparator = getDomainSeperator(networkId);
const dataToSign = getDataToSignForEIP712(req,networkId);
​
let sig;
// sign message //
​
// get the signature with RPC method eth_signTypedData_v4
// NOTE : for personal signature refer code on line# 60
​
const promi = new Promise(async function(resolve, reject) {
await web3.currentProvider.send(
{
jsonrpc: "2.0",
id: 999999999999,
method: "eth_signTypedData_v4",
params: [address, dataToSign]
}, function(error, res){
if(error) {
reject(error);
} else {
resolve(res.result);
}
});
});
​
promi.then(async function(signature){
console.log('signature ' + signature);
sig = signature;
// make the API call here i.e. sendTransaction
}).catch(function(error) {
console.log('could not get signature error ' + error);
});
​
/** If you wish to use Personal Signature type
* //domain seperator is not required
* //build the request as mentioned above
* const hashToSign = getDataToSignForPersonalSign(req);
* const sig = await web3.eth.personal.sign("0x" + hashToSign.toString("hex"), userAddress);
*/
​
ethers

​Check the repository here for complete example code

/* this example demonstrates paying DAI tokens as gas fees*/
// token address would be DAI for your provider network Id
// you can use any supported token by passing tokenAddress in forward request and getTokenGasPrice below
​
import { helperAttributes,
erc20FeeProxyAddressMap,
getDomainSeperator,
getDataToSignForPersonalSign,
getDataToSignForEIP712,
buildForwardTxRequest,
getBiconomyForwarderConfig,
getTokenGasPrice,
getDaiPermit} from "./erc20ForwarderHelpers";
​
let provider = <ethereum provider>;
let address = <wallet public address>;
let networkId = <your provider networkId here>;
let tokenAddress = <address of token to pay with>;
​
let ethersProvider = new ethers.providers.Web3Provider(provider);
let signer = ethersProvider.getSigner();
​
// Initialize Contracts
let contract = new ethers.Contract(<CONTRACT_ADDRESS>,
<CONTRACT_ABI>, signer);
​
let {data} = await contract.populateTransaction.addRating(1, 5);
let gasPrice = await ethersProvider.getGasPrice();
let gasLimit = await ethersProvider.estimateGas({
to: contract.address,
from: address,
data: data
});
// only required if user has not given permit earlier to spend dai
// all options are optional except network Id
const daiPermitOptions = {
spender: erc20FeeProxyAddressMap[networkId],
expiry: Math.floor(Date.now() / 1000 + 3600),
allowed: true,
networkId: networkId
};
​
await getDaiPermit(provider,address,daiPermitOptions);
​
let forwarder = await getBiconomyForwarderConfig(networkId);
let forwarderContract = new ethers.Contract(
forwarder.address,
forwarder.abi,
signer
);
​
const batchNonce = await forwarderContract.getNonce(userAddress,0);
//const batchId = await forwarderContract.getBatch(userAddress);
​
const tokenGasPrice = await getTokenGasPrice(networkId,tokenAddress);
const gasLimitNum = Number(gasLimit);
​
let builtTx = await buildForwardTxRequest(networkId,{account:address,to:contract.address,gasLimitNum,batchId:0,batchNonce,tokenGasPrice:tokGasPrice,data,tokenAddress});
const req = builtTx.request;
console.log(req);
// Show your users fees!
const cost = builtTx.cost;
console.log(`paying ${cost} number of tokens as gas fees`);
​
​
/* If you wish to use EIP712 Signature type */
const domainSeparator = getDomainSeperator(networkId);
const dataToSign = getDataToSignForEIP712(req,networkId);
​
let sig;
// get the user's signature
ethersProvider.send("eth_signTypedData_v4", [address, dataToSign])
.then(function(signature){
sig = signature;
// make the API call
//sendTransaction({userAddress, req, domainSeparator, sig, signatureType:"EIP712_SIGN"});
})
.catch(function(error) {
console.log(error)
});
​
/** If you wish to use Personal Signature type
* //domain seperator is not required
* //build the request as mentioned above
* const hashToSign = getDataToSignForPersonalSign(req);
*/
/* signer.signMessage(hashToSign)
.then(function(sig){
console.log('signature ' + sig);
// make API call
//sendTransaction({userAddress, req, sig, signatureType:"PERSONAL_SIGN"});
})
.catch(function(error) {
console.log(error)
});
*/

​

Great! once your parameters are built you are ready to make the API call to send gasless transactions!

const sendTransaction = ({userAddress, req, sig, domainSeparator, signatureType}) => {
if (web3 && contract) {
let params;
if(domainSeparator) {
params = [req, domainSeparator, sig]
} else {
params = [req, sig]
}
try {
fetch(`https://api.biconomy.io/api/v2/meta-tx/native`, {
method: "POST",
headers: {
"x-api-key" : <dapp api key>,
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify({
"to": contract.address,
"apiId": <api_id_from_dashboard>,
"params": params,
"from": userAddress,
"signatureType": signatureType
})
})
.then(response=>response.json())
.then(function(result) {
console.log('transaction hash ' + result.txHash);
})
// once you receive transaction hash you can wait for mined transaction receipt here
// using Promise in web3 : web3.eth.getTransactionReceipt
// or using ethersProvider event emitters
.catch(function(error) {
console.log(error)
});
} catch (error) {showInfoMessage(`Transaction sent by relayer with hash ${result.txHash}`);
console.log(error);
}
}
};

Congratulations πŸ‘

You're now ready to enable paying gas fees for transactions using ERC20 tokens in your DApp using our API.