Using API
Use native meta transaction APIs to easily implement meta transactions!
Before using this API, make sure your smart contracts inherits from one of the contracts mentioned in Custom Implementation , and you have removed the dependency on msg.sender property from your smart contracts by replacing it with msgSender() method.
post
https://api.biconomy.io
/api/v2/meta-tx/native
/api/v2/meta-tx/native

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

Web3 + EIP712Sign
Web3 + PersonalSign
Ethers + EIP712Sign
Ethers + PersonalSign
​Check the repository here for complete example code.
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
};
};
​Check the repository here for complete example code.
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)]
);
}
​Check the repository here for complete example code.
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);
}
}
};
​Check the repository here for complete example code.
// 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.