Skip to content

Commit 329526a

Browse files
committed
feat(docs): enhance gasless FXRP payments guide with detailed examples and improved clarity
1 parent 1f3dd4d commit 329526a

File tree

5 files changed

+587
-167
lines changed

5 files changed

+587
-167
lines changed

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

Lines changed: 113 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ title: Gasless FXRP Payments
33
tags: [intermediate, fassets, fxrp, meta-transactions]
44
slug: gasless-fxrp-payments
55
description: Transfer FXRP without gas using EIP-712 signed meta-transactions and a relayer.
6-
keywords: [fassets, fxrp, gasless, meta-transactions, eip-712, flare-network, relayer]
6+
keywords:
7+
[fassets, fxrp, gasless, meta-transactions, eip-712, flare-network, relayer]
78
sidebar_position: 4
89
---
910

@@ -14,13 +15,15 @@ import Relayer from "!!raw-loader!/examples/developer-hub-javascript/fxrp-gasles
1415
import DeployScript from "!!raw-loader!/examples/developer-hub-javascript/fxrp-gasless/deploy.ts";
1516
import ExampleUsage from "!!raw-loader!/examples/developer-hub-javascript/fxrp-gasless/example-usage.ts";
1617

17-
This guide describes how to create a system for gasless FXRP (FAsset) transfers on Flare.
18-
Users sign payment requests off-chain with [EIP-712](https://eips.ethereum.org/EIPS/eip-712), and a relayer submits them on-chain and pays gas on their behalf.
18+
This guide explains how to set up gasless FXRP (FAsset) transfers on Flare.
19+
Users sign payment requests off-chain with [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data, and a relayer submits them on-chain and pays gas on their behalf.
1920

20-
### EIP-3009 and EIP-712
21+
### Standards explained
2122

22-
[EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) (Transfer with Authorization) extends [ERC-20](https://eips.ethereum.org/EIPS/eip-20) with meta-transactions. The token holder signs an authorization off-chain, and a relayer executes the transfer on-chain and pays gas.
23-
This guide uses a **custom [EIP-712](https://eips.ethereum.org/EIPS/eip-712) payment message** and a forwarder contract instead of EIP-3009 on the token itself.
23+
[EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) (Transfer with Authorization) extends [ERC-20](https://eips.ethereum.org/EIPS/eip-20) with meta-transactions.
24+
The token holder signs an authorization off-chain, and a relayer executes the transfer on-chain and pays gas.
25+
26+
This guide uses a custom [EIP-712](https://eips.ethereum.org/EIPS/eip-712) payment message and a forwarder contract instead of EIP-3009 on the token itself.
2427
The forwarder pulls FXRP from the user (after a one-time approval) and sends it to the recipient.
2528
The relayer pays gas and can charge a fee in FXRP.
2629

@@ -39,33 +42,34 @@ Add the following files to your project:
3942

4043
### Gasless Payment Forwarder Contract
4144

42-
Save as `contracts/GaslessPaymentForwarder.sol`:
45+
Save as `contracts/GaslessPaymentForwarder.sol` to create the forwarder smart contract that will be used to execute gasless payments.
4346

4447
<details>
45-
<summary>View GaslessPaymentForwarder.sol</summary>
48+
<summary>View `contracts/GaslessPaymentForwarder.sol`</summary>
4649

4750
<CodeBlock language="solidity" title="contracts/GaslessPaymentForwarder.sol">
4851
{GaslessPaymentForwarder}
4952
</CodeBlock>
5053

51-
</details>
52-
53-
#### How the contract works
54+
**How the contract works**
5455

5556
1. Define libraries and state.
5657
2. Define events.
57-
3. Define errors.
58-
4. Implement the contract constructor that sets the EIP-712 domain, owner, and initial relayer fee.
59-
5. Implement the `fxrp` function that returns the FXRP token from the Flare registry.
60-
6. Define the `executePayment` function that executes a gasless payment.
61-
7. Hash the request (type hash + from, to, amount, fee, nonce, deadline) and get the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) digest.
62-
8. Recover the signer from the signature with ECDSA.
63-
9. Require signer from the payment request.
64-
10. Require allowance is sufficient for the total amount (amount + fee).
65-
11. Transfer amount from sender to recipient.
66-
12. Transfer fee from sender to relayer (msg.sender); emit PaymentExecuted.
67-
13. Define the view functions to get the nonce, domain separator, and payment request hash.
68-
14. Define the functions to set the relayer authorization and fee.
58+
3. Defines errors.
59+
4. Implements the contract constructor that sets the EIP-712 domain, owner, and initial relayer fee.
60+
5. Implements the `fxrp` function that returns the FXRP token from the Flare registry.
61+
6. Defines the `executePayment` function that executes a gasless payment.
62+
7. Hashes the request (type hash + from, to, amount, fee, nonce, deadline) and gets the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) digest.
63+
8. Recovers the signer from the signature with ECDSA.
64+
9. Requires the signer of the payment request.
65+
10. Requires a sufficient allowance for the total amount (amount + fee).
66+
11. Transfers the amount from the sender to the recipient.
67+
12. Transfers fee from sender to relayer (msg.sender); emits `PaymentExecuted`.
68+
13. Defines the view functions to get the nonce, domain separator, and payment request hash.
69+
14. Defines the function to set the relater authorization.
70+
15. Define the function to set the relayer fee.
71+
72+
</details>
6973

7074
Run the following command to compile the contract and generate the typechain-types:
7175

@@ -75,37 +79,72 @@ npx hardhat compile
7579

7680
### Utilities
7781

78-
Save as `utils/payment.ts`.
82+
Save as `utils/payment.ts` to create the utility functions that will be used to create and sign payment requests.
83+
84+
:::info
7985

80-
It uses the `typechain-types` generated by the previous command.
86+
This utility uses the `typechain-types` generated by the previous command.
87+
88+
:::
8189

8290
<details>
83-
<summary>View utils/payment.ts</summary>
91+
<summary>View `utils/payment.ts`</summary>
8492

8593
<CodeBlock language="typescript" title="utils/payment.ts">
8694
{PaymentUtils}
8795
</CodeBlock>
8896

97+
**Code Breakdown**
98+
99+
1. Import the necessary libraries.
100+
2. Define the needed constants and EIP-712 types.
101+
3. Define types for sign params, payment request, approval result, and user status.
102+
4. Parse human-readable amount to raw bigint with decimals.
103+
5. Format the raw amount to a readable string with decimals.
104+
6. Get FXRP decimals from the forwarder and token contract.
105+
7. Get the current nonce for a user from the forwarder.
106+
8. Get the minimum relayer fee from the forwarder.
107+
9. Sign the payment message with EIP-712 and return the signature.
108+
10. Create a full payment request: fetch nonce and fee, set deadline, sign, and return payload for the relayer.
109+
11. Approve the forwarder to spend FXRP.
110+
12. Check user balance, allowance, nonce, and whether approval is needed.
111+
89112
</details>
90113

91114
### Relayer Service
92115

93-
This is a relayer service that will submit payment requests to the Flare blockchain and pay gas on behalf of the user.
116+
This is a relayer service that submits payment requests to the Flare blockchain and pays gas on behalf of the user.
94117

95-
Save as `relayer/index.ts` to start the relayer service:
118+
Save as `relayer/index.ts` to start the relayer service.
96119

97120
<details>
98-
<summary>View relayer/index.ts</summary>
121+
<summary>View `relayer/index.ts`</summary>
99122

100123
<CodeBlock language="typescript" title="relayer/index.ts">
101124
{Relayer}
102125
</CodeBlock>
103126

127+
**Code Breakdown**
128+
129+
1. Import necessary libraries.
130+
2. Define EIP-712 domain, payment request types, and other network configurations.
131+
3. Define `RelayerConfig`, `PaymentRequest`, and `ExecuteResult` types.
132+
4. Implement the `GaslessRelayer` class to interact with the forwarder contract.
133+
5. Execute gasless payment: normalize and validate request, recover signer with EIP-712, validate request (balance, allowance, deadline), staticCall, then recheck nonce, estimate gas with buffer, call executePayment, wait for receipt, return result.
134+
6. Validate request: check deadline against chain time, check sender FXRP balance and allowance, check fee meets minimum.
135+
7. Get nonce for an address from the forwarder contract.
136+
8. Get the minimum relayer fee from the forwarder contract.
137+
9. Get FXRP token decimals from the forwarder and token contract.
138+
10. Get the relayer FLR balance for gas.
139+
11. Start Express server.
140+
12. Main function to read environment variables, create a relayer, log FLR balance, and start the server.
141+
13. Run the main function to start the relayer service.
142+
104143
</details>
105144

106145
### Deploy Script
107146

108-
Save as `scripts/deploy.ts` to deploy the [`GaslessPaymentForwarder`](/fxrp/token-interactions/gasless-fxrp-payments#gasless-payment-forwarder-contract) contract:
147+
Save as `scripts/deploy.ts` to deploy the [`GaslessPaymentForwarder`](/fxrp/token-interactions/gasless-fxrp-payments#gasless-payment-forwarder-contract) contract.
109148

110149
<details>
111150
<summary>View scripts/deploy.ts</summary>
@@ -114,11 +153,23 @@ Save as `scripts/deploy.ts` to deploy the [`GaslessPaymentForwarder`](/fxrp/toke
114153
{DeployScript}
115154
</CodeBlock>
116155

156+
**Code Breakdown**
157+
158+
1. Import necessary libraries.
159+
2. Define default relayer fee constant in FXRP base units.
160+
3. Get deployer signer and balance; log address and FLR balance.
161+
4. Deploy `GaslessPaymentForwarder` contract with the relayer fee (FXRP comes from the Flare Contract Registry) and wait for deployment, and get the contract address.
162+
5. Get FXRP token address and decimals from the forwarder.
163+
Format fee for display.
164+
Log deployment summary (network, contract, FXRP, owner, fee).
165+
6. Verify contract on block explorer.
166+
7. Run main function.
167+
117168
</details>
118169

119170
### Example Flow
120171

121-
Use `scripts/example-usage.ts` to run the example flow:
172+
Use `scripts/example-usage.ts` to run the example flow.
122173

123174
<details>
124175
<summary>View scripts/example-usage.ts</summary>
@@ -127,28 +178,32 @@ Use `scripts/example-usage.ts` to run the example flow:
127178
{ExampleUsage}
128179
</CodeBlock>
129180

130-
</details>
181+
**Code Breakdown**
131182

132-
The example flow tests the system and executes the following steps:
183+
1. Import the necessary libraries.
184+
2. Read configuration from environment.
185+
3. Validate the configuration.
186+
4. Create a provider and a wallet.
187+
5. Check user FXRP balance and allowance.
188+
6. Approve the forwarder to spend FXRP.
189+
7. Create a payment request and sign it.
190+
8. Submit the payment request to the relayer.
191+
9. Call the main function to run the example flow.
133192

134-
1. Check the user's balance and allowance.
135-
2. Approve the forwarder to spend FXRP if needed.
136-
3. Create a payment request.
137-
4. Submit the payment request to the relayer.
138-
5. Check the payment status.
193+
</details>
139194

140195
## Configuration
141196

142197
Create a `.env` file in your project root with the following variables:
143198

144-
| Variable | Description |
145-
|----------|-------------|
146-
| `PRIVATE_KEY` | Deployer private key (for deployment) |
147-
| `RELAYER_PRIVATE_KEY` | Relayer wallet (pays gas, receives FXRP fees) |
148-
| `USER_PRIVATE_KEY` | User wallet for testing |
149-
| `FORWARDER_ADDRESS` | Deployed contract address (set after deploy) |
150-
| `RPC_URL` | Flare network RPC (e.g. Coston2 testnet) |
151-
| `RELAYER_URL` | Relayer HTTP URL (e.g. `http://localhost:3000`) |
199+
| Variable | Description |
200+
| --------------------- | ----------------------------------------------- |
201+
| `PRIVATE_KEY` | Deployer private key (for deployment) |
202+
| `RELAYER_PRIVATE_KEY` | Relayer wallet (pays gas, receives FXRP fees) |
203+
| `USER_PRIVATE_KEY` | User wallet for testing |
204+
| `FORWARDER_ADDRESS` | Deployed contract address (set after deploy) |
205+
| `RPC_URL` | Flare network RPC (e.g. Coston2 testnet) |
206+
| `RELAYER_URL` | Relayer HTTP URL (e.g. `http://localhost:3000`) |
152207

153208
## Deploy and run
154209

@@ -182,11 +237,11 @@ npx ts-node relayer/index.ts
182237

183238
**Main relayer backend endpoints:**
184239

185-
| Method | Path | Description |
186-
|--------|------|-------------|
187-
| POST | `/execute` | Execute single payment |
188-
| GET | `/nonce/:address` | Get nonce for address |
189-
| GET | `/fee` | Get the relayer fee |
240+
| Method | Path | Description |
241+
| ------ | ----------------- | ---------------------- |
242+
| POST | `/execute` | Execute single payment |
243+
| GET | `/nonce/:address` | Get nonce for address |
244+
| GET | `/fee` | Get the relayer fee |
190245

191246
### Run the example Flow
192247

@@ -198,11 +253,11 @@ npx ts-node scripts/example-usage.ts
198253

199254
This script will execute the following steps:
200255

201-
1. Check the user's balance and allowance.
202-
2. Approve the forwarder to spend FXRP if needed.
203-
3. Create a payment request.
204-
4. Submit the payment request to the relayer.
205-
5. Check the payment status.
256+
1. Checks the user's balance and allowance.
257+
2. Approves the forwarder to spend FXRP if needed.
258+
3. Creates a payment request.
259+
4. Submits the payment request to the relayer.
260+
5. Checks the payment status.
206261

207262
You should see the following output:
208263

@@ -248,8 +303,9 @@ Step 4: Submitting to relayer...
248303
</details>
249304

250305
:::tip[Next steps]
306+
251307
- [Get FXRP token address](/fxrp/token-interactions/fxrp-address) for use in your app.
252308
- [Swap USDT0 to FXRP](/fxrp/token-interactions/usdt0-fxrp-swap) to acquire FXRP.
253309
- Review [FXRP operational parameters](/fxrp/parameters).
254310
- [Gasless USD₮0 Transfers](/network/guides/gasless-usdt0-transfers) — Similar pattern for USDT0 on Flare.
255-
:::
311+
:::

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

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,93 @@
11
/**
2-
* Deploy GaslessPaymentForwarder
2+
* Deploy the GaslessPaymentForwarder contract
33
*
4-
* Copy to scripts/deploy.ts. Usage: npx hardhat run scripts/deploy.ts --network coston2
4+
* Usage: npm run deploy:coston2
55
*/
66

7+
// 1. Import the necessary libraries
78
import hre from "hardhat";
89
import { erc20Abi } from "viem";
910
import type { GaslessPaymentForwarder } from "../typechain-types/contracts/GaslessPaymentForwarder";
1011
import type { IERC20Metadata } from "../typechain-types/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata";
1112

13+
// 2. Define the default relayer fee (FXRP token base units)
1214
const DEFAULT_RELAYER_FEE = 10000n;
1315

1416
async function main(): Promise<string> {
1517
const network = hre.network.name;
1618
console.log(`\nDeploying GaslessPaymentForwarder to ${network}...`);
1719

20+
// 3. Get deployer account and balance
1821
const [deployer] = await hre.ethers.getSigners();
1922
console.log(`Deployer address: ${deployer.address}`);
2023

2124
const balance = await hre.ethers.provider.getBalance(deployer.address);
2225
console.log(`Deployer balance: ${hre.ethers.formatEther(balance)} FLR`);
2326

24-
const GaslessPaymentForwarder = await hre.ethers.getContractFactory("GaslessPaymentForwarder");
25-
const forwarder = (await GaslessPaymentForwarder.deploy(DEFAULT_RELAYER_FEE)) as unknown as GaslessPaymentForwarder;
27+
console.log(`\nFXRP address will be fetched from Flare Contract Registry`);
28+
console.log(
29+
` ContractRegistry.getAssetManagerFXRP() -> AssetManager.fAsset()`,
30+
);
31+
console.log(`Relayer fee: ${DEFAULT_RELAYER_FEE} (base units)`);
32+
33+
// 4. Deploy the GaslessPaymentForwarder contract (FXRP fetched from registry)
34+
const GaslessPaymentForwarder = await hre.ethers.getContractFactory(
35+
"GaslessPaymentForwarder",
36+
);
37+
const forwarder = (await GaslessPaymentForwarder.deploy(
38+
DEFAULT_RELAYER_FEE,
39+
)) as unknown as GaslessPaymentForwarder;
2640

2741
await forwarder.waitForDeployment();
2842
const forwarderAddress = await forwarder.getAddress();
2943

44+
// 5. Get FXRP token address from forwarder (for display)
3045
const fxrpAddress = await forwarder.fxrp();
3146
const fxrp = new hre.ethers.Contract(
3247
fxrpAddress,
3348
erc20Abi as unknown as string[],
34-
hre.ethers.provider
49+
hre.ethers.provider,
3550
) as unknown as IERC20Metadata;
3651
const decimals = await fxrp.decimals();
37-
const relayerFeeFormatted = hre.ethers.formatUnits(DEFAULT_RELAYER_FEE, decimals);
52+
const relayerFeeFormatted = hre.ethers.formatUnits(
53+
DEFAULT_RELAYER_FEE,
54+
decimals,
55+
);
3856

3957
console.log(`\nGaslessPaymentForwarder deployed to: ${forwarderAddress}`);
58+
console.log("\n--- Deployment Summary ---");
59+
console.log(`Network: ${network}`);
60+
console.log(`Contract: ${forwarderAddress}`);
4061
console.log(`FXRP Token: ${fxrpAddress}`);
41-
console.log(`Relayer Fee: ${relayerFeeFormatted} FXRP`);
62+
console.log(`Owner: ${deployer.address}`);
63+
console.log(
64+
`Relayer Fee: ${relayerFeeFormatted} FXRP (${DEFAULT_RELAYER_FEE} base units)`,
65+
);
66+
67+
// 6. Verify contract on block explorer
68+
console.log("\nVerifying contract...");
69+
try {
70+
await hre.run("verify:verify", {
71+
address: forwarderAddress,
72+
constructorArguments: [DEFAULT_RELAYER_FEE.toString()],
73+
});
74+
console.log("Contract verified successfully.");
75+
} catch (err) {
76+
const msg = err instanceof Error ? err.message : String(err);
77+
if (msg.includes("Already Verified") || msg.includes("already verified")) {
78+
console.log("Contract already verified.");
79+
} else {
80+
console.warn("Verification failed:", msg);
81+
console.log(
82+
`\nTo verify manually: npx hardhat verify --network ${network} ${forwarderAddress} "${DEFAULT_RELAYER_FEE}"`,
83+
);
84+
}
85+
}
4286

4387
return forwarderAddress;
4488
}
4589

90+
// 7. Main entry point for the deployment script
4691
main()
4792
.then(() => process.exit(0))
4893
.catch((error) => {

0 commit comments

Comments
 (0)