Skip to content

Commit 89af118

Browse files
committed
fix(docs): update import paths and improve type imports in gasless FXRP payment examples
1 parent 329526a commit 89af118

File tree

4 files changed

+188
-23
lines changed

4 files changed

+188
-23
lines changed

docs/fxrp/token-interactions/04-gasless-fxrp-payments.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ sidebar_position: 4
99
---
1010

1111
import CodeBlock from "@theme/CodeBlock";
12-
import GaslessPaymentForwarder from "!!raw-loader!/examples/developer-hub-javascript/fxrp-gasless/GaslessPaymentForwarder.sol";
12+
import GaslessPaymentForwarder from "!!raw-loader!/examples/developer-hub-solidity/GaslessPaymentForwarder.sol";
1313
import PaymentUtils from "!!raw-loader!/examples/developer-hub-javascript/fxrp-gasless/payment.ts";
1414
import Relayer from "!!raw-loader!/examples/developer-hub-javascript/fxrp-gasless/relayer.ts";
1515
import DeployScript from "!!raw-loader!/examples/developer-hub-javascript/fxrp-gasless/deploy.ts";

examples/developer-hub-javascript/fxrp-gasless/payment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
// 1. Import the necessary libraries
10-
import { ethers, Contract, Wallet, Provider } from "ethers";
10+
import { ethers, Contract, type Wallet, type Provider } from "ethers";
1111
import { erc20Abi, type TypedDataDomain, type TypedData } from "viem";
1212
import { GaslessPaymentForwarder__factory } from "../typechain-types/factories/contracts/GaslessPaymentForwarder__factory";
1313

examples/developer-hub-javascript/fxrp-gasless/relayer.ts

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
type TypedDataDomain,
2222
type TypedData,
2323
} from "viem";
24-
import express, { Request, Response } from "express";
24+
import express, { type Request, type Response } from "express";
2525
import cors from "cors";
2626
import "dotenv/config";
2727
import type { GaslessPaymentForwarder } from "../typechain-types/contracts/GaslessPaymentForwarder";
@@ -222,28 +222,18 @@ export class GaslessRelayer {
222222
}
223223

224224
// Execute the payment
225-
let tx: ethers.ContractTransactionResponse;
226-
try {
227-
tx = await this.forwarder.executePayment(
228-
from,
229-
to,
230-
amount,
231-
fee,
232-
deadline,
233-
signature,
234-
{ gasLimit },
235-
);
236-
} catch (sendError) {
237-
throw sendError;
238-
}
225+
const tx = await this.forwarder.executePayment(
226+
from,
227+
to,
228+
amount,
229+
fee,
230+
deadline,
231+
signature,
232+
{ gasLimit },
233+
);
239234

240235
// Wait for confirmation
241-
let receipt: ethers.TransactionReceipt | null;
242-
try {
243-
receipt = await tx.wait();
244-
} catch (waitError) {
245-
throw waitError;
246-
}
236+
const receipt = await tx.wait();
247237

