@gooddollar/goodcollective-contracts 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,9 +31,11 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
31
31
  mapping(address => PoolRegistry) public registry;
32
32
  mapping(bytes32 => DirectPaymentsPool) public projectIdToControlPool;
33
33
 
34
+ address public feeRecipient;
35
+ uint32 public feeBps;
36
+
34
37
  modifier onlyProjectOwnerOrNon(string memory projectId) {
35
38
  DirectPaymentsPool controlPool = projectIdToControlPool[keccak256(bytes(projectId))];
36
- console.log("control:%s sender:%s %s", address(controlPool), msg.sender);
37
39
  // console.log("result %s", controlPool.hasRole(controlPool.DEFAULT_ADMIN_ROLE(), msg.sender));
38
40
  if (address(controlPool) != address(0)) {
39
41
  if (controlPool.hasRole(controlPool.DEFAULT_ADMIN_ROLE(), msg.sender) == false) {
@@ -55,16 +57,32 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
55
57
 
56
58
  function _authorizeUpgrade(address _impl) internal virtual override onlyRole(DEFAULT_ADMIN_ROLE) {}
57
59
 
58
- function initialize(address _owner, address _dpimpl, address _nftimpl) external initializer {
60
+ function initialize(
61
+ address _owner,
62
+ address _dpimpl,
63
+ address _nftimpl,
64
+ address _feeRecipient,
65
+ uint32 _feeBps
66
+ ) external initializer {
59
67
  nextNftType = 1;
60
68
  impl = _dpimpl;
61
- bytes memory initCall = abi.encodeWithSelector(ProvableNFT.initialize.selector, "DirectPayments NFT", "DPNFT");
69
+ bytes memory initCall = abi.encodeWithSelector(ProvableNFT.initialize.selector, "GoodCollective NFT", "GC-NFT");
62
70
  nft = ProvableNFT(address(new ERC1967Proxy(_nftimpl, initCall)));
71
+ feeRecipient = _feeRecipient;
72
+ feeBps = _feeBps;
63
73
 
64
74
  nft.grantRole(DEFAULT_ADMIN_ROLE, _owner);
65
75
  _setupRole(DEFAULT_ADMIN_ROLE, _owner);
66
76
  }
67
77
 
78
+ //TODO: implement a pool that's auto upgradeable using beacon method
79
+ function createBeaconPool(
80
+ string memory _projectId,
81
+ string memory _ipfs,
82
+ DirectPaymentsPool.PoolSettings memory _settings,
83
+ DirectPaymentsPool.SafetyLimits memory _limits
84
+ ) external onlyProjectOwnerOrNon(_projectId) returns (DirectPaymentsPool pool) {}
85
+
68
86
  function createPool(
69
87
  string memory _projectId,
70
88
  string memory _ipfs,
@@ -74,7 +92,13 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
74
92
  //TODO: add check if msg.sender is whitelisted
75
93
 
76
94
  _settings.nftType = nextNftType;
77
- bytes memory initCall = abi.encodeWithSelector(DirectPaymentsPool.initialize.selector, nft, _settings, _limits);
95
+ bytes memory initCall = abi.encodeWithSelector(
96
+ DirectPaymentsPool.initialize.selector,
97
+ nft,
98
+ _settings,
99
+ _limits,
100
+ address(this)
101
+ );
78
102
  pool = DirectPaymentsPool(address(new ERC1967Proxy(impl, initCall)));
79
103
 
80
104
  nft.grantRole(nft.getManagerRole(nextNftType), _settings.manager);
@@ -105,4 +129,9 @@ contract DirectPaymentsFactory is AccessControlUpgradeable, UUPSUpgradeable {
105
129
  impl = _impl;
106
130
  emit UpdatedImpl(_impl);
107
131
  }
132
+
133
+ function setFeeInfo(address _feeRecipient, uint32 _feeBps) external onlyRole(DEFAULT_ADMIN_ROLE) {
134
+ feeBps = _feeBps;
135
+ feeRecipient = _feeRecipient;
136
+ }
108
137
  }
@@ -55,7 +55,8 @@ contract DirectPaymentsPool is
55
55
  event PoolLimitsChanged(SafetyLimits limits);
56
56
  event MemberAdded(address member);
57
57
  event MemberRemoved(address member);
58
- event RewardClaimed(uint256 indexed tokenId, uint256 totalRewards);
58
+ event EventRewardClaimed(uint256 indexed tokenId, ProvableNFT.EventData eventData);
59
+ event NFTClaimed(uint256 indexed tokenId, uint256 totalRewards);
59
60
 
60
61
  // Define functions
61
62
  struct PoolSettings {
@@ -64,7 +65,7 @@ contract DirectPaymentsPool is
64
65
  uint128[] rewardPerEvent;
65
66
  address manager;
66
67
  IMembersValidator membersValidator;
67
- IIdentityV2 uniqunessValidator;
68
+ IIdentityV2 uniquenessValidator;
68
69
  IERC20Upgradeable rewardToken;
69
70
  }
70
71
 
@@ -90,7 +91,7 @@ contract DirectPaymentsPool is
90
91
  mapping(address => bool) public members;
91
92
  mapping(address => LimitsData) public memberLimits;
92
93
  LimitsData public globalLimits;
93
- address public createdBy;
94
+ DirectPaymentsFactory public registry;
94
95
 
95
96
  /// @custom:oz-upgrades-unsafe-allow constructor
96
97
  constructor(ISuperfluid _host, ISwapRouter _swapRouter) GoodCollectiveSuperApp(_host, _swapRouter) {}
@@ -101,6 +102,10 @@ contract DirectPaymentsPool is
101
102
  */
102
103
  function _authorizeUpgrade(address impl) internal virtual override {}
103
104
 
105
+ function getRegistry() public view override returns (DirectPaymentsFactory) {
106
+ return DirectPaymentsFactory(registry);
107
+ }
108
+
104
109
  /**
105
110
  * @dev Initializes the contract with the given settings and limits.
106
111
  * @param _nft The ProvableNFT contract address.
@@ -110,9 +115,10 @@ contract DirectPaymentsPool is
110
115
  function initialize(
111
116
  ProvableNFT _nft,
112
117
  PoolSettings memory _settings,
113
- SafetyLimits memory _limits
118
+ SafetyLimits memory _limits,
119
+ DirectPaymentsFactory _registry
114
120
  ) external initializer {
115
- createdBy = msg.sender;
121
+ registry = _registry;
116
122
  settings = _settings;
117
123
  limits = _limits;
118
124
  nft = _nft;
@@ -123,7 +129,7 @@ contract DirectPaymentsPool is
123
129
  }
124
130
 
125
131
  function upgradeToLatest(bytes memory data) external payable virtual onlyProxy {
126
- address impl = DirectPaymentsFactory(createdBy).impl();
132
+ address impl = DirectPaymentsFactory(registry).impl();
127
133
  _authorizeUpgrade(impl);
128
134
  _upgradeToAndCallUUPS(impl, data, false);
129
135
  }
@@ -171,10 +177,11 @@ contract DirectPaymentsPool is
171
177
  if (totalRewards > rewardsBalance) revert NO_BALANCE();
172
178
  rewardsBalance -= totalRewards;
173
179
  _sendReward(_data.events[i].contributers, uint128(reward * _data.events[i].quantity));
180
+ emit EventRewardClaimed(_nftId, _data.events[i]);
174
181
  }
175
182
  }
176
183
 
177
- emit RewardClaimed(_nftId, totalRewards);
184
+ emit NFTClaimed(_nftId, totalRewards);
178
185
  }
179
186
 
180
187
  /**
@@ -279,8 +286,8 @@ contract DirectPaymentsPool is
279
286
  */
280
287
 
281
288
  function addMember(address member, bytes memory extraData) external {
282
- if (address(settings.uniqunessValidator) != address(0)) {
283
- address rootAddress = settings.uniqunessValidator.getWhitelistedRoot(member);
289
+ if (address(settings.uniquenessValidator) != address(0)) {
290
+ address rootAddress = settings.uniquenessValidator.getWhitelistedRoot(member);
284
291
  if (rootAddress == address(0)) revert NOT_WHITELISTED(member);
285
292
  }
286
293
 
@@ -6,21 +6,45 @@ import { SuperAppBaseFlow } from "./SuperAppBaseFlow.sol";
6
6
  import { ISuperfluid, ISuperToken, SuperAppDefinitions } from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
7
7
  import { ISuperGoodDollar } from "@gooddollar/goodprotocol/contracts/token/superfluid/ISuperGoodDollar.sol";
8
8
  import { SuperTokenV1Library } from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol";
9
+ import { CFAv1Library, IConstantFlowAgreementV1 } from "@superfluid-finance/ethereum-contracts/contracts/apps/CFAv1Library.sol";
9
10
 
10
11
  import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
11
12
  import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
12
13
 
14
+ import "../DirectPayments/DirectPaymentsFactory.sol";
15
+
13
16
  // import "hardhat/console.sol";
14
17
 
15
18
  abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
19
+ int96 public constant MIN_FLOW_RATE = 386e9;
20
+
16
21
  using SuperTokenV1Library for ISuperToken;
22
+ using CFAv1Library for CFAv1Library.InitData;
17
23
 
18
24
  error ZERO_ADDRESS();
19
25
  error ZERO_AMOUNT();
20
26
  error UNSUPPORTED_TOKEN();
21
27
  error ONLY_HOST_OR_SENDER(address);
28
+ error FEE_FLOW_FAILED(int96 curFeeRate, int96 newFeeRate);
29
+ error MIN_FLOWRATE(int96 flowRate);
22
30
 
23
- event SupporterUpdated(address indexed supporter, uint256 contribution, int96 flowRate, uint256 lastUpdated);
31
+ /**
32
+ * @dev Emitted when a supporter's contribution or flow rate is updated
33
+ * @param supporter The address of the supporter
34
+ * @param previousContribution The previous total contribution amount
35
+ * @param contribution The new total contribution amount
36
+ * @param previousFlowRate The previous flow rate if isFlowUpdate otherwise 0
37
+ * @param flowRate The new flow rate
38
+ * @param isFlowUpdate True if the update was a flow rate update, false if it was a single contribution update
39
+ */
40
+ event SupporterUpdated(
41
+ address indexed supporter,
42
+ uint256 previousContribution,
43
+ uint256 contribution,
44
+ int96 previousFlowRate,
45
+ int96 flowRate,
46
+ bool isFlowUpdate
47
+ );
24
48
 
25
49
  //TODO: ask about "view" for beforeagreement functions
26
50
  // ask about "receiver" can it be different then app?
@@ -40,7 +64,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
40
64
  address swapFrom;
41
65
  uint256 amount;
42
66
  uint256 minReturn;
43
- uint256 timestamp;
67
+ uint256 deadline;
44
68
  bytes path;
45
69
  }
46
70
 
@@ -50,11 +74,24 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
50
74
  uint128 lastUpdated;
51
75
  }
52
76
 
77
+ struct Stats {
78
+ uint256 netIncome; //without fees
79
+ uint256 totalFees;
80
+ uint256 lastUpdate;
81
+ address lastFeeRecipient;
82
+ int96 lastIncomeRate;
83
+ }
84
+
53
85
  ISuperToken public superToken;
54
86
 
55
87
  mapping(address => SupporterData) public supporters;
56
88
 
57
- uint256[50] private _reserved;
89
+ //initialize cfaV1 variable
90
+ CFAv1Library.InitData public cfaV1;
91
+
92
+ Stats public stats;
93
+
94
+ uint256[48] private _reserved;
58
95
 
59
96
  /// @custom:oz-upgrades-unsafe-allow constructor
60
97
  constructor(ISuperfluid _host, ISwapRouter _swapRouter) SuperAppBaseFlow(_host) {
@@ -62,6 +99,8 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
62
99
  swapRouter = _swapRouter;
63
100
  }
64
101
 
102
+ function getRegistry() public view virtual returns (DirectPaymentsFactory);
103
+
65
104
  /**
66
105
  * @dev Sets the address of the super token and registers the app with the host
67
106
  * @param _superToken The address of the super token contract
@@ -77,6 +116,14 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
77
116
 
78
117
  // Register the app with the host
79
118
  host.registerApp(callBackDefinitions);
119
+
120
+ //initialize InitData struct, and set equal to cfaV1
121
+ cfaV1 = CFAv1Library.InitData(
122
+ host,
123
+ IConstantFlowAgreementV1(
124
+ address(host.getAgreementClass(keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1")))
125
+ )
126
+ );
80
127
  }
81
128
 
82
129
  function isAcceptedSuperToken(ISuperToken _superToken) public view override returns (bool) {
@@ -89,6 +136,20 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
89
136
  return supporter.contribution + uint96(supporter.flowRate) * (block.timestamp - supporter.lastUpdated);
90
137
  }
91
138
 
139
+ function getRealtimeStats()
140
+ public
141
+ view
142
+ returns (uint256 netIncome, uint256 totalFees, int96 incomeFlowRate, int96 feeRate)
143
+ {
144
+ incomeFlowRate = stats.lastIncomeRate;
145
+ netIncome = stats.netIncome + uint96(stats.lastIncomeRate) * (block.timestamp - stats.lastUpdate);
146
+ feeRate = superToken.getFlowRate(address(this), stats.lastFeeRecipient);
147
+ totalFees =
148
+ stats.totalFees +
149
+ uint96(superToken.getFlowRate(address(this), stats.lastFeeRecipient)) *
150
+ (block.timestamp - stats.lastUpdate);
151
+ }
152
+
92
153
  /**
93
154
  * @dev This function is called when a token transfer occurs
94
155
  * @param _sender The address of the sender
@@ -100,16 +161,16 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
100
161
  if (_amount == 0) revert ZERO_AMOUNT();
101
162
 
102
163
  // Update the contribution amount for the sender in the supporters mapping
103
- _updateSupporter(_sender, int96(int256(_amount / 1000)), block.timestamp - 1000);
164
+ _updateSupporter(_sender, int256(_amount), 0, "");
104
165
 
105
166
  return true;
106
167
  }
107
168
 
108
169
  /**
109
- * @dev This function supports the Superfluid protocol by allowing a sender to contribute tokens to the network.
170
+ * @dev allow single contribution. user needs to approve tokens first. can be used in superfluid batch actions.
110
171
  * @param _sender The address of the sender who is contributing tokens.
111
172
  * @param _amount The amount of tokens being contributed.
112
- * @param _ctx The context of the transaction for superfluid
173
+ * @param _ctx The context of the transaction for superfluid in case this was used in superfluid batch. otherwise can be empty.
113
174
  * @return Returns the context of the transaction.
114
175
  */
115
176
  function support(
@@ -123,7 +184,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
123
184
  TransferHelper.safeTransferFrom(address(superToken), _sender, address(this), _amount);
124
185
 
125
186
  // Update the contribution amount for the sender in the supporters mapping
126
- _updateSupporter(_sender, int96(int256(_amount / 1000)), block.timestamp - 1000);
187
+ _updateSupporter(_sender, int256(_amount), 0, ""); //we pass empty ctx since this is not a flow but a single donation
127
188
 
128
189
  return _ctx;
129
190
  }
@@ -151,7 +212,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
151
212
  ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
152
213
  path: _customData.path,
153
214
  recipient: _sender,
154
- deadline: _customData.timestamp,
215
+ deadline: _customData.deadline,
155
216
  amountIn: _customData.amount,
156
217
  amountOutMinimum: _customData.minReturn
157
218
  });
@@ -163,7 +224,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
163
224
  tokenOut: address(superToken),
164
225
  fee: 10000,
165
226
  recipient: _sender,
166
- deadline: _customData.timestamp,
227
+ deadline: _customData.deadline,
167
228
  amountIn: _customData.amount,
168
229
  amountOutMinimum: _customData.minReturn,
169
230
  sqrtPriceLimitX96: 0
@@ -189,10 +250,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
189
250
  bytes calldata _ctx
190
251
  ) internal virtual override returns (bytes memory /*newCtx*/) {
191
252
  // Update the supporter's information
192
- _updateSupporter(_sender, 0, 0);
193
-
194
- // Return the context of the transaction
195
- return _ctx;
253
+ return _updateSupporter(_sender, 0, 0, _ctx);
196
254
  }
197
255
 
198
256
  /**
@@ -211,10 +269,7 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
211
269
  bytes calldata _ctx
212
270
  ) internal virtual override returns (bytes memory /*newCtx*/) {
213
271
  // Update the supporter's information
214
- _updateSupporter(_sender, _previousFlowRate, _lastUpdated);
215
-
216
- // Return the context of the transaction
217
- return _ctx;
272
+ return _updateSupporter(_sender, _previousFlowRate, _lastUpdated, _ctx);
218
273
  }
