@towns-protocol/contracts 0.0.426 → 0.0.428

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@towns-protocol/contracts",
3
- "version": "0.0.426",
3
+ "version": "0.0.428",
4
4
  "scripts": {
5
5
  "clean": "forge clean",
6
6
  "compile": "forge build",
@@ -33,7 +33,7 @@
33
33
  "@layerzerolabs/oapp-evm": "^0.3.2",
34
34
  "@openzeppelin/merkle-tree": "^1.0.8",
35
35
  "@prb/test": "^0.6.4",
36
- "@towns-protocol/prettier-config": "^0.0.426",
36
+ "@towns-protocol/prettier-config": "^0.0.428",
37
37
  "@wagmi/cli": "^2.2.0",
38
38
  "forge-std": "github:foundry-rs/forge-std#v1.10.0",
39
39
  "prettier": "^3.5.3",
@@ -53,5 +53,5 @@
53
53
  "publishConfig": {
54
54
  "access": "public"
55
55
  },
56
- "gitHead": "546bca3241d785992e428f973be7a558715cdbb1"
56
+ "gitHead": "8500096a8786765d97f2dbe88c17f24f051796db"
57
57
  }
@@ -51,14 +51,6 @@ contract InteractPostDeploy is Interaction {
51
51
  IImplementationRegistry(spaceFactory).addImplementation(baseRegistry);
52
52
  IImplementationRegistry(spaceFactory).addImplementation(riverAirdrop);
53
53
  IImplementationRegistry(spaceFactory).addImplementation(appRegistry);
54
- IFeeManager(spaceFactory).setFeeConfig(
55
- FeeTypesLib.TIP_MEMBER,
56
- deployer,
57
- FeeCalculationMethod.PERCENT,
58
- 50,
59
- 0,
60
- true
61
- );
62
54
  ISubscriptionModule(subscriptionModule).setSpaceFactory(spaceFactory);
63
55
  IMainnetDelegation(baseRegistry).setProxyDelegation(proxyDelegation);
64
56
  IRewardsDistribution(baseRegistry).setRewardNotifier(deployer, true);
@@ -8,6 +8,9 @@ import {OwnableBase} from "@towns-protocol/diamond/src/facets/ownable/OwnableBas
8
8
  import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
9
9
  import {ReentrancyGuardTransient} from "solady/utils/ReentrancyGuardTransient.sol";
10
10
 
11
+ // libraries
12
+ import {FeeTypesLib} from "./FeeTypesLib.sol";
13
+
11
14
  /// @title FeeManagerFacet
12
15
  /// @notice Facet for unified fee management across the protocol
13
16
  contract FeeManagerFacet is
@@ -28,6 +31,7 @@ contract FeeManagerFacet is
28
31
 
29
32
  function __FeeManagerFacet__init_unchained(address protocolRecipient) internal {
30
33
  _setProtocolFeeRecipient(protocolRecipient);
34
+ _setInitialFeeConfigs(protocolRecipient);
31
35
  }
32
36
 
33
37
  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -100,4 +104,36 @@ contract FeeManagerFacet is
100
104
  function getProtocolFeeRecipient() external view returns (address recipient) {
101
105
  return _getProtocolFeeRecipient();
102
106
  }
107
+
108
+ function _setInitialFeeConfigs(address protocolRecipient) internal {
109
+ // tipping fee
110
+ _setFeeConfig(
111
+ FeeTypesLib.TIP_MEMBER,
112
+ protocolRecipient,
113
+ FeeCalculationMethod.PERCENT,
114
+ 50,
115
+ 0,
116
+ true
117
+ );
118
+
119
+ // membership fee
120
+ _setFeeConfig(
121
+ FeeTypesLib.MEMBERSHIP,
122
+ protocolRecipient,
123
+ FeeCalculationMethod.HYBRID,
124
+ 1000, // 10%
125
+ 0.0005 ether,
126
+ true
127
+ );
128
+
129
+ // app installation fee
130
+ _setFeeConfig(
131
+ FeeTypesLib.APP_INSTALL,
132
+ protocolRecipient,
133
+ FeeCalculationMethod.HYBRID,
134
+ 1000, // 10%
135
+ 0.0005 ether,
136
+ true
137
+ );
138
+ }
103
139
  }
