0
# Payment Mechanisms
1
2
OpenZeppelin provides secure payment splitting, pull payment patterns, and escrow mechanisms for handling payments and fund management in smart contracts. These utilities enable automatic payment distribution, safe withdrawal patterns, and conditional fund release.
3
4
## Capabilities
5
6
### PaymentSplitter - Automatic Payment Distribution
7
8
Contract that splits payments among multiple payees proportionally to their shares in the contract.
9
10
```solidity { .api }
11
/**
12
* This contract allows to split Ether payments among a group of accounts
13
*/
14
contract PaymentSplitter is Context {
15
/**
16
* Creates an instance of PaymentSplitter where each account in payees is assigned the number of shares at the matching position in the shares array
17
*/
18
constructor(address[] memory payees, uint256[] memory shares_);
19
20
/**
21
* Getter for the total shares held by payees
22
*/
23
function totalShares() public view returns (uint256);
24
25
/**
26
* Getter for the total amount of Ether already released
27
*/
28
function totalReleased() public view returns (uint256);
29
30
/**
31
* Getter for the amount of shares held by an account
32
*/
33
function shares(address account) public view returns (uint256);
34
35
/**
36
* Getter for the amount of Ether already released to a payee
37
*/
38
function released(address account) public view returns (uint256);
39
40
/**
41
* Getter for the address of the payee number index
42
*/
43
function payee(uint256 index) public view returns (address);
44
45
/**
46
* Triggers a transfer to account of the amount of Ether they are owed
47
*/
48
function release(address payable account) public virtual;
49
50
/**
51
* Triggers a transfer to account of the amount of token they are owed
52
*/
53
function release(IERC20 token, address account) public virtual;
54
55
/**
56
* Getter for the total amount of token already released
57
*/
58
function totalReleased(IERC20 token) public view returns (uint256);
59
60
/**
61
* Getter for the amount of token already released to a payee
62
*/
63
function released(IERC20 token, address account) public view returns (uint256);
64
65
event PayeeAdded(address account, uint256 shares);
66
event PaymentReleased(address to, uint256 amount);
67
event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);
68
event PaymentReceived(address from, uint256 amount);
69
}
70
```
71
72
**Usage Example:**
73
74
```solidity
75
import "@openzeppelin/contracts/finance/PaymentSplitter.sol";
76
77
contract RevenueShare {
78
PaymentSplitter public splitter;
79
80
constructor() {
81
address[] memory payees = new address[](3);
82
payees[0] = 0x1234567890123456789012345678901234567890;
83
payees[1] = 0x2345678901234567890123456789012345678901;
84
payees[2] = 0x3456789012345678901234567890123456789012;
85
86
uint256[] memory shares = new uint256[](3);
87
shares[0] = 50; // 50% share
88
shares[1] = 30; // 30% share
89
shares[2] = 20; // 20% share
90
91
splitter = new PaymentSplitter(payees, shares);
92
}
93
94
// Forward payments to splitter
95
receive() external payable {
96
(bool success, ) = address(splitter).call{value: msg.value}("");
97
require(success, "Payment failed");
98
}
99
100
function claimPayment(address payable account) external {
101
splitter.release(account);
102
}
103
}
104
```
105
106
### PullPayment - Safe Withdrawal Pattern
107
108
Contract that implements the pull payment pattern for secure fund withdrawals.
109
110
```solidity { .api }
111
/**
112
* Simple implementation of a pull-payment strategy
113
*/
114
abstract contract PullPayment {
115
/**
116
* Returns the payments owed to an address
117
*/
118
function payments(address dest) public view returns (uint256);
119
120
/**
121
* Withdraw accumulated payments
122
*/
123
function withdrawPayments(address payable payee) public virtual;
124
125
/**
126
* Called by the payer to store the sent amount as credit to be pulled
127
*/
128
function _asyncTransfer(address dest, uint256 amount) internal;
129
}
130
```
131
132
**Usage Example:**
133
134
```solidity
135
import "@openzeppelin/contracts/security/PullPayment.sol";
136
137
contract Auction is PullPayment {
138
address public highestBidder;
139
uint256 public highestBid;
140
mapping(address => uint256) public bids;
141
142
function bid() external payable {
143
require(msg.value > highestBid, "Bid too low");
144
145
if (highestBidder != address(0)) {
146
// Instead of directly sending funds, use pull payment pattern
147
_asyncTransfer(highestBidder, highestBid);
148
}
149
150
highestBidder = msg.sender;
151
highestBid = msg.value;
152
bids[msg.sender] = msg.value;
153
}
154
155
function withdraw() external {
156
withdrawPayments(payable(msg.sender));
157
}
158
}
159
```
160
161
### Escrow - Basic Escrow Contract
162
163
Basic escrow contract that holds funds until release conditions are met.
164
165
```solidity { .api }
166
/**
167
* Base escrow contract, holds funds designated for a payee until they withdraw them
168
*/
169
contract Escrow is Ownable {
170
/**
171
* Returns the amount deposited for a payee
172
*/
173
function depositsOf(address payee) public view returns (uint256);
174
175
/**
176
* Stores the sent amount as credit to be withdrawn
177
*/
178
function deposit(address payee) public payable virtual onlyOwner;
179
180
/**
181
* Withdraw accumulated balance for a payee
182
*/
183
function withdraw(address payable payee) public virtual;
184
185
event Deposited(address indexed payee, uint256 weiAmount);
186
event Withdrawn(address indexed payee, uint256 weiAmount);
187
}
188
```
189
190
### ConditionalEscrow - Conditional Release Escrow
191
192
Escrow that only allows withdrawal if a condition is met.
193
194
```solidity { .api }
195
/**
196
* Base abstract escrow to only allow withdrawal if a condition is met
197
*/
198
abstract contract ConditionalEscrow is Escrow {
199
/**
200
* Returns whether an address is allowed to withdraw their funds
201
*/
202
function withdrawalAllowed(address payee) public view virtual returns (bool);
203
204
/**
205
* Withdraw accumulated balance for a payee, only if withdrawal is allowed
206
*/
207
function withdraw(address payable payee) public virtual override;
208
}
209
```
210
211
### RefundEscrow - Refundable Escrow Pattern
212
213
Escrow that supports both beneficiary withdrawal and refund scenarios.
214
215
```solidity { .api }
216
/**
217
* Escrow that holds funds for a beneficiary, deposited from multiple parties
218
*/
219
contract RefundEscrow is ConditionalEscrow {
220
enum State { Active, Refunding, Closed }
221
222
State private _state;
223
address payable private _beneficiary;
224
225
/**
226
* Constructor
227
*/
228
constructor(address payable beneficiary);
229
230
/**
231
* Returns the current state of the escrow
232
*/
233
function state() public view returns (State);
234
235
/**
236
* Returns the beneficiary of the escrow
237
*/
238
function beneficiary() public view returns (address);
239
240
/**
241
* Stores funds that may later be refunded
242
*/
243
function deposit(address refundee) public payable override;
244
245
/**
246
* Allows for the beneficiary to withdraw their funds
247
*/
248
function close() public onlyOwner;
249
250
/**
251
* Allows for refunds to take place
252
*/
253
function enableRefunds() public onlyOwner;
254
255
/**
256
* Withdraws the beneficiary's funds
257
*/
258
function beneficiaryWithdraw() public;
259
260
/**
261
* Returns whether refundees can withdraw their deposits
262
*/
263
function withdrawalAllowed(address) public view override returns (bool);
264
265
event RefundsClosed();
266
event RefundsEnabled();
267
}
268
```
269
270
**Usage Example:**
271
272
```solidity
273
import "@openzeppelin/contracts/utils/escrow/RefundEscrow.sol";
274
275
contract Crowdsale {
276
RefundEscrow private escrow;
277
uint256 public goal;
278
uint256 public raised;
279
bool public finalized;
280
281
constructor(address payable beneficiary, uint256 _goal) {
282
escrow = new RefundEscrow(beneficiary);
283
goal = _goal;
284
}
285
286
function contribute() external payable {
287
require(!finalized, "Crowdsale finalized");
288
289
raised += msg.value;
290
escrow.deposit{value: msg.value}(msg.sender);
291
}
292
293
function finalize() external {
294
require(!finalized, "Already finalized");
295
finalized = true;
296
297
if (raised >= goal) {
298
escrow.close();
299
escrow.beneficiaryWithdraw();
300
} else {
301
escrow.enableRefunds();
302
}
303
}
304
305
function claimRefund() external {
306
require(finalized, "Not finalized");
307
escrow.withdraw(payable(msg.sender));
308
}
309
}
310
```
311
312
## Payment Patterns
313
314
### Revenue Sharing with Multiple Tokens
315
316
```solidity
317
import "@openzeppelin/contracts/finance/PaymentSplitter.sol";
318
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
319
320
contract MultiTokenRevenue {
321
PaymentSplitter public splitter;
322
IERC20[] public supportedTokens;
323
324
constructor(
325
address[] memory payees,
326
uint256[] memory shares,
327
IERC20[] memory tokens
328
) {
329
splitter = new PaymentSplitter(payees, shares);
330
supportedTokens = tokens;
331
}
332
333
function distributeTokens() external {
334
for (uint256 i = 0; i < supportedTokens.length; i++) {
335
IERC20 token = supportedTokens[i];
336
uint256 balance = token.balanceOf(address(this));
337
338
if (balance > 0) {
339
token.transfer(address(splitter), balance);
340
}
341
}
342
}
343
344
function claimAllTokens(address account) external {
345
// Claim ETH
346
splitter.release(payable(account));
347
348
// Claim all supported tokens
349
for (uint256 i = 0; i < supportedTokens.length; i++) {
350
splitter.release(supportedTokens[i], account);
351
}
352
}
353
}
354
```
355
356
### Milestone-Based Escrow
357
358
```solidity
359
import "@openzeppelin/contracts/utils/escrow/ConditionalEscrow.sol";
360
361
contract MilestoneEscrow is ConditionalEscrow {
362
mapping(address => bool) public milestoneCompleted;
363
mapping(address => address) public milestoneValidator;
364
365
function setMilestoneValidator(address payee, address validator) external onlyOwner {
366
milestoneValidator[payee] = validator;
367
}
368
369
function completeMilestone(address payee) external {
370
require(msg.sender == milestoneValidator[payee], "Not authorized validator");
371
milestoneCompleted[payee] = true;
372
}
373
374
function withdrawalAllowed(address payee) public view override returns (bool) {
375
return milestoneCompleted[payee];
376
}
377
}
378
```
379
380
### Vesting Escrow
381
382
```solidity
383
import "@openzeppelin/contracts/utils/escrow/ConditionalEscrow.sol";
384
385
contract VestingEscrow is ConditionalEscrow {
386
mapping(address => uint256) public vestingStart;
387
mapping(address => uint256) public vestingDuration;
388
389
function setVesting(
390
address payee,
391
uint256 startTime,
392
uint256 duration
393
) external onlyOwner {
394
vestingStart[payee] = startTime;
395
vestingDuration[payee] = duration;
396
}
397
398
function withdrawalAllowed(address payee) public view override returns (bool) {
399
return block.timestamp >= vestingStart[payee] + vestingDuration[payee];
400
}
401
402
function vestedAmount(address payee) public view returns (uint256) {
403
uint256 totalDeposit = depositsOf(payee);
404
405
if (block.timestamp < vestingStart[payee]) {
406
return 0;
407
} else if (block.timestamp >= vestingStart[payee] + vestingDuration[payee]) {
408
return totalDeposit;
409
} else {
410
uint256 elapsed = block.timestamp - vestingStart[payee];
411
return (totalDeposit * elapsed) / vestingDuration[payee];
412
}
413
}
414
}
415
```