or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

access-control.mdcontract-presets.mdcryptography-math.mdgsn-support.mdindex.mdintrospection.mdpayment-mechanisms.mdproxy-patterns.mdsecurity-utilities.mdtoken-standards.md

payment-mechanisms.mddocs/

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

```