@@ -63,7 +63,6 @@ interface IMembershipBase {
63
63
  error Membership__InvalidPricingModule();
64
64
  error Membership__AlreadyMember();
65
65
  error Membership__InsufficientPayment();
66
- error Membership__PriceTooLow();
67
66
  error Membership__MaxSupplyReached();
68
67
  error Membership__InvalidTokenId();
69
68
  error Membership__NotExpired();
@@ -35,7 +35,6 @@ abstract contract MembershipBase is IMembershipBase {
35
35
  $.freeAllocationEnabled = true;
36
36
 
37
37
  if (info.price > 0) {
38
- _verifyPrice(info.price);
39
38
  if (info.freeAllocation > 0)
40
39
  Membership__CannotSetFreeAllocationOnPaidSpace.selector.revertWith();
41
40
  IMembershipPricing(info.pricingModule).setPrice(info.price);
@@ -66,14 +65,29 @@ abstract contract MembershipBase is IMembershipBase {
66
65
  );
67
66
  }
68
67
 
68
+ function _getMembershipPrice(
69
+ uint256 totalSupply
70
+ ) internal view virtual returns (uint256 membershipPrice) {
71
+ address pricingModule = _getPricingModule();
72
+ if (pricingModule == address(0)) return 0;
73
+ uint256 freeAllocation = _getMembershipFreeAllocation();
74
+ membershipPrice = IMembershipPricing(pricingModule).getPrice(freeAllocation, totalSupply);
75
+ }
76
+
69
77
  function _getProtocolFee(uint256 membershipPrice) internal view returns (uint256) {
70
78
  IPlatformRequirements platform = _getPlatformRequirements();
79
+ uint256 baseFee = platform.getMembershipFee();
80
+ if (membershipPrice == 0) return baseFee;
81
+ uint256 bpsFee = BasisPoints.calculate(membershipPrice, platform.getMembershipBps());
82
+ return FixedPointMathLib.max(bpsFee, baseFee);
83
+ }
71
84
 
72
- uint256 minPrice = platform.getMembershipMinPrice();
73
-
74
- if (membershipPrice < minPrice) return platform.getMembershipFee();
75
-
76
- return BasisPoints.calculate(membershipPrice, platform.getMembershipBps());
85
+ function _getTotalMembershipPayment(
86
+ uint256 membershipPrice
87
+ ) internal view returns (uint256 totalRequired, uint256 protocolFee) {
88
+ protocolFee = _getProtocolFee(membershipPrice);
89
+ if (membershipPrice == 0) return (protocolFee, protocolFee);
90
+ return (membershipPrice + protocolFee, protocolFee);
77
91
  }
78
92
 
79
93
  function _transferIn(address from, uint256 amount) internal returns (uint256) {
@@ -152,31 +166,9 @@ abstract contract MembershipBase is IMembershipBase {
152
166
  }
153
167
 
154
168
  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
155
- /* PRICING */
169
+ /* RENEWAL */
156
170
  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
157
171
 
158
- function _verifyPrice(uint256 newPrice) internal view {
159
- uint256 minFee = _getPlatformRequirements().getMembershipFee();
160
- if (newPrice < minFee) Membership__PriceTooLow.selector.revertWith();
161
- }
162
-
163
- /// @dev Makes it virtual to allow other pricing strategies
164
- function _getMembershipPrice(
165
- uint256 totalSupply
166
- ) internal view virtual returns (uint256 membershipPrice) {
167
- address pricingModule = _getPricingModule();
168
- IPlatformRequirements platform = _getPlatformRequirements();
169
- if (pricingModule == address(0)) return platform.getMembershipFee();
170
-
171
- // get free allocation
172
- uint256 freeAllocation = _getMembershipFreeAllocation();
173
- membershipPrice = IMembershipPricing(pricingModule).getPrice(freeAllocation, totalSupply);
174
- if (membershipPrice == 0) return 0;
175
-
176
- uint256 minPrice = platform.getMembershipMinPrice();
177
- if (membershipPrice < minPrice) return platform.getMembershipFee();
178
- }
179
-
180
172
  function _setMembershipRenewalPrice(uint256 tokenId, uint256 pricePaid) internal {
181
173
  MembershipStorage.layout().renewalPriceByTokenId[tokenId] = pricePaid;
182
174
  }
@@ -186,22 +178,14 @@ abstract contract MembershipBase is IMembershipBase {
186
178
  uint256 totalSupply
187
179
  ) internal view returns (uint256) {
188
180
  MembershipStorage.Layout storage $ = MembershipStorage.layout();
189
- IPlatformRequirements platform = _getPlatformRequirements();
190
-
191
- uint256 minFee = platform.getMembershipFee();
192
- uint256 renewalPrice = $.renewalPriceByTokenId[tokenId];
181
+ uint256 lockedRenewalPrice = $.renewalPriceByTokenId[tokenId];
193
182
  uint256 currentPrice = _getMembershipPrice(totalSupply);
194
183
 
195
- // If no stored renewal price, use current price
196
- if (renewalPrice == 0) {
197
- return FixedPointMathLib.max(currentPrice, minFee);
198
- }
184
+ // If no locked price, use current price
185
+ if (lockedRenewalPrice == 0) return currentPrice;
199
186
 
200
- // Use the LOWER of stored renewal price or current price
201
- // This ensures users benefit from price drops (including free transitions)
202
- // while maintaining their locked-in rate if prices increase
203
- uint256 effectivePrice = FixedPointMathLib.min(renewalPrice, currentPrice);
204
- return FixedPointMathLib.max(effectivePrice, minFee);
187
+ // Return the lower of the two prices (benefits user)
188
+ return FixedPointMathLib.min(lockedRenewalPrice, currentPrice);
205
189
  }
206
190
 
207
191
  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -98,25 +98,26 @@ contract MembershipFacet is IMembership, MembershipJoin, ReentrancyGuard, Facet
98
98
 
99
99
  /// @inheritdoc IMembership
100
100
  function setMembershipPrice(uint256 newPrice) external onlyOwner {
101
- _verifyPrice(newPrice);
102
- if (newPrice > 0 && _getMembershipFreeAllocation() > 0)
103
- Membership__CannotSetPriceOnFreeSpace.selector.revertWith();
104
101
  IMembershipPricing(_getPricingModule()).setPrice(newPrice);
105
102
  }
106
103
 
107
104
  /// @inheritdoc IMembership
108
- function getMembershipPrice() external view returns (uint256) {
109
- return _getMembershipPrice(_totalSupply());
105
+ function getMembershipPrice() external view returns (uint256 totalRequired) {
106
+ (totalRequired, ) = _getTotalMembershipPayment(_getMembershipPrice(_totalSupply()));
110
107
  }
111
108
 
112
109
  /// @inheritdoc IMembership
113
- function getMembershipRenewalPrice(uint256 tokenId) external view returns (uint256) {
114
- return _getMembershipRenewalPrice(tokenId, _totalSupply());
110
+ function getMembershipRenewalPrice(
111
+ uint256 tokenId
112
+ ) external view returns (uint256 totalRequired) {
113
+ (totalRequired, ) = _getTotalMembershipPayment(
114
+ _getMembershipRenewalPrice(tokenId, _totalSupply())
115
+ );
115
116
  }
116
117
 
117
118
  /// @inheritdoc IMembership
118
- function getProtocolFee() external view returns (uint256) {
119
- return _getProtocolFee(_getMembershipPrice(_totalSupply()));
119
+ function getProtocolFee() external view returns (uint256 protocolFee) {
120
+ (, protocolFee) = _getTotalMembershipPayment(_getMembershipPrice(_totalSupply()));
120
121
  }
121
122
 
122
123
  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -133,11 +134,6 @@ contract MembershipFacet is IMembership, MembershipJoin, ReentrancyGuard, Facet
133
134
  Membership__InvalidFreeAllocation.selector.revertWith();
134
135
  }
135
136
 
136
- if (_getMembershipPrice(_totalSupply()) > 0) {
137
- Membership__CannotSetFreeAllocationOnPaidSpace.selector.revertWith();
138
- }
139
-
140
- // verify newLimit is not more than the allowed platform limit
141
137
  _verifyFreeAllocation(newAllocation);
142
138
  _setMembershipFreeAllocation(newAllocation);
143
139
  }
@@ -89,8 +89,8 @@ abstract contract MembershipJoin is
89
89
  return joinDetails;
90
90
  }
91
91
 
92
- // Regular paid join
93
- (joinDetails.amountDue, joinDetails.shouldCharge) = (membershipPrice, true);
92
+ (uint256 totalRequired, ) = _getTotalMembershipPayment(membershipPrice);
93
+ (joinDetails.amountDue, joinDetails.shouldCharge) = (totalRequired, true);
94
94
  }
95
95
 
96
96
  /// @notice Handles the process of joining a space
@@ -317,8 +317,7 @@ abstract contract MembershipJoin is
317
317
  sender,
318
318
  receiver,
319
319
  joinDetails.amountDue,
320
- ownerProceeds,
321
- joinDetails.basePrice
320
+ ownerProceeds
322
321
  );
323
322
  }
