0
# GSN Support
1
2
OpenZeppelin provides Gas Station Network (GSN) integration for enabling gasless transactions and meta-transactions. GSN allows users to interact with smart contracts without holding ETH for gas fees, with relay contracts covering the transaction costs.
3
4
## Capabilities
5
6
### GSNRecipient - Base GSN Integration
7
8
Base contract for receiving GSN meta-transactions with proper context handling.
9
10
```solidity { .api }
11
/**
12
* Base GSN recipient contract
13
*/
14
abstract contract GSNRecipient is IRelayRecipient, Context {
15
/**
16
* Returns the address of the RelayHub contract
17
*/
18
function getHubAddr() public view returns (address);
19
20
/**
21
* Version of GSN the recipient expects
22
*/
23
function versionRecipient() external view override virtual returns (string memory);
24
25
/**
26
* Called by RelayHub to validate if this recipient accepts being charged for a relayed call
27
*/
28
function acceptRelayedCall(
29
address relay,
30
address from,
31
bytes calldata encodedFunction,
32
uint256 transactionFee,
33
uint256 gasPrice,
34
uint256 gasLimit,
35
uint256 nonce,
36
bytes calldata approvalData,
37
uint256 maxPossibleCharge
38
) external view virtual override returns (uint256, bytes memory);
39
40
/**
41
* Called by RelayHub on approved relay call requests, before the relayed call is executed
42
*/
43
function preRelayedCall(bytes calldata context) external virtual override returns (bytes32);
44
45
/**
46
* Called by RelayHub on approved relay call requests, after the relayed call is executed
47
*/
48
function postRelayedCall(
49
bytes calldata context,
50
bool success,
51
uint256 actualCharge,
52
bytes32 preRetVal
53
) external virtual override;
54
55
/**
56
* Returns the sender of the transaction when called through GSN
57
*/
58
function _msgSender() internal view virtual override returns (address payable);
59
60
/**
61
* Returns the data of the transaction when called through GSN
62
*/
63
function _msgData() internal view virtual override returns (bytes memory);
64
}
65
```
66
67
### GSNRecipientSignature - Signature-Based GSN
68
69
GSN recipient that validates transactions using cryptographic signatures.
70
71
```solidity { .api }
72
/**
73
* GSN recipient that accepts relayed calls with valid signatures from trusted signers
74
*/
75
contract GSNRecipientSignature is GSNRecipient {
76
/**
77
* Sets the trusted signer that can authorize GSN transactions
78
*/
79
constructor(address trustedSigner);
80
81
/**
82
* Accepts a relayed call if it has a valid signature from the trusted signer
83
*/
84
function acceptRelayedCall(
85
address relay,
86
address from,
87
bytes calldata encodedFunction,
88
uint256 transactionFee,
89
uint256 gasPrice,
90
uint256 gasLimit,
91
uint256 nonce,
92
bytes calldata approvalData,
93
uint256 maxPossibleCharge
94
) external view override returns (uint256, bytes memory);
95
96
/**
97
* Returns the trusted signer address
98
*/
99
function getTrustedSigner() public view returns (address);
100
}
101
```
102
103
### GSNRecipientERC20Fee - Token-Based GSN Fees
104
105
GSN recipient that charges fees in ERC20 tokens instead of ETH.
106
107
```solidity { .api }
108
/**
109
* GSN recipient that charges fees in ERC20 tokens
110
*/
111
contract GSNRecipientERC20Fee is GSNRecipient {
112
/**
113
* ERC20 token used for fee payments
114
*/
115
IERC20 public token;
116
117
/**
118
* Sets the ERC20 token to be used for fee payments
119
*/
120
constructor(string memory name, string memory symbol);
121
122
/**
123
* Returns the ERC20 token used for payments
124
*/
125
function token() public view returns (IERC20);
126
127
/**
128
* Mints tokens for the account
129
*/
130
function mint(address account, uint256 amount) public;
131
132
/**
133
* Accepts relayed calls if the user has enough token balance for fees
134
*/
135
function acceptRelayedCall(
136
address relay,
137
address from,
138
bytes calldata encodedFunction,
139
uint256 transactionFee,
140
uint256 gasPrice,
141
uint256 gasLimit,
142
uint256 nonce,
143
bytes calldata approvalData,
144
uint256 maxPossibleCharge
145
) external view override returns (uint256, bytes memory);
146
147
/**
148
* Pre-charge the user by locking their tokens
149
*/
150
function preRelayedCall(bytes calldata context) external override returns (bytes32);
151
152
/**
153
* Post-process the payment, burning tokens equivalent to gas used
154
*/
155
function postRelayedCall(
156
bytes calldata context,
157
bool success,
158
uint256 actualCharge,
159
bytes32 preRetVal
160
) external override;
161
}
162
```
163
164
### IRelayRecipient - GSN Interface
165
166
Interface that must be implemented by GSN recipient contracts.
167
168
```solidity { .api }
169
/**
170
* Interface for contracts that can receive GSN relayed calls
171
*/
172
interface IRelayRecipient {
173
/**
174
* Returns the version of the recipient contract
175
*/
176
function versionRecipient() external view returns (string memory);
177
178
/**
179
* Called by RelayHub to validate if this recipient accepts being charged for a relayed call
180
*/
181
function acceptRelayedCall(
182
address relay,
183
address from,
184
bytes calldata encodedFunction,
185
uint256 transactionFee,
186
uint256 gasPrice,
187
uint256 gasLimit,
188
uint256 nonce,
189
bytes calldata approvalData,
190
uint256 maxPossibleCharge
191
) external view returns (uint256, bytes memory);
192
193
/**
194
* Called by RelayHub before executing the relayed call
195
*/
196
function preRelayedCall(bytes calldata context) external returns (bytes32);
197
198
/**
199
* Called by RelayHub after executing the relayed call
200
*/
201
function postRelayedCall(
202
bytes calldata context,
203
bool success,
204
uint256 actualCharge,
205
bytes32 preRetVal
206
) external;
207
}
208
```
209
210
### IRelayHub - GSN Relay Hub Interface
211
212
Interface for the RelayHub contract that coordinates meta-transactions.
213
214
```solidity { .api }
215
/**
216
* Interface for the RelayHub contract
217
*/
218
interface IRelayHub {
219
/**
220
* RelayHub version
221
*/
222
function version() external view returns (string memory);
223
224
/**
225
* Relays a transaction
226
*/
227
function relayCall(
228
address from,
229
address to,
230
bytes calldata encodedFunction,
231
uint256 transactionFee,
232
uint256 gasPrice,
233
uint256 gasLimit,
234
uint256 nonce,
235
bytes calldata signature,
236
bytes calldata approvalData
237
) external;
238
239
/**
240
* Returns the balance of a recipient
241
*/
242
function balanceOf(address target) external view returns (uint256);
243
244
/**
245
* Deposits funds for a recipient
246
*/
247
function depositFor(address target) external payable;
248
249
/**
250
* Registers a relay server
251
*/
252
function registerRelay(uint256 transactionFee, string calldata url) external;
253
254
/**
255
* Returns relay information
256
*/
257
function getRelay(address relay) external view returns (uint256 totalStake, uint256 unstakeDelay, uint256 unstakeTime, address payable owner, RelayState state);
258
259
enum RelayState {
260
Unknown,
261
Staked,
262
Registered,
263
Removed
264
}
265
}
266
```
267
268
## GSN Integration Examples
269
270
### Basic GSN-Enabled Contract
271
272
```solidity
273
import "@openzeppelin/contracts/GSN/GSNRecipient.sol";
274
import "@openzeppelin/contracts/access/Ownable.sol";
275
276
contract GaslessCounter is GSNRecipient, Ownable {
277
uint256 public counter;
278
mapping(address => uint256) public userCounters;
279
280
event CounterIncremented(address indexed user, uint256 newValue);
281
282
function versionRecipient() external view override returns (string memory) {
283
return "2.2.0";
284
}
285
286
// Accept all relayed calls
287
function acceptRelayedCall(
288
address relay,
289
address from,
290
bytes calldata encodedFunction,
291
uint256 transactionFee,
292
uint256 gasPrice,
293
uint256 gasLimit,
294
uint256 nonce,
295
bytes calldata approvalData,
296
uint256 maxPossibleCharge
297
) external view override returns (uint256, bytes memory) {
298
return _approveRelayedCall();
299
}
300
301
function preRelayedCall(bytes calldata context) external override returns (bytes32) {
302
// No pre-processing needed
303
return bytes32(0);
304
}
305
306
function postRelayedCall(
307
bytes calldata context,
308
bool success,
309
uint256 actualCharge,
310
bytes32 preRetVal
311
) external override {
312
// No post-processing needed
313
}
314
315
// This function can be called without gas by end users
316
function increment() external {
317
address sender = _msgSender(); // Gets actual user, not relay
318
counter++;
319
userCounters[sender]++;
320
emit CounterIncremented(sender, userCounters[sender]);
321
}
322
323
function getCounter() external view returns (uint256) {
324
return counter;
325
}
326
327
function getUserCounter(address user) external view returns (uint256) {
328
return userCounters[user];
329
}
330
}
331
```
332
333
### Signature-Based GSN Authentication
334
335
```solidity
336
import "@openzeppelin/contracts/GSN/GSNRecipientSignature.sol";
337
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
338
339
contract GaslessToken is ERC20, GSNRecipientSignature {
340
constructor(address trustedSigner)
341
ERC20("GaslessToken", "GLT")
342
GSNRecipientSignature(trustedSigner)
343
{
344
_mint(msg.sender, 1000000 * 10**decimals());
345
}
346
347
function versionRecipient() external view override returns (string memory) {
348
return "2.2.0";
349
}
350
351
// Users can transfer tokens without gas if they have valid signatures
352
function transfer(address to, uint256 amount) public override returns (bool) {
353
return super.transfer(to, amount);
354
}
355
356
// Override _msgSender to work with GSN
357
function _msgSender() internal view override(Context, GSNRecipient) returns (address payable) {
358
return GSNRecipient._msgSender();
359
}
360
361
function _msgData() internal view override(Context, GSNRecipient) returns (bytes memory) {
362
return GSNRecipient._msgData();
363
}
364
}
365
```
366
367
### Token-Fee GSN Integration
368
369
```solidity
370
import "@openzeppelin/contracts/GSN/GSNRecipientERC20Fee.sol";
371
372
contract TokenFeeApp is GSNRecipientERC20Fee {
373
mapping(address => string) public userMessages;
374
375
constructor() GSNRecipientERC20Fee("AppToken", "APP") {}
376
377
function versionRecipient() external view override returns (string memory) {
378
return "2.2.0";
379
}
380
381
// Allow users to set messages if they have enough tokens for fees
382
function setMessage(string calldata message) external {
383
address sender = _msgSender();
384
userMessages[sender] = message;
385
}
386
387
function getMessage(address user) external view returns (string memory) {
388
return userMessages[user];
389
}
390
391
// Owner can mint tokens for users to pay GSN fees
392
function mintTokensForUser(address user, uint256 amount) external {
393
mint(user, amount);
394
}
395
}
396
```
397
398
### Advanced GSN Pattern with Custom Logic
399
400
```solidity
401
import "@openzeppelin/contracts/GSN/GSNRecipient.sol";
402
import "@openzeppelin/contracts/access/AccessControl.sol";
403
404
contract AdvancedGSNApp is GSNRecipient, AccessControl {
405
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
406
407
mapping(address => uint256) public userCredits;
408
mapping(address => uint256) public dailyUsage;
409
mapping(address => uint256) public lastUsageReset;
410
411
uint256 public constant DAILY_LIMIT = 10;
412
uint256 public constant CREDIT_COST = 1 ether; // Cost per transaction
413
414
constructor() {
415
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
416
_setupRole(RELAYER_ROLE, msg.sender);
417
}
418
419
function versionRecipient() external view override returns (string memory) {
420
return "2.2.0";
421
}
422
423
function acceptRelayedCall(
424
address relay,
425
address from,
426
bytes calldata encodedFunction,
427
uint256 transactionFee,
428
uint256 gasPrice,
429
uint256 gasLimit,
430
uint256 nonce,
431
bytes calldata approvalData,
432
uint256 maxPossibleCharge
433
) external view override returns (uint256, bytes memory) {
434
// Check if user has credits or is within daily limit
435
if (userCredits[from] >= CREDIT_COST) {
436
return _approveRelayedCall(abi.encode(from, true)); // Use credits
437
}
438
439
// Check daily limit
440
uint256 today = block.timestamp / 1 days;
441
uint256 lastReset = lastUsageReset[from] / 1 days;
442
uint256 usage = (today > lastReset) ? 0 : dailyUsage[from];
443
444
if (usage < DAILY_LIMIT) {
445
return _approveRelayedCall(abi.encode(from, false)); // Use daily limit
446
}
447
448
return _rejectRelayedCall(uint256(GSNErrorCodes.INSUFFICIENT_BALANCE));
449
}
450
451
function preRelayedCall(bytes calldata context) external override returns (bytes32) {
452
(address user, bool useCredits) = abi.decode(context, (address, bool));
453
454
if (useCredits) {
455
userCredits[user] -= CREDIT_COST;
456
} else {
457
uint256 today = block.timestamp / 1 days;
458
uint256 lastReset = lastUsageReset[user] / 1 days;
459
460
if (today > lastReset) {
461
dailyUsage[user] = 0;
462
lastUsageReset[user] = block.timestamp;
463
}
464
465
dailyUsage[user]++;
466
}
467
468
return bytes32(0);
469
}
470
471
function postRelayedCall(
472
bytes calldata context,
473
bool success,
474
uint256 actualCharge,
475
bytes32 preRetVal
476
) external override {
477
// Post-processing if needed
478
}
479
480
// Admin functions
481
function addCredits(address user, uint256 amount) external onlyRole(DEFAULT_ADMIN_ROLE) {
482
userCredits[user] += amount;
483
}
484
485
function addRelayer(address relayer) external onlyRole(DEFAULT_ADMIN_ROLE) {
486
grantRole(RELAYER_ROLE, relayer);
487
}
488
489
// Example app function that can be called without gas
490
function doSomething(uint256 value) external {
491
address user = _msgSender();
492
// App logic here
493
}
494
}
495
```
496
497
## GSN Deployment and Setup
498
499
### Setting up GSN for Development
500
501
```javascript
502
// Deploy GSN contracts (typically done once per network)
503
const RelayHub = await ethers.getContractFactory("RelayHub");
504
const relayHub = await RelayHub.deploy();
505
506
// Deploy your GSN-enabled contract
507
const MyGSNApp = await ethers.getContractFactory("MyGSNApp");
508
const app = await MyGSNApp.deploy();
509
510
// Fund the contract on RelayHub
511
await relayHub.depositFor(app.address, { value: ethers.utils.parseEther("1") });
512
```
513
514
### Client-Side GSN Integration
515
516
```javascript
517
import { GSNProvider, RelayProvider } from '@opengsn/provider';
518
519
// Configure GSN provider
520
const gsnProvider = RelayProvider.newProvider({
521
provider: window.ethereum,
522
config: {
523
relayHubAddress: "0x...", // RelayHub contract address
524
stakeManagerAddress: "0x...", // StakeManager contract address
525
paymasterAddress: "0x...", // Your paymaster contract address
526
}
527
});
528
529
// Use with web3 or ethers
530
const web3 = new Web3(gsnProvider);
531
const contract = new web3.eth.Contract(abi, contractAddress);
532
533
// This transaction will be gasless for the user
534
await contract.methods.myFunction().send({ from: userAddress });
535
```