@towns-protocol/contracts 0.0.374 → 0.0.375

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.374",
3
+ "version": "0.0.375",
4
4
  "packageManager": "yarn@3.8.0",
5
5
  "scripts": {
6
6
  "build-types": "bash scripts/build-contract-types.sh",
@@ -35,7 +35,7 @@
35
35
  "@layerzerolabs/oapp-evm": "^0.3.2",
36
36
  "@openzeppelin/merkle-tree": "^1.0.8",
37
37
  "@prb/test": "^0.6.4",
38
- "@towns-protocol/prettier-config": "^0.0.374",
38
+ "@towns-protocol/prettier-config": "^0.0.375",
39
39
  "@typechain/ethers-v5": "^11.1.2",
40
40
  "@wagmi/cli": "^2.2.0",
41
41
  "forge-std": "github:foundry-rs/forge-std#v1.10.0",
@@ -57,5 +57,5 @@
57
57
  "publishConfig": {
58
58
  "access": "public"
59
59
  },
60
- "gitHead": "b6f28ce51336d969161de5c5aa0aa012f03a182a"
60
+ "gitHead": "f64537fbf8d7eca815c5c5e52f601a387e713a0a"
61
61
  }
@@ -7,15 +7,26 @@ import {ITipping} from "src/spaces/facets/tipping/ITipping.sol";
7
7
 
8
8
  // libraries
9
9
  import {LibDeploy} from "@towns-protocol/diamond/src/utils/LibDeploy.sol";
10
+ import {DynamicArrayLib} from "solady/utils/DynamicArrayLib.sol";
10
11
 
