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

1
curl
2
--request POST 'https://api.biconomy.io/api/v2/meta-tx/native'
3
--header 'x-api-key: <api_key_from_dashboard>'
4
--header 'Content-Type: application/json'
5
--data-raw '{
6
"userAddress": "<user_public_address>",
7
"apiId": "<api_id_from_dashboard>",
8
"params": [<param1>,<param2>,...... ],
9
"gasLimit":"0xF4240"
10
}'
Copied!

Example Code Snippets

Web3 + EIP712Sign
Web3 + PersonalSign
Ethers + EIP712Sign
Ethers + PersonalSign
​Check the repository here for complete example code.
1
let sigUtil = require("eth-sig-util"); // additional dependency
2
​
3
// This web3 instance is used to get user signature from connected wallet
4
let walletWeb3 = new Web3(window.ethereum);
5
​
6
// Initialize constants
7
const domainType = [
8
{ name: "name", type: "string" },
9
{ name: "version", type: "string" },
10
{ name: "verifyingContract", type: "address" },
11
{ name: "salt", type: "bytes32" },
12
];
13
const metaTransactionType = [
14
{ name: "nonce", type: "uint256" },
15
{ name: "from", type: "address" },
16
{ name: "functionSignature", type: "bytes" }
17
];
18
// replace the chainId 42 if network is not kovan
19
let domainData = {
20
name: "TestContract",
21
version: "1",
22
verifyingContract: config.contract.address,
23
// converts Number to bytes32. pass your chainId instead of 42 if network is not Kovan
24
salt : '0x' + (42).toString(16).padStart(64, '0')
25
};
26
​
27
let userAddress = <selected address>;
28
let contract = new web3.eth.Contract(
29
<Your Contract ABI>,
30
<Your Contract Address>
31
);
32
33
let nonce = await contract.methods.getNonce(userAddress).call();
34
// Create your target method signature.. here we are calling setQuote() method of our contract
35
let functionSignature = contract.methods.setQuote(newQuote).encodeABI();
36
let message = {};
37
message.nonce = parseInt(nonce);
38
message.from = userAddress;
39
message.functionSignature = functionSignature;
40
​
41
const dataToSign = JSON.stringify({
42
types: {
43
EIP712Domain: domainType,
44
MetaTransaction: metaTransactionType
45
},
46
domain: domainData,
47
primaryType: "MetaTransaction",
48
message: message
49
});
50
​
51
​
52
web3.currentProvider.send(
53
{
54
jsonrpc: "2.0",
55
id: 999999999999,
56
method: "eth_signTypedData_v4",
57
params: [userAddress, dataToSign]
58
},
59
function (error, response) {
60
console.info(`User signature is ${response.result}`);
61
if (error || (response && response.error))
62
{
63
showErrorMessage("Could not get user signature");
64
}
65
else if (response && response.result)
66
{
67
let { r, s, v } = getSignatureParameters(response.result);
68
sendTransaction(userAddress, functionSignature, r, s, v);
69
}
70
}
71
);
72
​
73
///////////
74
//helpers//
75
​
76
const sendTransaction = async (userAddress, functionData, r, s, v) => {
77
if (web3 && contract) {
78
try {
79
fetch(`https://api.biconomy.io/api/v2/meta-tx/native`, {
80
method: "POST",
81
headers: {
82
"x-api-key" : <BICONOMY_DAPP_API_KEY>,
83
'Content-Type': 'application/json;charset=utf-8'
84
},
85
body: JSON.stringify({
86
"to": config.contract.address,
87
"apiId": <METHOD_API_ID>,
88
"params": [userAddress, functionData, r, s, v],
89
"from": userAddress
90
})
91
})
92
.then(response=>response.json())
93
.then(async function(result) {
94
console.log(result);
95
showInfoMessage(`Transaction sent by relayer with hash ${result.txHash}`);
96
97
let receipt = await getTransactionReceiptMined(result.txHash, 2000);
98
setTransactionHash(result.txHash);
99
showSuccessMessage("Transaction confirmed on chain");
100
getQuoteFromNetwork();
101
}).catch(function(error) {
102
console.log(error)
103
});
104
} catch (error) {
105
console.log(error);
106
}
107
}
108
};
109
110
const getTransactionReceiptMined = (txHash, interval) => {
111
const self = this;
112
const transactionReceiptAsync = async function(resolve, reject) {
113
var receipt = await web3.eth.getTransactionReceipt(txHash);
114
if (receipt == null) {
115
setTimeout(
116
() => transactionReceiptAsync(resolve, reject),
117
interval ? interval : 500);
118
} else {
119
resolve(receipt);
120
}
121
};
122
123
if (typeof txHash === "string") {
124
return new Promise(transactionReceiptAsync);
125
} else {
126
throw new Error("Invalid Type: " + txHash);
127
}
128
};
129
130
131
const getSignatureParameters = signature => {
132
if (!web3.utils.isHexStrict(signature)) {
133
throw new Error(
134
'Given value "'.concat(signature, '" is not a valid hex string.')
135
);
136
}
137
var r = signature.slice(0, 66);
138
var s = "0x".concat(signature.slice(66, 130));
139
var v = "0x".concat(signature.slice(130, 132));
140
v = web3.utils.hexToNumber(v);
141
if (![27, 28].includes(v)) v += 27;
142
return {
143
r: r,
144
s: s,
145
v: v
146
};
147
};
Copied!
​Check the repository here for complete example code.
1
let abi = require('ethereumjs-abi'); //dependency
2
​
3
// This web3 instance is used to get user signature from connected wallet
4
let walletWeb3 = new Web3(window.ethereum);
5
​
6
// Initialize constants
7
let contract = new web3.eth.Contract(
8
<Your Contract ABI>,
9
<Your Contract Address>
10
);
11
​
12
let nonce = await contract.methods.getNonce(userAddress).call();
13
let functionSignature = contract.methods.setQuote(newQuote).encodeABI();
14
​
15
let messageToSign = constructMetaTransactionMessage(nonce,
16
<CHAIN_ID>, functionSignature,
17
<YOUR_CONTRACT_ADDRESS>);
18
19
// NOTE: We are using walletWeb3 here to get signature from connected wallet
20
const signature = await walletWeb3.eth.personal.sign(
21
"0x" + messageToSign.toString("hex"),
22
userAddress
23
);
24
​
25
let { r, s, v } = getSignatureParameters(signature);
26
sendTransaction(userAddress, functionSignature, r, s, v);
27
​
28
///////////
29
//helpers//
30
​
31
const sendTransaction = async (userAddress, functionData, r, s, v) => {
32
if (web3 && contract) {
33
try {
34
fetch(`https://api.biconomy.io/api/v2/meta-tx/native`, {
35
method: "POST",
36
headers: {
37
"x-api-key" : <BICONOMY_DAPP_API_KEY>,
38
'Content-Type': 'application/json;charset=utf-8'
39
},
40
body: JSON.stringify({
41
"to": config.contract.address,
42
"apiId": <METHOD_API_ID>,
43
"params": [userAddress, functionData, r, s, v],
44
"from": userAddress
45
})
46
})
47
.then(response=>response.json())
48
.then(async function(result) {
49
console.log(result);
50
showInfoMessage(`Transaction sent by relayer with hash ${result.txHash}`);
51
52
let receipt = await getTransactionReceiptMined(result.txHash, 2000);
53
setTransactionHash(result.txHash);
54
showSuccessMessage("Transaction confirmed on chain");
55
getQuoteFromNetwork();
56
}).catch(function(error) {
57
console.log(error)
58
});
59
} catch (error) {
60
console.log(error);
61
}
62
}
63
};
64
65
const getTransactionReceiptMined = (txHash, interval) => {
66
const self = this;
67
const transactionReceiptAsync = async function(resolve, reject) {
68
var receipt = await web3.eth.getTransactionReceipt(txHash);
69
if (receipt == null) {
70
setTimeout(
71
() => transactionReceiptAsync(resolve, reject),
72
interval ? interval : 500);
73
} else {
74
resolve(receipt);
75
}
76
};
77
78
if (typeof txHash === "string") {
79
return new Promise(transactionReceiptAsync);
80
} else {
81
throw new Error("Invalid Type: " + txHash);
82
}
83
};
84
85
86
const getSignatureParameters = signature => {
87
if (!web3.utils.isHexStrict(signature)) {
88
throw new Error(
89
'Given value "'.concat(signature, '" is not a valid hex string.')
90
);
91
}
92
var r = signature.slice(0, 66);
93
var s = "0x".concat(signature.slice(66, 130));
94
var v = "0x".concat(signature.slice(130, 132));
95
v = web3.utils.hexToNumber(v);
96
if (![27, 28].includes(v)) v += 27;
97
return {
98
r: r,
99
s: s,
100
v: v
101
};
102
};
103
104
const constructMetaTransactionMessage = (nonce, salt, functionSignature, contractAddress) => {
105
return abi.soliditySHA3(
106
["uint256","address","uint256","bytes"],
107
[nonce, contractAddress, salt, toBuffer(functionSignature)]
108
);
109
}
Copied!
​Check the repository here for complete example code.
1
let walletProvider, walletSigner;
2
​
3
walletProvider = new ethers.providers.Web3Provider(window.ethereum);
4
walletSigner = walletProvider.getSigner();
5
​
6
// Initialize Constants
7
const domainType = [
8
{ name: "name", type: "string" },
9
{ name: "version", type: "string" },
10
{ name: "verifyingContract", type: "address" },
11
{ name: "salt", type: "bytes32" },
12
];
13
const metaTransactionType = [
14
{ name: "nonce", type: "uint256" },
15
{ name: "from", type: "address" },
16
{ name: "functionSignature", type: "bytes" }
17
];
18
// replace the chainId 42 if network is not kovan
19
let domainData = {
20
name: "TestContract",
21
version: "1",
22
verifyingContract: config.contract.address,
23
// converts Number to bytes32. Change 42 to your chainId if network is not Kovan
24
salt: ethers.utils.hexZeroPad((ethers.BigNumber.from(42)).toHexString(), 32)
25
};
26
​
27
let contract = new ethers.Contract(<CONTRACT_ADDRESS>,
28
<CONTRACT_ABI>, biconomy.getSignerByAddress(userAddress));
29
let contractInterface = new ethers.utils.Interface(<CONTRACT_ABI>);
30
​
31
/*
32
This provider is linked to your wallet.
33
If needed, substitute your wallet solution in place of window.ethereum
34
*/
35
walletProvider = new ethers.providers.Web3Provider(window.ethereum);
36
walletSigner = walletProvider.getSigner();
37
​
38
let nonce = await contract.getNonce(userAddress);
39
let functionSignature = contractInterface.encodeFunctionData("setQuote", [newQuote]);
40
41
let message = {};
42
message.nonce = parseInt(nonce);
43
message.from = userAddress;
44
message.functionSignature = functionSignature;
45
​
46
const dataToSign = JSON.stringify({
47
types: {
48
EIP712Domain: domainType,
49
MetaTransaction: metaTransactionType
50
},
51
domain: domainData,
52
primaryType: "MetaTransaction",
53
message: message
54
});
55
​
56
/*Its important to use eth_signTypedData_v3 and not v4 to get EIP712 signature
57
because we have used salt in domain data instead of chainId*/
58
// Get the EIP-712 Signature and send the transaction
59
let signature = await walletProvider.send("eth_signTypedData_v3", [userAddress, dataToSign])
60
let { r, s, v } = getSignatureParameters(signature);
61
sendTransaction(userAddress, functionSignature, r, s, v);
62
​
63
///////////
64
/*helpers*/
65
​
66
const getSignatureParameters = signature => {
67
if (!ethers.utils.isHexString(signature)) {
68
throw new Error(
69
'Given value "'.concat(signature, '" is not a valid hex string.')
70
);
71
}
72
var r = signature.slice(0, 66);
73
var s = "0x".concat(signature.slice(66, 130));
74
var v = "0x".concat(signature.slice(130, 132));
75
v = ethers.BigNumber.from(v).toNumber();
76
if (![27, 28].includes(v)) v += 27;
77
return {
78
r: r,
79
s: s,
80
v: v
81
};
82
};
83
84
const sendTransaction = async (userAddress, functionData, r, s, v) => {
85
if (ethersProvider && contract) {
86
try {
87
fetch(`https://api.biconomy.io/api/v2/meta-tx/native`, {
88
method: "POST",
89
headers: {
90
"x-api-key" : <BICONOMY_DASHBOARD_API_KEY>,
91
'Content-Type': 'application/json;charset=utf-8'
92
},
93
body: JSON.stringify({
94
"to": config.contract.address,
95
"apiId": <METHOD_API_ID>,
96
"params": [userAddress, functionData, r, s, v],
97
"from": userAddress
98
})
99
})
100
.then(response=>response.json())
101
.then(async function(result) {
102
console.log(result);
103
showInfoMessage(`Transaction sent by relayer with hash ${result.txHash}`);
104
let receipt = await ethersProvider.waitForTransaction(
105
result.txHash
106
);
107
console.log(receipt);
108
setTransactionHash(receipt.transactionHash);
109
showSuccessMessage("Transaction confirmed on chain");
110
getQuoteFromNetwork();
111
}).catch(function(error) {
112
console.log(error)
113
});
114
} catch (error) {
115
console.log(error);
116
}
117
}
118
};
Copied!
​Check the repository here for complete example code.
1
// Extra Dependencies
2
import abi from "ethereumjs-abi";
3
import {toBuffer} from "ethereumjs-util";
4
​
5
let walletProvider, walletSigner;
6
​
7
// Initialize Constants
8
let contract = new ethers.Contract(<CONTRACT_ADDRESS>,
9
<CONTRACT_ABI>, biconomy.getSignerByAddress(userAddress));
10
let contractInterface = new ethers.utils.Interface(<CONTRACT_ABI>);
11
​
12
/*
13
This provider is linked to your wallet.
14
If needed, substitute your wallet solution in place of window.ethereum
15
*/
16
walletProvider = new ethers.providers.Web3Provider(window.ethereum);
17
walletSigner = walletProvider.getSigner();
18
​
19
​
20
let nonce = await contract.getNonce(userAddress); //BigNumber
21
let functionSignature = contractInterface.encodeFunctionData("setQuote", [newQuote]);
22
23
let messageToSign = constructMetaTransactionMessage(nonce.toNumber(), <CHAIN_ID> functionSignature, <YOUR_CONTRACT_ADDRESS>);
24
const signature = await walletSigner.signMessage(messageToSign);
25
let { r, s, v } = getSignatureParameters(signature);
26
sendTransaction(userAddress, functionSignature, r, s, v);
27
​
28
//////////
29
/**helpers**/
30
​
31
const getSignatureParameters = signature => {
32
if (!ethers.utils.isHexString(signature)) {
33
throw new Error(
34
'Given value "'.concat(signature, '" is not a valid hex string.')
35
);
36
}
37
var r = signature.slice(0, 66);
38
var s = "0x".concat(signature.slice(66, 130));
39
var v = "0x".concat(signature.slice(130, 132));
40
v = ethers.BigNumber.from(v).toNumber();
41
if (![27, 28].includes(v)) v += 27;
42
return {
43
r: r,
44
s: s,
45
v: v
46
};
47
};
48
49
const constructMetaTransactionMessage = (nonce, salt, functionSignature, contractAddress) => {
50
return abi.soliditySHA3(
51
["uint256","address","uint256","bytes"],
52
[nonce, contractAddress, salt, toBuffer(functionSignature)]
53
);
54
}
55
​
56
const sendTransaction = async (userAddress, functionData, r, s, v) => {
57
if (ethersProvider && contract) {
58
try {
59
fetch(`https://api.biconomy.io/api/v2/meta-tx/native`, {
60
method: "POST",
61
headers: {
62
"x-api-key" : <BICONOMY_DASHBOARD_API_KEY>,
63
'Content-Type': 'application/json;charset=utf-8'
64
},
65
body: JSON.stringify({
66
"to": config.contract.address,
67
"apiId": <METHOD_API_ID>,
68
"params": [userAddress, functionData, r, s, v],
69
"from": userAddress
70
})
71
})
72
.then(response=>response.json())
73
.then(async function(result) {
74
console.log(result);
75
showInfoMessage(`Transaction sent by relayer with hash ${result.txHash}`);
76
let receipt = await ethersProvider.waitForTransaction(
77
result.txHash
78
);
79
console.log(receipt);
80
setTransactionHash(receipt.transactionHash);
81
showSuccessMessage("Transaction confirmed on chain");
82
getQuoteFromNetwork();
83
}).catch(function(error) {
84
console.log(error)
85
});
86
} catch (error) {
87
console.log(error);
88
}
89
}
90
};
91
​
92
​
Copied!

Congratulations πŸ‘

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