or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

access-control.mdcontract-presets.mdcryptography-math.mdgsn-support.mdindex.mdintrospection.mdpayment-mechanisms.mdproxy-patterns.mdsecurity-utilities.mdtoken-standards.md

gsn-support.mddocs/

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

```