324
323
 
@@ -365,8 +364,7 @@ abstract contract MembershipJoin is
365
364
  sender,
366
365
  receiver,
367
366
  joinDetails.amountDue,
368
- ownerProceeds,
369
- joinDetails.basePrice
367
+ ownerProceeds
370
368
  );
371
369
  }
372
370
 
@@ -375,8 +373,7 @@ abstract contract MembershipJoin is
375
373
  address payer,
376
374
  address receiver,
377
375
  uint256 paymentRequired,
378
- uint256 ownerProceeds,
379
- uint256 membershipPrice
376
+ uint256 ownerProceeds
380
377
  ) internal {
381
378
  // account for owner's proceeds
382
379
  if (ownerProceeds != 0) _transferIn(payer, ownerProceeds);
@@ -384,7 +381,7 @@ abstract contract MembershipJoin is
384
381
  _releaseCapturedValue(transactionId, paymentRequired);
385
382
  _deleteCapturedData(transactionId);
386
383
 
387
- _mintMembershipPoints(receiver, membershipPrice);
384
+ _mintMembershipPoints(receiver, paymentRequired);
388
385
  }
389
386
 
390
387
  /// @notice Issues a membership token to the receiver
@@ -498,22 +495,25 @@ abstract contract MembershipJoin is
498
495
 
