0
# Financial Primitives
1
2
OpenZeppelin Contracts provides essential financial mechanisms for handling cryptocurrency payments, distributions, and vesting schedules in smart contracts.
3
4
## Core Imports
5
6
Import financial contracts using Solidity import statements:
7
8
```solidity
9
import "@openzeppelin/contracts/finance/PaymentSplitter.sol";
10
import "@openzeppelin/contracts/finance/VestingWallet.sol";
11
```
12
13
## Capabilities
14
15
### Payment Splitter
16
17
Splits incoming payments among multiple payees according to their share allocation. Supports both Ether and ERC20 token distributions.
18
19
```solidity { .api }
20
contract PaymentSplitter is Context {
21
constructor(address[] memory payees, uint256[] memory shares_);
22
23
function totalShares() public view returns (uint256);
24
function totalReleased() public view returns (uint256);
25
function totalReleased(IERC20 token) public view returns (uint256);
26
function shares(address account) public view returns (uint256);
27
function released(address account) public view returns (uint256);
28
function released(IERC20 token, address account) public view returns (uint256);
29
function payee(uint256 index) public view returns (address);
30
function releasable(address account) public view returns (uint256);
31
function releasable(IERC20 token, address account) public view returns (uint256);
32
function release(address payable account) public virtual;
33
function release(IERC20 token, address account) public virtual;
34
}
35
```
36
37
#### Events
38
39
```solidity { .api }
40
event PayeeAdded(address account, uint256 shares);
41
event PaymentReleased(address to, uint256 amount);
42
event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);
43
event PaymentReceived(address from, uint256 amount);
44
```
45
46
#### Usage Example
47
48
```solidity
49
pragma solidity ^0.8.0;
50
51
import "@openzeppelin/contracts/finance/PaymentSplitter.sol";
52
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
53
54
contract RevenueSharing is PaymentSplitter {
55
constructor(
56
address[] memory founders,
57
uint256[] memory founderShares
58
) PaymentSplitter(founders, founderShares) {}
59
60
// Contract automatically receives and splits payments
61
receive() external payable {
62
// PaymentSplitter handles the payment tracking
63
}
64
65
function releaseAll() external {
66
for (uint256 i = 0; i < payee(i) != address(0); i++) {
67
address payeeAddress = payee(i);
68
if (releasable(payeeAddress) > 0) {
69
release(payable(payeeAddress));
70
}
71
}
72
}
73
74
function releaseToken(IERC20 token) external {
75
for (uint256 i = 0; i < payee(i) != address(0); i++) {
76
address payeeAddress = payee(i);
77
if (releasable(token, payeeAddress) > 0) {
78
release(token, payeeAddress);
79
}
80
}
81
}
82
}
83
```
84
85
### Vesting Wallet
86
87
Implements token vesting functionality with linear release schedules over a specified duration.
88
89
```solidity { .api }
90
contract VestingWallet is Context {
91
constructor(address beneficiaryAddress, uint64 startTimestamp, uint64 durationSeconds);
92
93
function beneficiary() public view virtual returns (address);
94
function start() public view virtual returns (uint256);
95
function duration() public view virtual returns (uint256);
96
function released() public view virtual returns (uint256);
97
function released(address token) public view virtual returns (uint256);
98
function releasable() public view virtual returns (uint256);
99
function releasable(address token) public view virtual returns (uint256);
100
function release() public virtual;
101
function release(address token) public virtual;
102
function vestedAmount(uint64 timestamp) public view virtual returns (uint256);
103
function vestedAmount(address token, uint64 timestamp) public view virtual returns (uint256);
104
}
105
```
106
107
#### Events
108
109
```solidity { .api }
110
event EtherReleased(uint256 amount);
111
event ERC20Released(address indexed token, uint256 amount);
112
```
113
114
#### Usage Example
115
116
```solidity
117
pragma solidity ^0.8.0;
118
119
import "@openzeppelin/contracts/finance/VestingWallet.sol";
120
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
121
122
contract TeamVesting {
123
mapping(address => VestingWallet) public vestingWallets;
124
IERC20 public token;
125
126
constructor(IERC20 _token) {
127
token = _token;
128
}
129
130
function createVestingSchedule(
131
address beneficiary,
132
uint256 amount,
133
uint64 startTime,
134
uint64 duration
135
) external {
136
// Create vesting wallet for team member
137
VestingWallet vestingWallet = new VestingWallet(
138
beneficiary,
139
startTime,
140
duration
141
);
142
143
vestingWallets[beneficiary] = vestingWallet;
144
145
// Transfer tokens to vesting wallet
146
token.transfer(address(vestingWallet), amount);
147
}
148
149
function releaseVestedTokens(address beneficiary) external {
150
VestingWallet wallet = vestingWallets[beneficiary];
151
require(address(wallet) != address(0), "No vesting schedule");
152
153
wallet.release(address(token));
154
}
155
156
function getVestingInfo(address beneficiary)
157
external
158
view
159
returns (
160
uint256 totalAmount,
161
uint256 releasedAmount,
162
uint256 releasableAmount,
163
uint256 startTime,
164
uint256 duration
165
)
166
{
167
VestingWallet wallet = vestingWallets[beneficiary];
168
require(address(wallet) != address(0), "No vesting schedule");
169
170
totalAmount = token.balanceOf(address(wallet)) + wallet.released(address(token));
171
releasedAmount = wallet.released(address(token));
172
releasableAmount = wallet.releasable(address(token));
173
startTime = wallet.start();
174
duration = wallet.duration();
175
}
176
}
177
```
178
179
## Advanced Financial Patterns
180
181
### Escrow Pattern with Payment Splitter
182
183
```solidity
184
pragma solidity ^0.8.0;
185
186
import "@openzeppelin/contracts/finance/PaymentSplitter.sol";
187
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
188
189
contract EscrowPaymentSplitter is PaymentSplitter, ReentrancyGuard {
190
enum EscrowState { Pending, Released, Disputed }
191
192
struct Escrow {
193
uint256 amount;
194
EscrowState state;
195
uint256 releaseTime;
196
}
197
198
mapping(bytes32 => Escrow) public escrows;
199
address public arbiter;
200
201
constructor(
202
address[] memory payees,
203
uint256[] memory shares_,
204
address _arbiter
205
) PaymentSplitter(payees, shares_) {
206
arbiter = _arbiter;
207
}
208
209
function createEscrow(bytes32 escrowId, uint256 releaseTime) external payable {
210
require(escrows[escrowId].amount == 0, "Escrow already exists");
211
require(msg.value > 0, "Must send funds");
212
213
escrows[escrowId] = Escrow({
214
amount: msg.value,
215
state: EscrowState.Pending,
216
releaseTime: releaseTime
217
});
218
}
219
220
function releaseEscrow(bytes32 escrowId) external nonReentrant {
221
Escrow storage escrow = escrows[escrowId];
222
require(escrow.state == EscrowState.Pending, "Escrow not pending");
223
require(block.timestamp >= escrow.releaseTime, "Release time not reached");
224
225
escrow.state = EscrowState.Released;
226
227
// Funds automatically split according to PaymentSplitter shares
228
(bool success, ) = address(this).call{value: escrow.amount}("");
229
require(success, "Failed to release funds");
230
}
231
}
232
```
233
234
### Milestone-Based Vesting
235
236
```solidity
237
pragma solidity ^0.8.0;
238
239
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
240
import "@openzeppelin/contracts/access/Ownable.sol";
241
242
contract MilestoneVesting is Ownable {
243
struct Milestone {
244
uint256 amount;
245
bool completed;
246
bool released;
247
}
248
249
struct VestingSchedule {
250
address beneficiary;
251
uint256 totalAmount;
252
uint256 releasedAmount;
253
Milestone[] milestones;
254
}
255
256
mapping(address => VestingSchedule) public vestingSchedules;
257
IERC20 public token;
258
259
event MilestoneCompleted(address indexed beneficiary, uint256 milestoneIndex);
260
event TokensReleased(address indexed beneficiary, uint256 amount);
261
262
constructor(IERC20 _token) {
263
token = _token;
264
}
265
266
function createVestingSchedule(
267
address beneficiary,
268
uint256[] memory milestoneAmounts
269
) external onlyOwner {
270
require(vestingSchedules[beneficiary].beneficiary == address(0), "Schedule exists");
271
272
VestingSchedule storage schedule = vestingSchedules[beneficiary];
273
schedule.beneficiary = beneficiary;
274
275
uint256 totalAmount = 0;
276
for (uint256 i = 0; i < milestoneAmounts.length; i++) {
277
schedule.milestones.push(Milestone({
278
amount: milestoneAmounts[i],
279
completed: false,
280
released: false
281
}));
282
totalAmount += milestoneAmounts[i];
283
}
284
285
schedule.totalAmount = totalAmount;
286
token.transferFrom(msg.sender, address(this), totalAmount);
287
}
288
289
function completeMilestone(address beneficiary, uint256 milestoneIndex) external onlyOwner {
290
VestingSchedule storage schedule = vestingSchedules[beneficiary];
291
require(schedule.beneficiary != address(0), "No vesting schedule");
292
require(milestoneIndex < schedule.milestones.length, "Invalid milestone");
293
require(!schedule.milestones[milestoneIndex].completed, "Already completed");
294
295
schedule.milestones[milestoneIndex].completed = true;
296
emit MilestoneCompleted(beneficiary, milestoneIndex);
297
}
298
299
function releaseMilestone(uint256 milestoneIndex) external {
300
VestingSchedule storage schedule = vestingSchedules[msg.sender];
301
require(schedule.beneficiary == msg.sender, "Not beneficiary");
302
require(milestoneIndex < schedule.milestones.length, "Invalid milestone");
303
304
Milestone storage milestone = schedule.milestones[milestoneIndex];
305
require(milestone.completed, "Milestone not completed");
306
require(!milestone.released, "Already released");
307
308
milestone.released = true;
309
schedule.releasedAmount += milestone.amount;
310
311
token.transfer(msg.sender, milestone.amount);
312
emit TokensReleased(msg.sender, milestone.amount);
313
}
314
}
315
```
316
317
## Financial Best Practices
318
319
1. **Payment Splitting**: Use PaymentSplitter for automatic revenue distribution
320
2. **Vesting Security**: Implement proper access controls for vesting schedule modifications
321
3. **Reentrancy Protection**: Use ReentrancyGuard for contracts handling payments
322
4. **Token Support**: Design contracts to support both Ether and ERC20 tokens
323
5. **Release Mechanisms**: Implement both automatic and manual release options
324
6. **Audit Considerations**: Financial contracts require thorough security audits
325
326
## Integration Patterns
327
328
### With Governance
329
330
```solidity
331
// Combine vesting with governance tokens
332
contract GovernanceVesting is VestingWallet {
333
constructor(
334
address beneficiary,
335
uint64 startTimestamp,
336
uint64 durationSeconds
337
) VestingWallet(beneficiary, startTimestamp, durationSeconds) {}
338
339
// Override to delegate voting power while tokens are vesting
340
function _afterTokenTransfer(address token, uint256 amount) internal {
341
// Delegate voting power to beneficiary even while tokens are locked
342
IVotes(token).delegate(beneficiary());
343
}
344
}
345
```
346
347
### With Access Control
348
349
```solidity
350
// Multi-signature controlled payment splitter
351
contract MultiSigPaymentSplitter is PaymentSplitter, AccessControl {
352
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
353
354
modifier onlyAdmin() {
355
require(hasRole(ADMIN_ROLE, msg.sender), "Not admin");
356
_;
357
}
358
359
function emergencyWithdraw(address token, uint256 amount) external onlyAdmin {
360
// Emergency withdrawal mechanism
361
}
362
}
363
```
364
365
## Error Handling
366
367
Financial contracts may revert with various errors:
368
369
- **PaymentSplitter**: `PaymentSplitter__InvalidShares()`, `PaymentSplitter__NoPayeesProvided()`
370
- **VestingWallet**: No specific custom errors, uses standard transfer/balance checks
371
- **General**: Standard ERC20 transfer errors, access control violations, reentrancy protection