219
274
 
220
275
  /**
@@ -234,27 +289,107 @@ abstract contract GoodCollectiveSuperApp is SuperAppBaseFlow {
234
289
  bytes calldata _ctx
235
290
  ) internal virtual override returns (bytes memory /*newCtx*/) {
236
291
  // Update the supporter's information
237
- _updateSupporter(_sender, _previousFlowRate, _lastUpdated);
238
-
239
- // Return the context of the transaction
240
- return _ctx;
292
+ return _updateSupporter(_sender, _previousFlowRate, _lastUpdated, _ctx);
241
293
  }
242
294
 
243
295
  /**
244
296
  * @dev Updates the information for a supporter
245
297
  * @param _supporter The address of the supporter
246
- * @param _previousFlowRate The previous flow rate of the stream
298
+ * @param _previousFlowRateOrAmount The previous flow rate of the stream or single donation amount
247
299
  * @param _lastUpdated The timestamp of the last update to the stream
300
+ * @param _ctx flow context
248
301
  */
249
- function _updateSupporter(address _supporter, int96 _previousFlowRate, uint256 _lastUpdated) internal {
302
+ function _updateSupporter(
303
+ address _supporter,
304
+ int256 _previousFlowRateOrAmount,
305
+ uint256 _lastUpdated,
306
+ bytes memory _ctx
307
+ ) internal returns (bytes memory newCtx) {
308
+ newCtx = _ctx;
309
+ bool _isFlow = _ctx.length > 0;
310
+ _updateStats(_isFlow ? 0 : uint256(_previousFlowRateOrAmount));
250
311
  // Get the current flow rate for the supporter
251
- (, int96 flowRate, , ) = superToken.getFlowInfo(_supporter, address(this));
312
+ int96 flowRate = superToken.getFlowRate(_supporter, address(this));
313
+ uint256 prevContribution = supporters[_supporter].contribution;
314
+ if (_isFlow) {
315
+ //enforce minimal flow rate
316
+ if (flowRate > 0 && flowRate < MIN_FLOW_RATE) revert MIN_FLOWRATE(flowRate);
317
+ // Update the supporter's information
318
+ supporters[_supporter].lastUpdated = uint128(block.timestamp);
319
+ supporters[_supporter].flowRate = flowRate;
320
+ supporters[_supporter].contribution +=
321
+ uint96(int96(_previousFlowRateOrAmount)) *
322
+ (block.timestamp - _lastUpdated);
323
+ newCtx = _takeFeeFlow(flowRate - int96(_previousFlowRateOrAmount), _ctx);
324
+ // we update the last rate after we do all changes to our own flows
325
+ stats.lastIncomeRate = superToken.getNetFlowRate(address(this));
326
+ } else {
327
+ supporters[_supporter].contribution += uint256(_previousFlowRateOrAmount);
328
+ _takeFeeSingle(uint256(_previousFlowRateOrAmount));
329
+ }
252
330
 
253
- // Update the supporter's information
254
- supporters[_supporter].lastUpdated = uint128(block.timestamp);
255
- supporters[_supporter].flowRate = flowRate;
256
- supporters[_supporter].contribution += uint96(_previousFlowRate) * (block.timestamp - _lastUpdated);
257
- emit SupporterUpdated(_supporter, supporters[_supporter].contribution, flowRate, block.timestamp);
331
+ emit SupporterUpdated(
332
+ _supporter,
333
+ prevContribution,
334
+ supporters[_supporter].contribution,
335
+ _isFlow ? int96(int256(_previousFlowRateOrAmount)) : int96(0),
336
+ flowRate,
337
+ _isFlow
338
+ );
339
+ }
340
+
341
+ // this should be called before any flow rate changes
342
+ function _updateStats(uint256 _amount) internal {
343
+ //use last rate before the current possible rate update
344
+ stats.netIncome += uint96(stats.lastIncomeRate) * (block.timestamp - stats.lastUpdate);
345
+ uint feeBps;
346
+ if (address(getRegistry()) != address(0)) {
347
+ feeBps = getRegistry().feeBps();
348
+ //fees sent to last recipient, the flowRate to recipient still wasnt updated.
349
+ stats.totalFees +=
350
+ uint96(superToken.getFlowRate(address(this), stats.lastFeeRecipient)) *
351
+ (block.timestamp - stats.lastUpdate);
352
+ }
353
+ if (_amount > 0) {
354
+ stats.netIncome += (_amount * (10000 - feeBps)) / 10000;
355
+ stats.totalFees += (_amount * feeBps) / 10000;
356
+ }
357
+ stats.lastUpdate = block.timestamp;
358
+ }
359
+
360
+ function _takeFeeFlow(int96 _diffRate, bytes memory _ctx) internal returns (bytes memory newCtx) {
361
+ newCtx = _ctx;
362
+ if (address(getRegistry()) == address(0)) return newCtx;
363
+ address recipient = getRegistry().feeRecipient();
364
+ int96 curFeeRate = superToken.getFlowRate(address(this), stats.lastFeeRecipient);
365
+ bool newRecipient;
366
+ if (recipient != stats.lastFeeRecipient) {
367
+ newRecipient = true;
368
+ if (stats.lastFeeRecipient != address(0)) {
369
+ //delete old recipient flow
370
+ if (curFeeRate > 0)
371
+ newCtx = cfaV1.deleteFlowWithCtx(newCtx, address(this), stats.lastFeeRecipient, superToken); //passing in the ctx which is sent to the callback here
372
+ }
373
+ stats.lastFeeRecipient = recipient;
374
+ }
375
+ if (recipient == address(0)) return newCtx;
376
+
377
+ int96 feeRateChange = (_diffRate * int32(getRegistry().feeBps())) / 10000;
378
+ int96 newFeeRate = curFeeRate + feeRateChange;
379
+ if (newFeeRate <= 0 && newRecipient == false) {
380
+ newCtx = cfaV1.deleteFlowWithCtx(newCtx, address(this), recipient, superToken); //passing in the ctx which is sent to the callback here
381
+ } else if (curFeeRate > 0 && newRecipient == false) {
382
+ newCtx = cfaV1.updateFlowWithCtx(newCtx, recipient, superToken, newFeeRate); //passing in the ctx which is sent to the callback here
383
+ } else if (newFeeRate > 0) newCtx = cfaV1.createFlowWithCtx(newCtx, recipient, superToken, newFeeRate); //passing in the ctx which is sent to the callback here
384
+ }
385
+
386
+ function _takeFeeSingle(uint256 _amount) internal {
387
+ if (address(getRegistry()) == address(0)) return;
388
+ address recipient = getRegistry().feeRecipient();
389
+ if (recipient == address(0)) return;
390
+
391
+ uint256 fee = (_amount * getRegistry().feeBps()) / 10000;
392
+ TransferHelper.safeTransfer(address(superToken), recipient, fee);
258
393
  }
259
394
 
260
395
  modifier onlyHostOrSender(address _sender) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gooddollar/goodcollective-contracts",
3
3
  "packageManager": "yarn@3.2.1",
4
- "version": "1.0.2",
4
+ "version": "1.0.4",
5
5
  "license": "MIT",
6
6
  "types": "./typechain-types/index.ts",
7
7
  "files": [