499
496
  function _renewMembership(address payer, uint256 tokenId) internal {
500
497
  address receiver = _ownerOf(tokenId);
501
-
502
498
  if (receiver == address(0)) Membership__InvalidAddress.selector.revertWith();
503
499
 
504
500
  uint256 duration = _getMembershipDuration();
505
- uint256 membershipPrice = _getMembershipRenewalPrice(tokenId, _totalSupply());
501
+ uint256 basePrice = _getMembershipRenewalPrice(tokenId, _totalSupply());
502
+ (uint256 totalRequired, ) = _getTotalMembershipPayment(basePrice);
506
503
 
507
- if (membershipPrice > msg.value) Membership__InvalidPayment.selector.revertWith();
504
+ if (totalRequired > msg.value) Membership__InvalidPayment.selector.revertWith();
508
505
 
509
- _mintMembershipPoints(receiver, membershipPrice);
506
+ // Collect protocol fee (transfers from payer)
507
+ uint256 protocolFee = _collectProtocolFee(payer, basePrice);
510
508
 
511
- uint256 protocolFee = _collectProtocolFee(payer, membershipPrice);
509
+ // Calculate owner proceeds (what goes to space owner)
510
+ uint256 ownerProceeds = totalRequired - protocolFee;
512
511
 
513
- uint256 remainingDue = membershipPrice - protocolFee;
514
- if (remainingDue > 0) _transferIn(payer, remainingDue);
512
+ // Transfer owner proceeds to contract
513
+ if (ownerProceeds > 0) _transferIn(payer, ownerProceeds);
515
514
 
516
- uint256 excess = msg.value - membershipPrice;
515
+ // Handle excess payment
516
+ uint256 excess = msg.value - totalRequired;
517
517
  if (excess > 0) {
518
518
  CurrencyTransfer.transferCurrency(
519
519
  _getMembershipCurrency(),
@@ -523,6 +523,7 @@ abstract contract MembershipJoin is
523
523
  );
524
524
  }
525
525
 
526
+ _mintMembershipPoints(receiver, totalRequired);
526
527
  _renewSubscription(tokenId, uint64(duration));
527
528
  }
528
529