248238
return {
249239
success: true,
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.25;
3+
4+
// OpenZeppelin: SafeERC20, ECDSA, EIP712, Ownable, ReentrancyGuard
5+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6+
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
7+
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
8+
import "@openzeppelin/contracts/access/Ownable.sol";
9+
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
10+
11+
// Flare Contract Registry and FAsset interface
12+
import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/flare/ContractRegistry.sol";
13+
import {IAssetManager} from "@flarenetwork/flare-periphery-contracts/flare/IAssetManager.sol";
14+
import {IFAsset} from "@flarenetwork/flare-periphery-contracts/flare/IFAsset.sol";
15+
16+
/**
17+
* @title GaslessPaymentForwarder
18+
* @notice Enables gasless FXRP transfers using EIP-712 signed meta-transactions
19+
* @dev Users sign payment requests off-chain, relayers submit them on-chain.
20+
* FXRP from Flare Contract Registry: getAssetManagerFXRP() -> fAsset().
21+
*
22+
* Flow: (1) User approves this contract to spend FXRP once.
23+
* (2) User signs PaymentRequest off-chain. (3) Relayer calls executePayment().
24+
* (4) Contract verifies signature and executes transfer.
25+
*/
26+
contract GaslessPaymentForwarder is EIP712, Ownable, ReentrancyGuard {
27+
// 1. Define the necessary libraries and contract variables
28+
using SafeERC20 for IFAsset;
29+
using ECDSA for bytes32;
30+
31+
mapping(address => uint256) public nonces; // replay protection per sender
32+
mapping(address => bool) public authorizedRelayers; // relayer allowlist
33+
34+
uint256 public relayerFee; // default fee (owner-configurable)
35+
36+
// EIP-712 type hash for PaymentRequest
37+
bytes32 public constant PAYMENT_REQUEST_TYPEHASH =
38+
keccak256(
39+
"PaymentRequest(address from,address to,uint256 amount,uint256 fee,uint256 nonce,uint256 deadline)"
40+
);
41+
42+
// 2. Contract events
43+
event PaymentExecuted(
44+
address indexed from,
45+
address indexed to,
46+
uint256 amount,
47+
uint256 fee,
48+
uint256 nonce
49+
);
50+
event RelayerAuthorized(address indexed relayer, bool authorized); // relayer allowlist changed
51+
event RelayerFeeUpdated(uint256 newFee); // default fee changed
52+
53+
// 3. Custom errors
54+
error InvalidSignature(); // signer != from
55+
error ExpiredRequest(); // block.timestamp > deadline
56+
error InvalidNonce(); // nonce mismatch (replay)
57+
error UnauthorizedRelayer(); // caller not in allowlist
58+
error InsufficientAllowance(); // user approval < amount + fee
59+
error ZeroAddress(); // zero address passed
60+
61+
// 4. Constructor that initializes the relayer fee
62+
constructor(
63+
uint256 _relayerFee
64+
) EIP712("GaslessPaymentForwarder", "1") Ownable(msg.sender) {
65+
relayerFee = _relayerFee; // set initial default fee
66+
}
67+
68+
// 5. Returns FXRP token from Flare Contract Registry
69+
function fxrp() public view returns (IFAsset) {
70+
IAssetManager assetManager = ContractRegistry.getAssetManagerFXRP();
71+
return IFAsset(address(assetManager.fAsset())); // FXRP token from registry
72+
}
73+
74+
// 6. Execute a gasless payment
75+
function executePayment(
76+
address from,
77+
address to,
78+
uint256 amount,
79+
uint256 fee,
80+
uint256 deadline,
81+
bytes calldata signature
82+
) external nonReentrant {
83+
if (block.timestamp > deadline) revert ExpiredRequest(); // validate deadline
84+
85+
uint256 currentNonce = nonces[from];
86+
87+
// 7. Hash the payment request
88+
bytes32 structHash = keccak256(
89+
abi.encode(
90+
PAYMENT_REQUEST_TYPEHASH,
91+
from,
92+
to,
93+
amount,
94+
fee,
95+
currentNonce,
96+
deadline
97+
)
98+
);
99+
100+
// 8. Recover the signer from the hash
101+
bytes32 hash = _hashTypedDataV4(structHash);
102+
address signer = hash.recover(signature);
103+
104+
// 9. Check if the signer is the from address
105+
if (signer != from) revert InvalidSignature();
106+
107+
nonces[from] = currentNonce + 1; // increment nonce (prevents replay)
108+
109+
IFAsset _fxrp = fxrp();
110+
111+
// 10. Check if the allowance is sufficient
112+
uint256 totalAmount = amount + fee;
113+
if (_fxrp.allowance(from, address(this)) < totalAmount) {
114+
revert InsufficientAllowance();
115+
}
116+
117+
// 11. Transfer the amount to the recipient
118+
_fxrp.safeTransferFrom(from, to, amount);
119+
120+
// 12. Transfer the fee to the relayer
121+
if (fee > 0) {
122+
_fxrp.safeTransferFrom(from, msg.sender, fee);
123+
}
124+
125+
emit PaymentExecuted(from, to, amount, fee, currentNonce); // log success
126+
}
127+
128+
// 13. Views for off-chain signing / validation
129+
function getNonce(address account) external view returns (uint256) {
130+
return nonces[account]; // current nonce for off-chain signing
131+
}
132+
133+
// Get the EIP-712 domain separator
134+
function getDomainSeparator() external view returns (bytes32) {
135+
return _domainSeparatorV4(); // EIP-712 domain separator
136+
}
137+
138+
// Compute the hash of a payment request for signing
139+
function getPaymentRequestHash(
140+
address from,
141+
address to,
142+
uint256 amount,
143+
uint256 fee,
144+
uint256 nonce,
145+
uint256 deadline
146+
) external view returns (bytes32) {
147+
bytes32 structHash = keccak256(
148+
abi.encode(
149+
PAYMENT_REQUEST_TYPEHASH,
150+
from,
151+
to,
152+
amount,
153+
fee,
154+
nonce,
155+
deadline
156+
)
157+
);
158+
return _hashTypedDataV4(structHash); // full EIP-712 typed-data hash
159+
}
160+
161+
// 15. Set relayer authorization status
162+
function setRelayerAuthorization(
163+
address relayer,
164+
bool authorized
165+
) external onlyOwner {
166+
authorizedRelayers[relayer] = authorized; // update allowlist
167+
emit RelayerAuthorized(relayer, authorized);
168+
}
169+
170+
// 15. Update the default relayer fee
171+
function setRelayerFee(uint256 _relayerFee) external onlyOwner {
172+
relayerFee = _relayerFee; // update default fee
173+
emit RelayerFeeUpdated(_relayerFee);
174+
}
175+
}

0 commit comments

Comments
 (0)