Skip to content

Commit 7e8e3a0

Browse files
committed
comments, renaming
1 parent 3123496 commit 7e8e3a0

File tree

4 files changed

+221
-157
lines changed

4 files changed

+221
-157
lines changed

src/PaymentsGateway.sol

Lines changed: 171 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
// SPDX-License-Identifier: UNLICENSED
1+
// SPDX-License-Identifier: Apache-2.0
22
pragma solidity ^0.8.22;
33

4+
/// @author thirdweb
5+
46
import "@openzeppelin/contracts/access/Ownable.sol";
57
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
68
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
@@ -12,21 +14,70 @@ import { ECDSA } from "./lib/ECDSA.sol";
1214
contract PaymentsGateway is EIP712, Ownable, ReentrancyGuard {
1315
using ECDSA for bytes32;
1416

15-
error PaymentsGatewayMismatchedValue(uint256 expected, uint256 actual);
16-
error PaymentsGatewayInvalidAmount(uint256 amount);
17-
error PaymentsGatewayVerificationFailed();
18-
error PaymentsGatewayFailedToForward();
19-
error PaymentsGatewayRequestExpired(uint256 expirationTimestamp);
17+
/*///////////////////////////////////////////////////////////////
18+
State, constants, structs
19+
//////////////////////////////////////////////////////////////*/
20+
21+
bytes32 private constant PAYOUTINFO_TYPEHASH =
22+
keccak256("PayoutInfo(bytes32 clientId,address payoutAddress,uint256 feeBPS)");
23+
bytes32 private constant REQUEST_TYPEHASH =
24+
keccak256(
25+
"PayRequest(bytes32 clientId,bytes32 transactionId,address tokenAddress,uint256 tokenAmount,uint256 expirationTimestamp,PayoutInfo[] payouts,address forwardAddress,bytes data)PayoutInfo(bytes32 clientId,address payoutAddress,uint256 feeBPS)"
26+
);
27+
address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
28+
29+
/// @dev Mapping from pay request UID => whether the pay request is processed.
30+
mapping(bytes32 => bool) private processed;
2031

21-
event TransferStart(
32+
/**
33+
* @notice Info of fee payout recipients.
34+
*
35+
* @param clientId ClientId of fee recipient
36+
* @param payoutAddress Recipient address
37+
* @param feeBPS The fee basis points to be charged. Max = 10000 (10000 = 100%, 1000 = 10%)
38+
*/
39+
struct PayoutInfo {
40+
bytes32 clientId;
41+
address payable payoutAddress;
42+
uint256 feeBPS;
43+
}
44+
45+
/**
46+
* @notice The body of a request to purchase tokens.
47+
*
48+
* @param clientId Thirdweb clientId for logging attribution data
49+
* @param transactionId Acts as a uid and a key to lookup associated swap provider
50+
* @param tokenAddress Address of the currency used for purchase
51+
* @param tokenAmount Currency amount being sent
52+
* @param expirationTimestamp The unix timestamp at which the request expires
53+
* @param payouts Array of Payout struct - containing fee recipients' info
54+
* @param forwardAddress Address of swap provider contract
55+
* @param data Calldata for swap provider
56+
*/
57+
struct PayRequest {
58+
bytes32 clientId;
59+
bytes32 transactionId;
60+
address tokenAddress;
61+
uint256 tokenAmount;
62+
uint256 expirationTimestamp;
63+
PayoutInfo[] payouts;
64+
address payable forwardAddress;
65+
bytes data;
66+
}
67+
68+
/*///////////////////////////////////////////////////////////////
69+
Events
70+
//////////////////////////////////////////////////////////////*/
71+
72+
event TokenPurchaseInitiated(
2273
bytes32 indexed clientId,
2374
address indexed sender,
2475
bytes32 transactionId,
2576
address tokenAddress,
2677
uint256 tokenAmount
2778
);
2879

29-
event TransferEnd(
80+
event TokenPurchaseCompleted(
3081
bytes32 indexed clientId,
3182
address indexed receiver,
3283
bytes32 transactionId,
@@ -43,38 +94,27 @@ contract PaymentsGateway is EIP712, Ownable, ReentrancyGuard {
4394
uint256 feeBPS
4495
);
4596

46-
event OperatorChanged(address indexed previousOperator, address indexed newOperator);
97+
/*///////////////////////////////////////////////////////////////
98+
Errors
99+
//////////////////////////////////////////////////////////////*/
47100

48-
struct PayoutInfo {
49-
bytes32 clientId;
50-
address payable payoutAddress;
51-
uint256 feeBPS;
52-
}
53-
struct PayRequest {
54-
bytes32 clientId;
55-
bytes32 transactionId;
56-
address tokenAddress;
57-
uint256 tokenAmount;
58-
uint256 expirationTimestamp;
59-
PayoutInfo[] payouts;
60-
address payable forwardAddress;
61-
bytes data;
62-
}
63-
64-
bytes32 private constant PAYOUTINFO_TYPEHASH =
65-
keccak256("PayoutInfo(bytes32 clientId,address payoutAddress,uint256 feeBPS)");
66-
bytes32 private constant REQUEST_TYPEHASH =
67-
keccak256(
68-
"PayRequest(bytes32 clientId,bytes32 transactionId,address tokenAddress,uint256 tokenAmount,uint256 expirationTimestamp,PayoutInfo[] payouts,address forwardAddress,bytes data)PayoutInfo(bytes32 clientId,address payoutAddress,uint256 feeBPS)"
69-
);
70-
address private constant NATIVE_TOKEN_ADDRESS = 0x0000000000000000000000000000000000000000;
101+
error PaymentsGatewayMismatchedValue(uint256 expected, uint256 actual);
102+
error PaymentsGatewayInvalidAmount(uint256 amount);
103+
error PaymentsGatewayVerificationFailed();
104+
error PaymentsGatewayFailedToForward();
105+
error PaymentsGatewayRequestExpired(uint256 expirationTimestamp);
71106

72-
/// @dev Mapping from pay request UID => whether the pay request is processed.
73-
mapping(bytes32 => bool) private processed;
107+
/*///////////////////////////////////////////////////////////////
108+
Constructor
109+
//////////////////////////////////////////////////////////////*/
74110

75111
constructor(address contractOwner) Ownable(contractOwner) {}
76112

77-
/* some bridges may refund need a way to get funds back to user */
113+
/*///////////////////////////////////////////////////////////////
114+
External / public functions
115+
//////////////////////////////////////////////////////////////*/
116+
117+
/// @notice some bridges may refund need a way to get funds back to user
78118
function withdrawTo(
79119
address tokenAddress,
80120
uint256 tokenAmount,
@@ -91,101 +131,19 @@ contract PaymentsGateway is EIP712, Ownable, ReentrancyGuard {
91131
withdrawTo(tokenAddress, tokenAmount, payable(msg.sender));
92132
}
93133

94-
function _isTokenERC20(address tokenAddress) private pure returns (bool) {
95-
return tokenAddress != NATIVE_TOKEN_ADDRESS;
96-
}
97-
98-
function _isTokenNative(address tokenAddress) private pure returns (bool) {
99-
return tokenAddress == NATIVE_TOKEN_ADDRESS;
100-
}
101-
102-
function _calculateFee(uint256 amount, uint256 feeBPS) private pure returns (uint256) {
103-
uint256 feeAmount = (amount * feeBPS) / 10_000;
104-
return feeAmount;
105-
}
106-
107-
function _distributeFees(
108-
address tokenAddress,
109-
uint256 tokenAmount,
110-
PayoutInfo[] calldata payouts
111-
) private returns (uint256) {
112-
uint256 totalFeeAmount = 0;
113-
114-
for (uint32 payeeIdx = 0; payeeIdx < payouts.length; payeeIdx++) {
115-
uint256 feeAmount = _calculateFee(tokenAmount, payouts[payeeIdx].feeBPS);
116-
totalFeeAmount += feeAmount;
117-
118-
emit FeePayout(
119-
payouts[payeeIdx].clientId,
120-
msg.sender,
121-
payouts[payeeIdx].payoutAddress,
122-
tokenAddress,
123-
feeAmount,
124-
payouts[payeeIdx].feeBPS
125-
);
126-
if (_isTokenNative(tokenAddress)) {
127-
SafeTransferLib.safeTransferETH(payouts[payeeIdx].payoutAddress, feeAmount);
128-
} else {
129-
SafeTransferLib.safeTransferFrom(tokenAddress, msg.sender, payouts[payeeIdx].payoutAddress, feeAmount);
130-
}
131-
}
132-
133-
if (totalFeeAmount > tokenAmount) {
134-
revert PaymentsGatewayMismatchedValue(totalFeeAmount, tokenAmount);
135-
}
136-
return totalFeeAmount;
137-
}
138-
139-
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
140-
name = "PaymentsGateway";
141-
version = "1";
142-
}
143-
144-
function _hashPayoutInfo(PayoutInfo[] calldata payouts) private pure returns (bytes32) {
145-
bytes32[] memory payoutsHashes = new bytes32[](payouts.length);
146-
for (uint i = 0; i < payouts.length; i++) {
147-
payoutsHashes[i] = keccak256(
148-
abi.encode(PAYOUTINFO_TYPEHASH, payouts[i].clientId, payouts[i].payoutAddress, payouts[i].feeBPS)
149-
);
150-
}
151-
return keccak256(abi.encodePacked(payoutsHashes));
152-
}
153-
154-
function _verifyTransferStart(PayRequest calldata req, bytes calldata signature) private view returns (bool) {
155-
bytes32 payoutsHash = _hashPayoutInfo(req.payouts);
156-
bytes32 structHash = keccak256(
157-
abi.encode(
158-
REQUEST_TYPEHASH,
159-
req.clientId,
160-
req.transactionId,
161-
req.tokenAddress,
162-
req.tokenAmount,
163-
req.expirationTimestamp,
164-
payoutsHash,
165-
req.forwardAddress,
166-
keccak256(req.data)
167-
)
168-
);
169-
170-
bytes32 digest = _hashTypedData(structHash);
171-
address recovered = digest.recover(signature);
172-
bool valid = recovered == owner() && !processed[req.transactionId];
173-
174-
return valid;
175-
}
176-
177134
/**
178-
The purpose of buyToken is to be the entrypoint for all thirdweb pay swap / bridge
135+
@notice
136+
The purpose of initiateTokenPurchase is to be the entrypoint for all thirdweb pay swap / bridge
179137
transactions. This function will allow us to standardize the logging and fee splitting across all providers.
180138
181139
Requirements:
182140
1. Verify the parameters are the same parameters sent from thirdweb pay service by requiring a backend signature
183141
2. Log transfer start allowing us to link onchain and offchain data
184-
3. distribute the fees to all the payees (thirdweb, developer, swap provider??)
142+
3. distribute the fees to all the payees (thirdweb, developer, swap provider (?))
185143
4. forward the user funds to the swap provider (forwardAddress)
186144
*/
187145

188-
function buyToken(PayRequest calldata req, bytes calldata signature) external payable nonReentrant {
146+
function initiateTokenPurchase(PayRequest calldata req, bytes calldata signature) external payable nonReentrant {
189147
// verify amount
190148
if (req.tokenAmount == 0) {
191149
revert PaymentsGatewayInvalidAmount(req.tokenAmount);
@@ -244,11 +202,12 @@ contract PaymentsGateway is EIP712, Ownable, ReentrancyGuard {
244202
}
245203
}
246204

247-
emit TransferStart(req.clientId, msg.sender, req.transactionId, req.tokenAddress, req.tokenAmount);
205+
emit TokenPurchaseInitiated(req.clientId, msg.sender, req.transactionId, req.tokenAddress, req.tokenAmount);
248206
}
249207

250208
/**
251-
The purpose of endTransfer is to provide a forwarding contract call
209+
@notice
210+
The purpose of completeTokenPurchase is to provide a forwarding contract call
252211
on the destination chain. For LiFi (swap provider), they can only guarantee the toAmount
253212
if we use a contract call. This allows us to call the endTransfer function and forward the
254213
funds to the end user.
@@ -257,7 +216,7 @@ contract PaymentsGateway is EIP712, Ownable, ReentrancyGuard {
257216
1. Log the transfer end
258217
2. forward the user funds
259218
*/
260-
function endTransfer(
219+
function completeTokenPurchase(
261220
bytes32 clientId,
262221
bytes32 transactionId,
263222
address tokenAddress,
@@ -281,6 +240,93 @@ contract PaymentsGateway is EIP712, Ownable, ReentrancyGuard {
281240
SafeTransferLib.safeTransferETH(receiverAddress, tokenAmount);
282241
}
283242

284-
emit TransferEnd(clientId, receiverAddress, transactionId, tokenAddress, tokenAmount);
243+
emit TokenPurchaseCompleted(clientId, receiverAddress, transactionId, tokenAddress, tokenAmount);
244+
}
245+
246+
/*///////////////////////////////////////////////////////////////
247+
Internal functions
248+
//////////////////////////////////////////////////////////////*/
249+
250+
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
251+
name = "PaymentsGateway";
252+
version = "1";
253+
}
254+
255+
function _hashPayoutInfo(PayoutInfo[] calldata payouts) private pure returns (bytes32) {
256+
bytes32[] memory payoutsHashes = new bytes32[](payouts.length);
257+
for (uint i = 0; i < payouts.length; i++) {
258+
payoutsHashes[i] = keccak256(
259+
abi.encode(PAYOUTINFO_TYPEHASH, payouts[i].clientId, payouts[i].payoutAddress, payouts[i].feeBPS)
260+
);
261+
}
262+
return keccak256(abi.encodePacked(payoutsHashes));
263+
}
264+
265+
function _distributeFees(
266+
address tokenAddress,
267+
uint256 tokenAmount,
268+
PayoutInfo[] calldata payouts
269+
) private returns (uint256) {
270+
uint256 totalFeeAmount = 0;
271+
272+
for (uint32 payeeIdx = 0; payeeIdx < payouts.length; payeeIdx++) {
273+
uint256 feeAmount = _calculateFee(tokenAmount, payouts[payeeIdx].feeBPS);
274+
totalFeeAmount += feeAmount;
275+
276+
emit FeePayout(
277+
payouts[payeeIdx].clientId,
278+
msg.sender,
279+
payouts[payeeIdx].payoutAddress,
280+
tokenAddress,
281+
feeAmount,
282+
payouts[payeeIdx].feeBPS
283+
);
284+
if (_isTokenNative(tokenAddress)) {
285+
SafeTransferLib.safeTransferETH(payouts[payeeIdx].payoutAddress, feeAmount);
286+
} else {
287+
SafeTransferLib.safeTransferFrom(tokenAddress, msg.sender, payouts[payeeIdx].payoutAddress, feeAmount);
288+
}
289+
}
290+
291+
if (totalFeeAmount > tokenAmount) {
292+
revert PaymentsGatewayMismatchedValue(totalFeeAmount, tokenAmount);
293+
}
294+
return totalFeeAmount;
295+
}
296+
297+
function _verifyTransferStart(PayRequest calldata req, bytes calldata signature) private view returns (bool) {
298+
bytes32 payoutsHash = _hashPayoutInfo(req.payouts);
299+
bytes32 structHash = keccak256(
300+
abi.encode(
301+
REQUEST_TYPEHASH,
302+
req.clientId,
303+
req.transactionId,
304+
req.tokenAddress,
305+
req.tokenAmount,
306+
req.expirationTimestamp,
307+
payoutsHash,
308+
req.forwardAddress,
309+
keccak256(req.data)
310+
)
311+
);
312+
313+
bytes32 digest = _hashTypedData(structHash);
314+
address recovered = digest.recover(signature);
315+
bool valid = recovered == owner() && !processed[req.transactionId];
316+
317+
return valid;
318+
}
319+
320+
function _isTokenERC20(address tokenAddress) private pure returns (bool) {
321+
return tokenAddress != NATIVE_TOKEN_ADDRESS;
322+
}
323+
324+
function _isTokenNative(address tokenAddress) private pure returns (bool) {
325+
return tokenAddress == NATIVE_TOKEN_ADDRESS;
326+
}
327+
328+
function _calculateFee(uint256 amount, uint256 feeBPS) private pure returns (uint256) {
329+
uint256 feeAmount = (amount * feeBPS) / 10_000;
330+
return feeAmount;
285331
}
286332
}

0 commit comments

Comments
 (0)