11
12
  library DeployTipping {
13
+ using DynamicArrayLib for DynamicArrayLib.DynamicArray;
14
+
12
15
  function selectors() internal pure returns (bytes4[] memory res) {
13
- res = new bytes4[](5);
14
- res[0] = ITipping.tip.selector;
15
- res[1] = ITipping.tipsByCurrencyAndTokenId.selector;
16
- res[2] = ITipping.tippingCurrencies.selector;
17
- res[3] = ITipping.totalTipsByCurrency.selector;
18
- res[4] = ITipping.tipAmountByCurrency.selector;
16
+ DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(8);
17
+ arr.p(ITipping.sendTip.selector);
18
+ arr.p(ITipping.tip.selector);
19
+ arr.p(ITipping.tipsByWalletAndCurrency.selector);
20
+ arr.p(ITipping.tipCountByWalletAndCurrency.selector);
21
+ arr.p(ITipping.tipsByCurrencyAndTokenId.selector);
22
+ arr.p(ITipping.tippingCurrencies.selector);
23
+ arr.p(ITipping.totalTipsByCurrency.selector);
24
+ arr.p(ITipping.tipAmountByCurrency.selector);
25
+
26
+ bytes32[] memory selectors_ = arr.asBytes32Array();
27
+ assembly ("memory-safe") {
28
+ res := selectors_
29
+ }
19
30
  }
20
31
 
21
32
  function makeCut(
@@ -2,10 +2,45 @@
2
2
  pragma solidity ^0.8.23;
3
3
 
4
4
  interface ITippingBase {
5
+ // =============================================================
6
+ // Enums
7
+ // =============================================================
8
+
9
+ enum TipRecipientType {
10
+ Member, // Tips to token holders
11
+ Bot, // Tips to bot wallets
12
+ Pool // Tips to pool wallets
13
+ }
14
+
5
15
  // =============================================================
6
16
  // Structs
7
17
  // =============================================================
8
18
 
19
+ struct TipMetadata {
20
+ bytes32 messageId;
21
+ bytes32 channelId;
22
+ bytes data; // Extensible metadata
23
+ }
24
+
25
+ /// @notice Params for Member tips (includes tokenId)
26
+ struct MembershipTipParams {
27
+ address receiver;
28
+ uint256 tokenId;
29
+ address currency;
30
+ uint256 amount;
31
+ TipMetadata metadata;
32
+ }
33
+
34
+ /// @notice Params for Bot tips (similar to Wallet but distinct type)
35
+ struct BotTipParams {
36
+ address receiver;
37
+ address currency;
38
+ bytes32 appId;
39
+ uint256 amount;
40
+ TipMetadata metadata;
41
+ }
42
+
43
+ /// @notice Legacy tip request (maintain backwards compatibility)
9
44
  struct TipRequest {
10
45
  address receiver;
11
46
  uint256 tokenId;
@@ -19,6 +54,16 @@ interface ITippingBase {
19
54
  // Events
20
55
  // =============================================================
21
56
 
57
+ event TipSent(
58
+ address indexed sender,
59
+ address indexed receiver,
60
+ TipRecipientType indexed recipientType,
61
+ address currency,
62
+ uint256 amount,
63
+ uint256 tokenId // 0 if not a member tip
64
+ );
65
+
66
+ // Maintain legacy event for backwards compatibility
22
67
  event Tip(
23
68
  uint256 indexed tokenId,
24
69
  address indexed currency,
@@ -33,6 +78,8 @@ interface ITippingBase {
33
78
  // Errors
34
79
  // =============================================================
35
80
 
81
+ error InvalidRecipientType();
82
+ error InvalidTipData();
36
83
  error TokenDoesNotExist();
37
84
  error ReceiverIsNotMember();
38
85
  error CannotTipSelf();
@@ -43,7 +90,16 @@ interface ITippingBase {
43
90
  }
44
91
 
45
92
  interface ITipping is ITippingBase {
46
- /// @notice Sends a tip to a space member
93
+ /// @notice Send a tip using flexible encoding based on recipient type
94
+ /// @param recipientType The type of recipient (Member, Wallet, Bot, Pool)
95
+ /// @param data ABI-encoded tip params based on recipientType:
96
+ /// - Member: abi.encode(MembershipTipParams)
97
+ /// - Wallet: abi.encode(WalletTipParams)
98
+ /// - Bot: abi.encode(BotTipParams)
99
+ /// - Pool: Reserved for future implementation
100
+ function sendTip(TipRecipientType recipientType, bytes calldata data) external payable;
101
+
102
+ /// @notice Sends a tip to a space member (legacy)
47
103
  /// @param tipRequest The tip request containing token ID, currency, amount, message ID and
48
104
  /// channel ID
49
105
  /// @dev Requires sender and receiver to be members of the space
@@ -51,6 +107,24 @@ interface ITipping is ITippingBase {
51
107
  /// @dev Emits Tip event
52
108
  function tip(TipRequest calldata tipRequest) external payable;
53
109
 
110
+ /// @notice Get tips received by wallet address and currency
111
+ /// @param wallet The wallet address to get tips for
112
+ /// @param currency The currency address to get tips in
113
+ /// @return The total amount of tips received in the specified currency
114
+ function tipsByWalletAndCurrency(
115
+ address wallet,
116
+ address currency
117
+ ) external view returns (uint256);
118
+
119
+ /// @notice Get tip count by wallet and currency
120
+ /// @param wallet The wallet address to get tip count for
121
+ /// @param currency The currency address to get tip count in
122
+ /// @return The total number of tips received in the specified currency
123
+ function tipCountByWalletAndCurrency(
124
+ address wallet,
125
+ address currency
126
+ ) external view returns (uint256);
127
+
54
128
  /// @notice Gets the total tips received for a token ID in a specific currency
55
129
  /// @param tokenId The token ID to get tips for
56
130
  /// @param currency The currency address to get tips in
@@ -2,73 +2,269 @@
2
2
  pragma solidity ^0.8.23;
3
3
 
4
4
  // interfaces
5
+ import {ITippingBase} from "./ITipping.sol";
6
+ import {ITownsPointsBase} from "../../../airdrop/points/ITownsPoints.sol";
7
+ import {IPlatformRequirements} from "../../../factory/facets/platform/requirements/IPlatformRequirements.sol";
5
8
 
6
9
  // libraries
7
10
  import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
11
+ import {BasisPoints} from "../../../utils/libraries/BasisPoints.sol";
8
12
  import {CurrencyTransfer} from "../../../utils/libraries/CurrencyTransfer.sol";
13
+ import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
14
+ import {MembershipStorage} from "../membership/MembershipStorage.sol";
15
+ import {TippingStorage} from "./TippingStorage.sol";
9
16
 
10
17
  // contracts
18
+ import {PointsBase} from "../points/PointsBase.sol";
11
19
 
12
- library TippingBase {
20
+ abstract contract TippingBase is ITippingBase, PointsBase {
13
21
  using EnumerableSet for EnumerableSet.AddressSet;
22
+ using CustomRevert for bytes4;
14
23
 
15
- // keccak256(abi.encode(uint256(keccak256("spaces.facets.tipping.storage")) - 1)) &
16
- // ~bytes32(uint256(0xff))
17
- bytes32 internal constant STORAGE_SLOT =
18
- 0xb6cb334a9eea0cca2581db4520b45ac6f03de8e3927292302206bb82168be300;
24
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
25
+ /* Internal Functions */
26
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
19
27
 
20
- struct TippingStats {
21
- uint256 totalTips;
22
- uint256 tipAmount;
28
+ /// @dev Processes sendTip based on recipient type
29
+ function _sendTip(TipRecipientType recipientType, bytes calldata data) internal {
30
+ if (recipientType == TipRecipientType.Member) {
31
+ _sendMemberTip(data);
32
+ } else if (recipientType == TipRecipientType.Bot) {
33
+ _sendBotTip(data);
34
+ } else {
35
+ InvalidRecipientType.selector.revertWith();
36
+ }
37
+ }
38
+
39
+ /// @dev Processes legacy tip function
40
+ function _tip(TipRequest calldata tipRequest) internal {
41
+ _validateTipRequest(
42
+ msg.sender,
43
+ tipRequest.receiver,
44
+ tipRequest.currency,
45
+ tipRequest.amount
46
+ );
47
+
48
+ uint256 tipAmount = tipRequest.amount;
49
+
50
+ // Handle native token
51
+ if (tipRequest.currency == CurrencyTransfer.NATIVE_TOKEN) {
52
+ if (msg.value != tipAmount) MsgValueMismatch.selector.revertWith();
53
+ uint256 protocolFee = _handleProtocolFee(tipAmount);
54
+ tipAmount -= protocolFee;
55
+ } else {
56
+ if (msg.value != 0) UnexpectedETH.selector.revertWith();
57
+ }
58
+
59
+ // Process tip
60
+ _processTip(
61
+ msg.sender,
62
+ tipRequest.receiver,
63
+ tipRequest.tokenId,
64
+ TipRecipientType.Member,
65
+ tipRequest.currency,
66
+ tipAmount
67
+ );
68
+
69
+ // Emit legacy event (with original amount for backwards compatibility)
70
+ emit Tip(
71
+ tipRequest.tokenId,
72
+ tipRequest.currency,
73
+ msg.sender,
74
+ tipRequest.receiver,
75
+ tipRequest.amount,
76
+ tipRequest.messageId,
77
+ tipRequest.channelId
78
+ );
79
+
80
+ // Emit new event (with actual tip amount after fees)
81
+ emit TipSent(
82
+ msg.sender,
83
+ tipRequest.receiver,
84
+ TipRecipientType.Member,
85
+ tipRequest.currency,
86
+ tipAmount,
87
+ tipRequest.tokenId
88
+ );
23
89
  }
24
90
 
25
- struct Layout {
26
- EnumerableSet.AddressSet currencies;
27
- mapping(uint256 tokenId => mapping(address currency => uint256 amount)) tipsByCurrencyByTokenId;
28
- mapping(address currency => TippingStats) tippingStatsByCurrency;
91
+ /// @dev Handles member tips
92
+ function _sendMemberTip(bytes calldata data) internal {
93
+ MembershipTipParams memory params = abi.decode(data, (MembershipTipParams));
94
+ _validateTipRequest(msg.sender, params.receiver, params.currency, params.amount);
95
+
96
+ uint256 tipAmount = params.amount;
97
+
98
+ // Handle native token
99
+ if (params.currency == CurrencyTransfer.NATIVE_TOKEN) {
100
+ if (msg.value != tipAmount) MsgValueMismatch.selector.revertWith();
101
+ uint256 protocolFee = _handleProtocolFee(tipAmount);
102
+ tipAmount -= protocolFee;
103
+ } else {
104
+ if (msg.value != 0) UnexpectedETH.selector.revertWith();
105
+ }
106
+
107
+ // Process tip
108
+ _processTip(
109
+ msg.sender,
110
+ params.receiver,
111
+ params.tokenId,
112
+ TipRecipientType.Member,
113
+ params.currency,
114
+ tipAmount
115
+ );
116
+
117
+ // Emit events
118
+ emit TipSent(
119
+ msg.sender,
120
+ params.receiver,
121
+ TipRecipientType.Member,
122
+ params.currency,
123
+ tipAmount,
124
+ params.tokenId
125
+ );
126
+
127
+ emit Tip(
128
+ params.tokenId,
129
+ params.currency,
130
+ msg.sender,
131
+ params.receiver,
132
+ tipAmount,
133
+ params.metadata.messageId,
134
+ params.metadata.channelId
135
+ );
29
136
  }
30
137
 
31
- function layout() internal pure returns (Layout storage l) {
32
- assembly {
33
- l.slot := STORAGE_SLOT
138
+ /// @dev Handles bot tips
139
+ function _sendBotTip(bytes calldata data) internal {
140
+ BotTipParams memory params = abi.decode(data, (BotTipParams));
141
+ _validateTipRequest(msg.sender, params.receiver, params.currency, params.amount);
142
+
143
+ uint256 tipAmount = params.amount;
144
+
145
+ // Handle native token (no protocol fee for bot tips)
146
+ if (params.currency == CurrencyTransfer.NATIVE_TOKEN) {
147
+ if (msg.value != tipAmount) MsgValueMismatch.selector.revertWith();
148
+ } else {
149
+ if (msg.value != 0) UnexpectedETH.selector.revertWith();
34
150
  }
151
+
152
+ // Process tip (tokenId = 0 for bot tips)
153
+ _processTip(
154
+ msg.sender,
155
+ params.receiver,
156
+ 0, // No tokenId for bot tips
157
+ TipRecipientType.Bot,
158
+ params.currency,
159
+ tipAmount
160
+ );
161
+
162
+ emit TipSent(
163
+ msg.sender,
164
+ params.receiver,
165
+ TipRecipientType.Bot,
166
+ params.currency,
167
+ tipAmount,
168
+ 0
169
+ );
35
170
  }
36
171
 
37
- function tip(
172
+ /// @dev Core tip processing logic
173
+ function _processTip(
38
174
  address sender,
39
175
  address receiver,
40
176
  uint256 tokenId,
177
+ TipRecipientType recipientType,
41
178
  address currency,
42
179
  uint256 amount
43
180
  ) internal {
44
- Layout storage ds = layout();
181
+ TippingStorage.Layout storage $ = TippingStorage.layout();
45
182
 
46
- ds.currencies.add(currency);
47
- ds.tipsByCurrencyByTokenId[tokenId][currency] += amount;
183
+ // Add currency to set
184
+ $.currencies.add(currency);
48
185
 
49
- TippingStats storage stats = ds.tippingStatsByCurrency[currency];
186
+ // Update stats by currency
187
+ TippingStorage.TippingStats storage stats = $.tippingStatsByCurrency[currency];
50
188
  stats.tipAmount += amount;
51
189
  stats.totalTips += 1;
52
190
 
191
+ // Update wallet-based tracking (all tips)
192
+ $.tippingStatsByCurrencyByWallet[receiver][currency].tipAmount += amount;
193
+ $.tippingStatsByCurrencyByWallet[receiver][currency].totalTips += 1;
194
+
195
+ // Update tokenId-based tracking (backwards compatibility, only for Member tips)
196
+ if (recipientType == TipRecipientType.Member) {
197
+ $.tipsByCurrencyByTokenId[tokenId][currency] += amount;
198
+ }
199
+
200
+ // Transfer currency
53
201
  CurrencyTransfer.transferCurrency(currency, sender, receiver, amount);
54
202
  }
55
203
 
56
- function totalTipsByCurrency(address currency) internal view returns (uint256) {
57
- return layout().tippingStatsByCurrency[currency].totalTips;
204
+ /// @dev Handles protocol fee and points minting
205
+ function _handleProtocolFee(uint256 amount) internal returns (uint256 protocolFee) {
206
+ MembershipStorage.Layout storage ds = MembershipStorage.layout();
207
+ IPlatformRequirements platform = IPlatformRequirements(ds.spaceFactory);
208
+
209
+ protocolFee = BasisPoints.calculate(amount, 50); // 0.5%
210
+
211
+ CurrencyTransfer.transferCurrency(
212
+ CurrencyTransfer.NATIVE_TOKEN,
213
+ msg.sender,
214
+ platform.getFeeRecipient(),
215
+ protocolFee
216
+ );
217
+
218
+ // Mint points
219
+ address airdropDiamond = _getAirdropDiamond();
220
+ uint256 points = _getPoints(
221
+ airdropDiamond,
222
+ ITownsPointsBase.Action.Tip,
223
+ abi.encode(protocolFee)
224
+ );
225
+ _mintPoints(airdropDiamond, msg.sender, points);
58
226
  }
59
227
 
60
- function tipAmountByCurrency(address currency) internal view returns (uint256) {
61
- return layout().tippingStatsByCurrency[currency].tipAmount;
228
+ /// @dev Validates common tip requirements
229
+ function _validateTipRequest(
230
+ address sender,
231
+ address receiver,
232
+ address currency,
233
+ uint256 amount
234
+ ) internal pure {
235
+ if (currency == address(0)) CurrencyIsZero.selector.revertWith();
236
+ if (sender == receiver) CannotTipSelf.selector.revertWith();
237
+ if (amount == 0) AmountIsZero.selector.revertWith();
62
238
  }
63
239
 
64
- function tipsByCurrencyByTokenId(
65
- uint256 tokenId,
240
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
241
+ /* Internal View Functions */
242
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
243
+
244
+ function _getTipsByWallet(address wallet, address currency) internal view returns (uint256) {
245
+ return TippingStorage.layout().tippingStatsByCurrencyByWallet[wallet][currency].tipAmount;
246
+ }
247
+
248
+ function _getTipCountByWallet(
249
+ address wallet,
66
250
  address currency
67
251
  ) internal view returns (uint256) {
68
- return layout().tipsByCurrencyByTokenId[tokenId][currency];
252
+ return TippingStorage.layout().tippingStatsByCurrencyByWallet[wallet][currency].totalTips;
253
+ }
254
+
255
+ function _getTipsByTokenId(uint256 tokenId, address currency) internal view returns (uint256) {
256
+ return TippingStorage.layout().tipsByCurrencyByTokenId[tokenId][currency];
257
+ }
258
+
259
+ function _getTippingCurrencies() internal view returns (address[] memory) {
260
+ return TippingStorage.layout().currencies.values();
261
+ }
262
+
263
+ function _getTotalTipsByCurrency(address currency) internal view returns (uint256) {
264
+ return TippingStorage.layout().tippingStatsByCurrency[currency].totalTips;
69
265
  }
70
266
 
71
- function tippingCurrencies() internal view returns (address[] memory) {
72
- return layout().currencies.values();
267
+ function _getTipAmountByCurrency(address currency) internal view returns (uint256) {
268
+ return TippingStorage.layout().tippingStatsByCurrency[currency].tipAmount;
73
269
  }
74
270
  }
@@ -2,80 +2,48 @@
2
2
  pragma solidity ^0.8.23;
3
3
 
4
4
  // interfaces
5
- import {ITownsPointsBase} from "../../../airdrop/points/ITownsPoints.sol";
6
- import {IPlatformRequirements} from "../../../factory/facets/platform/requirements/IPlatformRequirements.sol";
7
5
  import {ITipping} from "./ITipping.sol";
8
6
 
9
7
  // libraries
10
- import {BasisPoints} from "../../../utils/libraries/BasisPoints.sol";
11
- import {CurrencyTransfer} from "../../../utils/libraries/CurrencyTransfer.sol";
12
- import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
13
- import {MembershipStorage} from "../membership/MembershipStorage.sol";
14
- import {TippingBase} from "./TippingBase.sol";
15
8
 
16
9
  // contracts
17
10
  import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
18
11
  import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
19
12
  import {ERC721ABase} from "../../../diamond/facets/token/ERC721A/ERC721ABase.sol";
20
- import {PointsBase} from "../points/PointsBase.sol";
21
-
22
- contract TippingFacet is ITipping, ERC721ABase, PointsBase, Facet, ReentrancyGuard {
23
- using CustomRevert for bytes4;
13
+ import {TippingBase} from "./TippingBase.sol";
24
14
 
15
+ contract TippingFacet is ITipping, TippingBase, ERC721ABase, Facet, ReentrancyGuard {
25
16
  function __Tipping_init() external onlyInitializing {
26
17
  _addInterface(type(ITipping).interfaceId);
27
18
  }
28
19
 
29
20
  /// @inheritdoc ITipping
30
- function tip(TipRequest calldata tipRequest) external payable nonReentrant {
31
- _validateTipRequest(
32
- msg.sender,
33
- tipRequest.receiver,
34
- tipRequest.currency,
35
- tipRequest.amount
36
- );
37
-
38
- uint256 tipAmount = tipRequest.amount;
39
-
40
- if (tipRequest.currency != CurrencyTransfer.NATIVE_TOKEN) {
41
- if (msg.value != 0) UnexpectedETH.selector.revertWith();
42
- } else {
43
- if (msg.value != tipAmount) MsgValueMismatch.selector.revertWith();
44
-
45
- uint256 protocolFee = _payProtocol(msg.sender, tipAmount);
46
- tipAmount -= protocolFee;
47
-
48
- address airdropDiamond = _getAirdropDiamond();
49
- uint256 points = _getPoints(
50
- airdropDiamond,
51
- ITownsPointsBase.Action.Tip,
52
- abi.encode(protocolFee)
53
- );
54
- _mintPoints(airdropDiamond, msg.sender, points);
55
- }
21
+ function sendTip(
22
+ TipRecipientType recipientType,
23
+ bytes calldata data
24
+ ) external payable nonReentrant {
25
+ _sendTip(recipientType, data);
26
+ }
56
27
 
57
- TippingBase.tip(
58
- msg.sender,
59
- tipRequest.receiver,
60
- tipRequest.tokenId,
61
- tipRequest.currency,
62
- tipAmount
63
- );
28
+ /// @inheritdoc ITipping
29
+ function tip(TipRequest calldata tipRequest) external payable nonReentrant {
30
+ _tip(tipRequest);
31
+ }
64
32
 
65
- emit Tip(
66
- tipRequest.tokenId,
67
- tipRequest.currency,
68
- msg.sender,
69
- tipRequest.receiver,
70
- tipRequest.amount,
71
- tipRequest.messageId,
72
- tipRequest.channelId
73
- );
33
+ /// @inheritdoc ITipping
34
+ function tipsByWalletAndCurrency(
35
+ address wallet,
36
+ address currency
37
+ ) external view returns (uint256) {
38
+ return _getTipsByWallet(wallet, currency);
74
39
  }
75
40
 
76
41
  /// @inheritdoc ITipping
77
- function tippingCurrencies() external view returns (address[] memory) {
78
- return TippingBase.tippingCurrencies();
42
+ function tipCountByWalletAndCurrency(
43
+ address wallet,
44
+ address currency
45
+ ) external view returns (uint256) {
46
+ return _getTipCountByWallet(wallet, currency);
79
47
  }
80
48
 
81
49
  /// @inheritdoc ITipping
@@ -83,45 +51,21 @@ contract TippingFacet is ITipping, ERC721ABase, PointsBase, Facet, ReentrancyGua
83
51
  uint256 tokenId,
84
52
  address currency
85
53
  ) external view returns (uint256) {
86
- return TippingBase.tipsByCurrencyByTokenId(tokenId, currency);
54
+ return _getTipsByTokenId(tokenId, currency);
87
55
  }
88
56
 
89
57
  /// @inheritdoc ITipping
90
- function totalTipsByCurrency(address currency) external view returns (uint256) {
91
- return TippingBase.totalTipsByCurrency(currency);
58
+ function tippingCurrencies() external view returns (address[] memory) {
59
+ return _getTippingCurrencies();
92
60
  }
93
61
 
94
62
  /// @inheritdoc ITipping
95
- function tipAmountByCurrency(address currency) external view returns (uint256) {
96
- return TippingBase.tipAmountByCurrency(currency);
97
- }
98
-
99
- /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
100
- /* Internal */
101
- /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
102
-
103
- function _validateTipRequest(
104
- address sender,
105
- address receiver,
106
- address currency,
107
- uint256 amount
108
- ) internal pure {
109
- if (currency == address(0)) CurrencyIsZero.selector.revertWith();
110
- if (sender == receiver) CannotTipSelf.selector.revertWith();
111
- if (amount == 0) AmountIsZero.selector.revertWith();
63
+ function totalTipsByCurrency(address currency) external view returns (uint256) {
64
+ return _getTotalTipsByCurrency(currency);
112
65
  }
113
66
 
114
- function _payProtocol(address sender, uint256 amount) internal returns (uint256 protocolFee) {
115
- MembershipStorage.Layout storage ds = MembershipStorage.layout();
116
- IPlatformRequirements platform = IPlatformRequirements(ds.spaceFactory);
117
-
118
- protocolFee = BasisPoints.calculate(amount, 50); // 0.5%
119
-
120
- CurrencyTransfer.transferCurrency(
121
- CurrencyTransfer.NATIVE_TOKEN,
122
- sender,
123
- platform.getFeeRecipient(),
124
- protocolFee
125
- );
67
+ /// @inheritdoc ITipping
68
+ function tipAmountByCurrency(address currency) external view returns (uint256) {
69
+ return _getTipAmountByCurrency(currency);
126
70
  }
127
71
  }
@@ -0,0 +1,30 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ // libraries
5
+ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
6
+
7
+ library TippingStorage {
8
+ // keccak256(abi.encode(uint256(keccak256("spaces.facets.tipping.storage")) - 1)) &
9
+ // ~bytes32(uint256(0xff))
10
+ bytes32 internal constant STORAGE_SLOT =
11
+ 0xb6cb334a9eea0cca2581db4520b45ac6f03de8e3927292302206bb82168be300;
12
+
13
+ struct TippingStats {
14
+ uint256 totalTips;
15
+ uint256 tipAmount;
16
+ }
17
+
18
+ struct Layout {
19
+ EnumerableSet.AddressSet currencies;
20
+ mapping(uint256 tokenId => mapping(address currency => uint256 amount)) tipsByCurrencyByTokenId;
21
+ mapping(address currency => TippingStats) tippingStatsByCurrency;
22
+ mapping(address wallet => mapping(address currency => TippingStats)) tippingStatsByCurrencyByWallet;
23
+ }
24
+
25
+ function layout() internal pure returns (Layout storage l) {
26
+ assembly {
27
+ l.slot := STORAGE_SLOT
28
+ }
29
+ }
30
+ }