clawpowers 2.2.5 → 2.2.7

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.
Files changed (138) hide show
  1. package/CHANGELOG.md +186 -160
  2. package/COMPATIBILITY.md +48 -13
  3. package/KNOWN_LIMITATIONS.md +20 -19
  4. package/LICENSE +44 -44
  5. package/LICENSING.md +10 -10
  6. package/README.md +486 -462
  7. package/SECURITY.md +52 -52
  8. package/dist/index.d.ts +17 -5
  9. package/dist/index.js +187 -92
  10. package/dist/index.js.map +1 -1
  11. package/native/Cargo.lock +4927 -4927
  12. package/native/Cargo.toml +73 -73
  13. package/native/crates/canonical/Cargo.toml +24 -24
  14. package/native/crates/canonical/src/lib.rs +677 -673
  15. package/native/crates/compression/Cargo.toml +20 -20
  16. package/native/crates/compression/benches/compression_bench.rs +42 -42
  17. package/native/crates/compression/src/lib.rs +393 -393
  18. package/native/crates/evm-eth/Cargo.toml +13 -13
  19. package/native/crates/evm-eth/src/lib.rs +105 -105
  20. package/native/crates/fee/Cargo.toml +15 -15
  21. package/native/crates/fee/src/lib.rs +281 -281
  22. package/native/crates/index/Cargo.toml +16 -16
  23. package/native/crates/index/src/lib.rs +277 -277
  24. package/native/crates/policy/Cargo.toml +17 -17
  25. package/native/crates/policy/src/lib.rs +614 -614
  26. package/native/crates/security/Cargo.toml +22 -22
  27. package/native/crates/security/src/lib.rs +478 -478
  28. package/native/crates/tokens/Cargo.toml +13 -13
  29. package/native/crates/tokens/src/lib.rs +534 -534
  30. package/native/crates/verification/Cargo.toml +23 -23
  31. package/native/crates/verification/src/lib.rs +333 -333
  32. package/native/crates/wallet/Cargo.toml +20 -20
  33. package/native/crates/wallet/src/lib.rs +261 -261
  34. package/native/crates/x402/Cargo.toml +30 -30
  35. package/native/crates/x402/src/lib.rs +423 -423
  36. package/native/ffi/Cargo.toml +34 -34
  37. package/native/ffi/build.rs +4 -4
  38. package/native/ffi/src/lib.rs +352 -352
  39. package/native/ffi/tests/integration.rs +354 -354
  40. package/native/pyo3/Cargo.toml +26 -26
  41. package/native/pyo3/pyproject.toml +16 -16
  42. package/native/pyo3/src/lib.rs +407 -407
  43. package/native/pyo3/tests/test_smoke.py +180 -180
  44. package/native/wasm/Cargo.toml +47 -44
  45. package/native/wasm/pkg/.gitignore +6 -6
  46. package/native/wasm/pkg/clawpowers_wasm.d.ts +208 -208
  47. package/native/wasm/pkg/clawpowers_wasm.js +872 -872
  48. package/native/wasm/pkg/clawpowers_wasm_bg.wasm.d.ts +40 -40
  49. package/native/wasm/pkg/package.json +16 -16
  50. package/native/wasm/pkg-node/clawpowers_wasm.d.ts +143 -143
  51. package/native/wasm/pkg-node/clawpowers_wasm.js +798 -798
  52. package/native/wasm/pkg-node/clawpowers_wasm_bg.wasm.d.ts +40 -40
  53. package/native/wasm/pkg-node/package.json +12 -12
  54. package/native/wasm/src/lib.rs +433 -433
  55. package/package.json +13 -8
  56. package/scripts/build-wasm.mjs +59 -0
  57. package/scripts/generate_hermes_wrappers.py +211 -0
  58. package/scripts/hermes_wrapper_overrides.json +184 -0
  59. package/scripts/run-python-script.mjs +48 -0
  60. package/scripts/verify-consumer-install.mjs +109 -0
  61. package/scripts/verify-wasm-artifacts.mjs +26 -3
  62. package/scripts/verify_hermes_wrappers.py +154 -0
  63. package/skill.json +20 -0
  64. package/skills/1password/SKILL.md +34 -0
  65. package/skills/README.md +44 -0
  66. package/skills/agent-nexus-2/SKILL.md +34 -0
  67. package/skills/apple-notes/SKILL.md +34 -0
  68. package/skills/apple-reminders/SKILL.md +34 -0
  69. package/skills/autoresearch/SKILL.md +43 -0
  70. package/skills/bear-notes/SKILL.md +34 -0
  71. package/skills/blogwatcher/SKILL.md +34 -0
  72. package/skills/blucli/SKILL.md +34 -0
  73. package/skills/bluebubbles/SKILL.md +34 -0
  74. package/skills/business-strategy/SKILL.md +41 -0
  75. package/skills/camsnap/SKILL.md +34 -0
  76. package/skills/canvas/SKILL.md +34 -0
  77. package/skills/clawhub/SKILL.md +34 -0
  78. package/skills/coding-agent/SKILL.md +34 -0
  79. package/skills/coding-discipline.skill/SKILL.md +34 -0
  80. package/skills/content-writer/SKILL.md +41 -0
  81. package/skills/discord/SKILL.md +34 -0
  82. package/skills/eightctl/SKILL.md +34 -0
  83. package/skills/execution-validation.skill/SKILL.md +34 -0
  84. package/skills/gemini/SKILL.md +34 -0
  85. package/skills/gh-issues/SKILL.md +34 -0
  86. package/skills/gifgrep/SKILL.md +34 -0
  87. package/skills/github/SKILL.md +41 -0
  88. package/skills/gog/SKILL.md +34 -0
  89. package/skills/goplaces/SKILL.md +34 -0
  90. package/skills/healthcheck/SKILL.md +34 -0
  91. package/skills/himalaya/SKILL.md +34 -0
  92. package/skills/humanize/SKILL.md +41 -0
  93. package/skills/imsg/SKILL.md +34 -0
  94. package/skills/itp/SKILL.md +112 -0
  95. package/skills/mcporter/SKILL.md +34 -0
  96. package/skills/model-usage/SKILL.md +34 -0
  97. package/skills/nano-pdf/SKILL.md +34 -0
  98. package/skills/node-connect/SKILL.md +34 -0
  99. package/skills/notion/SKILL.md +34 -0
  100. package/skills/obsidian/SKILL.md +34 -0
  101. package/skills/openai-whisper/SKILL.md +34 -0
  102. package/skills/openai-whisper-api/SKILL.md +34 -0
  103. package/skills/openhue/SKILL.md +34 -0
  104. package/skills/oracle/SKILL.md +34 -0
  105. package/skills/ordercli/SKILL.md +34 -0
  106. package/skills/peekaboo/SKILL.md +34 -0
  107. package/skills/polyclaw/SKILL.md +34 -0
  108. package/skills/prospector/SKILL.md +41 -0
  109. package/skills/rsi.skill/SKILL.md +34 -0
  110. package/skills/sag/SKILL.md +34 -0
  111. package/skills/security/SKILL.md +41 -0
  112. package/skills/session-logs/SKILL.md +34 -0
  113. package/skills/sherpa-onnx-tts/SKILL.md +34 -0
  114. package/skills/skill-creator/SKILL.md +34 -0
  115. package/skills/slack/SKILL.md +34 -0
  116. package/skills/songsee/SKILL.md +34 -0
  117. package/skills/sonoscli/SKILL.md +34 -0
  118. package/skills/spotify-player/SKILL.md +34 -0
  119. package/skills/strykr-prism/SKILL.md +41 -0
  120. package/skills/summarize/SKILL.md +34 -0
  121. package/skills/taskbridge/SKILL.md +34 -0
  122. package/skills/things-mac/SKILL.md +34 -0
  123. package/skills/tmux/SKILL.md +34 -0
  124. package/skills/trello/SKILL.md +34 -0
  125. package/skills/validator-agent/SKILL.md +41 -0
  126. package/skills/video-frames/SKILL.md +34 -0
  127. package/skills/voice-call/SKILL.md +34 -0
  128. package/skills/wacli/SKILL.md +34 -0
  129. package/skills/weather/SKILL.md +34 -0
  130. package/skills/webmcp-payments/SKILL.md +41 -0
  131. package/skills/xurl/SKILL.md +34 -0
  132. package/src/skills/catalog.ts +435 -435
  133. package/src/skills/executor.ts +56 -56
  134. package/src/skills/index.ts +3 -3
  135. package/src/skills/itp/SKILL.md +112 -112
  136. package/src/skills/loader.ts +262 -193
  137. package/native/ffi/index.node +0 -0
  138. package/native/wasm/pkg-node/.gitignore +0 -6
@@ -1,281 +1,281 @@
1
- //! clawpowers-fee — Fee schedule calculation for transactions and swaps.
2
- //!
3
- //! [`FeeSchedule`] holds basis-point rates for different operation types and
4
- //! computes the fee for a given [`TokenAmount`].
5
-
6
- use alloy_primitives::Address;
7
- use clawpowers_tokens::{TokenAmount, TokenError};
8
- use serde::{Deserialize, Serialize};
9
- use thiserror::Error;
10
-
11
- // ---------------------------------------------------------------------------
12
- // Constants
13
- // ---------------------------------------------------------------------------
14
-
15
- /// Default transaction fee: 77 bps = 0.77%.
16
- pub const DEFAULT_TX_FEE_BPS: u64 = 77;
17
-
18
- /// Default swap fee: 30 bps = 0.30%.
19
- pub const DEFAULT_SWAP_FEE_BPS: u64 = 30;
20
-
21
- /// Placeholder fee recipient address used by [`FeeSchedule::default`].
22
- pub const PLACEHOLDER_FEE_RECIPIENT: Address = Address::ZERO;
23
-
24
- // ---------------------------------------------------------------------------
25
- // FeeType
26
- // ---------------------------------------------------------------------------
27
-
28
- /// Fee classification.
29
- #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30
- #[serde(rename_all = "snake_case")]
31
- pub enum FeeType {
32
- /// Standard on-chain transaction fee.
33
- Transaction,
34
- /// DEX swap fee.
35
- Swap,
36
- /// Custom fee with explicit basis points.
37
- Custom(u64),
38
- }
39
-
40
- // ---------------------------------------------------------------------------
41
- // Errors
42
- // ---------------------------------------------------------------------------
43
-
44
- /// Errors produced by fee operations.
45
- #[derive(Debug, Error, PartialEq, Eq)]
46
- pub enum FeeError {
47
- /// Token arithmetic failed.
48
- #[error("token arithmetic error: {0}")]
49
- Token(String),
50
- /// Basis-point rate exceeds 10 000 (100%).
51
- #[error("invalid basis points: {0} (max 10000)")]
52
- InvalidBps(u64),
53
- /// Computed fee would exceed the gross amount.
54
- #[error("fee ({fee_bps} bps) would exceed gross amount")]
55
- FeeExceedsAmount {
56
- /// The basis-point rate that caused the violation.
57
- fee_bps: u64,
58
- },
59
- }
60
-
61
- impl From<TokenError> for FeeError {
62
- fn from(e: TokenError) -> Self {
63
- FeeError::Token(e.to_string())
64
- }
65
- }
66
-
67
- // ---------------------------------------------------------------------------
68
- // FeeCalculation
69
- // ---------------------------------------------------------------------------
70
-
71
- /// The result of a fee calculation.
72
- #[derive(Debug, Clone)]
73
- pub struct FeeCalculation {
74
- /// The original, pre-fee amount.
75
- pub gross_amount: TokenAmount,
76
- /// The fee portion deducted from `gross_amount`.
77
- pub fee_amount: TokenAmount,
78
- /// The amount remaining after fee deduction.
79
- pub net_amount: TokenAmount,
80
- /// The address that will receive the fee.
81
- pub fee_recipient: Address,
82
- /// The type of fee that was applied.
83
- pub fee_type: FeeType,
84
- }
85
-
86
- // ---------------------------------------------------------------------------
87
- // FeeSchedule
88
- // ---------------------------------------------------------------------------
89
-
90
- /// Configures fee rates and the recipient address for the protocol.
91
- #[derive(Debug, Clone)]
92
- pub struct FeeSchedule {
93
- /// Transaction fee in basis points (default: 77 = 0.77%).
94
- pub tx_fee_bps: u64,
95
- /// Swap fee in basis points (default: 30 = 0.30%).
96
- pub swap_fee_bps: u64,
97
- /// Address that accumulates collected fees.
98
- pub fee_recipient: Address,
99
- }
100
-
101
- impl Default for FeeSchedule {
102
- fn default() -> Self {
103
- Self {
104
- tx_fee_bps: DEFAULT_TX_FEE_BPS,
105
- swap_fee_bps: DEFAULT_SWAP_FEE_BPS,
106
- fee_recipient: PLACEHOLDER_FEE_RECIPIENT,
107
- }
108
- }
109
- }
110
-
111
- impl FeeSchedule {
112
- /// Creates a new [`FeeSchedule`] with explicit parameters.
113
- pub fn new(tx_fee_bps: u64, swap_fee_bps: u64, fee_recipient: Address) -> Self {
114
- Self {
115
- tx_fee_bps,
116
- swap_fee_bps,
117
- fee_recipient,
118
- }
119
- }
120
-
121
- /// Returns the effective basis-point rate for the given `fee_type`.
122
- fn bps_for(&self, fee_type: &FeeType) -> u64 {
123
- match fee_type {
124
- FeeType::Transaction => self.tx_fee_bps,
125
- FeeType::Swap => self.swap_fee_bps,
126
- FeeType::Custom(bps) => *bps,
127
- }
128
- }
129
-
130
- /// Calculates a fee for `amount` and the given `fee_type`.
131
- ///
132
- /// # Errors
133
- ///
134
- /// - [`FeeError::InvalidBps`] — rate exceeds 10 000.
135
- /// - [`FeeError::Token`] — arithmetic overflow.
136
- /// - [`FeeError::FeeExceedsAmount`] — fee ≥ gross.
137
- pub fn calculate(
138
- &self,
139
- amount: TokenAmount,
140
- fee_type: FeeType,
141
- ) -> Result<FeeCalculation, FeeError> {
142
- let bps = self.bps_for(&fee_type);
143
- if bps > 10_000 {
144
- return Err(FeeError::InvalidBps(bps));
145
- }
146
-
147
- let fee_amount = amount
148
- .checked_mul_bps(bps)
149
- .ok_or_else(|| FeeError::Token("overflow in checked_mul_bps".to_string()))?;
150
-
151
- let net_amount = amount
152
- .sub(&fee_amount)
153
- .map_err(|_| FeeError::FeeExceedsAmount { fee_bps: bps })?;
154
-
155
- Ok(FeeCalculation {
156
- gross_amount: amount,
157
- fee_amount,
158
- net_amount,
159
- fee_recipient: self.fee_recipient,
160
- fee_type,
161
- })
162
- }
163
- }
164
-
165
- // ---------------------------------------------------------------------------
166
- // Tests
167
- // ---------------------------------------------------------------------------
168
-
169
- #[cfg(test)]
170
- mod tests {
171
- use super::*;
172
- use alloy_primitives::Address;
173
-
174
- fn usdc(amount: f64) -> TokenAmount {
175
- TokenAmount::from_human(amount, 6)
176
- }
177
-
178
- fn eth(amount: f64) -> TokenAmount {
179
- TokenAmount::from_human(amount, 18)
180
- }
181
-
182
- #[test]
183
- fn default_fee_schedule_values() {
184
- let s = FeeSchedule::default();
185
- assert_eq!(s.tx_fee_bps, 77);
186
- assert_eq!(s.swap_fee_bps, 30);
187
- assert_eq!(s.fee_recipient, Address::ZERO);
188
- }
189
-
190
- #[test]
191
- fn tx_fee_usdc_1000() {
192
- let s = FeeSchedule::default();
193
- let calc = s.calculate(usdc(1000.0), FeeType::Transaction).unwrap();
194
- // 1000 USDC × 77/10000 = 7.7 USDC
195
- assert!((calc.fee_amount.to_human() - 7.7).abs() < 0.000_001);
196
- assert!((calc.net_amount.to_human() - 992.3).abs() < 0.000_001);
197
- assert_eq!(calc.fee_type, FeeType::Transaction);
198
- }
199
-
200
- #[test]
201
- fn tx_fee_usdc_1() {
202
- let s = FeeSchedule::default();
203
- let calc = s.calculate(usdc(1.0), FeeType::Transaction).unwrap();
204
- // 1 USDC × 77/10000 = 0.0077 USDC
205
- assert!((calc.fee_amount.to_human() - 0.0077).abs() < 0.000_001);
206
- }
207
-
208
- #[test]
209
- fn swap_fee_usdc_1000() {
210
- let s = FeeSchedule::default();
211
- let calc = s.calculate(usdc(1000.0), FeeType::Swap).unwrap();
212
- // 1000 USDC × 30/10000 = 3 USDC
213
- assert!((calc.fee_amount.to_human() - 3.0).abs() < 0.000_001);
214
- assert_eq!(calc.fee_type, FeeType::Swap);
215
- }
216
-
217
- #[test]
218
- fn swap_fee_custom_50bps() {
219
- let s = FeeSchedule::new(30, 50, Address::ZERO);
220
- let calc = s.calculate(usdc(200.0), FeeType::Swap).unwrap();
221
- // 200 USDC × 50/10000 = 1 USDC
222
- assert!((calc.fee_amount.to_human() - 1.0).abs() < 0.000_001);
223
- }
224
-
225
- #[test]
226
- fn tx_fee_eth_1() {
227
- let s = FeeSchedule::default();
228
- let calc = s.calculate(eth(1.0), FeeType::Transaction).unwrap();
229
- // 1 ETH × 77/10000 = 0.0077 ETH
230
- assert!((calc.fee_amount.to_human() - 0.0077).abs() < 0.000_001);
231
- }
232
-
233
- #[test]
234
- fn swap_fee_eth_10() {
235
- let s = FeeSchedule::default();
236
- let calc = s.calculate(eth(10.0), FeeType::Swap).unwrap();
237
- // 10 ETH × 30/10000 = 0.03 ETH
238
- assert!((calc.fee_amount.to_human() - 0.03).abs() < 0.000_001);
239
- }
240
-
241
- #[test]
242
- fn zero_amount_returns_zero_fee() {
243
- let s = FeeSchedule::default();
244
- let calc = s.calculate(usdc(0.0), FeeType::Transaction).unwrap();
245
- assert!(calc.fee_amount.is_zero());
246
- assert!(calc.net_amount.is_zero());
247
- }
248
-
249
- #[test]
250
- fn custom_fee_type_100bps() {
251
- let s = FeeSchedule::default();
252
- let calc = s.calculate(usdc(100.0), FeeType::Custom(100)).unwrap();
253
- // 100 USDC × 100/10000 = 1 USDC
254
- assert!((calc.fee_amount.to_human() - 1.0).abs() < 0.000_001);
255
- }
256
-
257
- #[test]
258
- fn fee_recipient_propagated() {
259
- use alloy_primitives::address;
260
- let recipient = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
261
- let s = FeeSchedule::new(77, 30, recipient);
262
- let calc = s.calculate(usdc(500.0), FeeType::Transaction).unwrap();
263
- assert_eq!(calc.fee_recipient, recipient);
264
- }
265
-
266
- #[test]
267
- fn gross_equals_fee_plus_net() {
268
- let s = FeeSchedule::default();
269
- let gross = usdc(123.456789);
270
- let calc = s.calculate(gross.clone(), FeeType::Swap).unwrap();
271
- let reconstructed = calc.fee_amount.add(&calc.net_amount).unwrap();
272
- assert_eq!(reconstructed, calc.gross_amount);
273
- }
274
-
275
- #[test]
276
- fn bps_over_10000_is_rejected() {
277
- let s = FeeSchedule::new(10_001, 30, Address::ZERO);
278
- let err = s.calculate(usdc(1.0), FeeType::Transaction).unwrap_err();
279
- assert_eq!(err, FeeError::InvalidBps(10_001));
280
- }
281
- }
1
+ //! clawpowers-fee — Fee schedule calculation for transactions and swaps.
2
+ //!
3
+ //! [`FeeSchedule`] holds basis-point rates for different operation types and
4
+ //! computes the fee for a given [`TokenAmount`].
5
+
6
+ use alloy_primitives::Address;
7
+ use clawpowers_tokens::{TokenAmount, TokenError};
8
+ use serde::{Deserialize, Serialize};
9
+ use thiserror::Error;
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Constants
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /// Default transaction fee: 77 bps = 0.77%.
16
+ pub const DEFAULT_TX_FEE_BPS: u64 = 77;
17
+
18
+ /// Default swap fee: 30 bps = 0.30%.
19
+ pub const DEFAULT_SWAP_FEE_BPS: u64 = 30;
20
+
21
+ /// Placeholder fee recipient address used by [`FeeSchedule::default`].
22
+ pub const PLACEHOLDER_FEE_RECIPIENT: Address = Address::ZERO;
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // FeeType
26
+ // ---------------------------------------------------------------------------
27
+
28
+ /// Fee classification.
29
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30
+ #[serde(rename_all = "snake_case")]
31
+ pub enum FeeType {
32
+ /// Standard on-chain transaction fee.
33
+ Transaction,
34
+ /// DEX swap fee.
35
+ Swap,
36
+ /// Custom fee with explicit basis points.
37
+ Custom(u64),
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Errors
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /// Errors produced by fee operations.
45
+ #[derive(Debug, Error, PartialEq, Eq)]
46
+ pub enum FeeError {
47
+ /// Token arithmetic failed.
48
+ #[error("token arithmetic error: {0}")]
49
+ Token(String),
50
+ /// Basis-point rate exceeds 10 000 (100%).
51
+ #[error("invalid basis points: {0} (max 10000)")]
52
+ InvalidBps(u64),
53
+ /// Computed fee would exceed the gross amount.
54
+ #[error("fee ({fee_bps} bps) would exceed gross amount")]
55
+ FeeExceedsAmount {
56
+ /// The basis-point rate that caused the violation.
57
+ fee_bps: u64,
58
+ },
59
+ }
60
+
61
+ impl From<TokenError> for FeeError {
62
+ fn from(e: TokenError) -> Self {
63
+ FeeError::Token(e.to_string())
64
+ }
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // FeeCalculation
69
+ // ---------------------------------------------------------------------------
70
+
71
+ /// The result of a fee calculation.
72
+ #[derive(Debug, Clone)]
73
+ pub struct FeeCalculation {
74
+ /// The original, pre-fee amount.
75
+ pub gross_amount: TokenAmount,
76
+ /// The fee portion deducted from `gross_amount`.
77
+ pub fee_amount: TokenAmount,
78
+ /// The amount remaining after fee deduction.
79
+ pub net_amount: TokenAmount,
80
+ /// The address that will receive the fee.
81
+ pub fee_recipient: Address,
82
+ /// The type of fee that was applied.
83
+ pub fee_type: FeeType,
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // FeeSchedule
88
+ // ---------------------------------------------------------------------------
89
+
90
+ /// Configures fee rates and the recipient address for the protocol.
91
+ #[derive(Debug, Clone)]
92
+ pub struct FeeSchedule {
93
+ /// Transaction fee in basis points (default: 77 = 0.77%).
94
+ pub tx_fee_bps: u64,
95
+ /// Swap fee in basis points (default: 30 = 0.30%).
96
+ pub swap_fee_bps: u64,
97
+ /// Address that accumulates collected fees.
98
+ pub fee_recipient: Address,
99
+ }
100
+
101
+ impl Default for FeeSchedule {
102
+ fn default() -> Self {
103
+ Self {
104
+ tx_fee_bps: DEFAULT_TX_FEE_BPS,
105
+ swap_fee_bps: DEFAULT_SWAP_FEE_BPS,
106
+ fee_recipient: PLACEHOLDER_FEE_RECIPIENT,
107
+ }
108
+ }
109
+ }
110
+
111
+ impl FeeSchedule {
112
+ /// Creates a new [`FeeSchedule`] with explicit parameters.
113
+ pub fn new(tx_fee_bps: u64, swap_fee_bps: u64, fee_recipient: Address) -> Self {
114
+ Self {
115
+ tx_fee_bps,
116
+ swap_fee_bps,
117
+ fee_recipient,
118
+ }
119
+ }
120
+
121
+ /// Returns the effective basis-point rate for the given `fee_type`.
122
+ fn bps_for(&self, fee_type: &FeeType) -> u64 {
123
+ match fee_type {
124
+ FeeType::Transaction => self.tx_fee_bps,
125
+ FeeType::Swap => self.swap_fee_bps,
126
+ FeeType::Custom(bps) => *bps,
127
+ }
128
+ }
129
+
130
+ /// Calculates a fee for `amount` and the given `fee_type`.
131
+ ///
132
+ /// # Errors
133
+ ///
134
+ /// - [`FeeError::InvalidBps`] — rate exceeds 10 000.
135
+ /// - [`FeeError::Token`] — arithmetic overflow.
136
+ /// - [`FeeError::FeeExceedsAmount`] — fee ≥ gross.
137
+ pub fn calculate(
138
+ &self,
139
+ amount: TokenAmount,
140
+ fee_type: FeeType,
141
+ ) -> Result<FeeCalculation, FeeError> {
142
+ let bps = self.bps_for(&fee_type);
143
+ if bps > 10_000 {
144
+ return Err(FeeError::InvalidBps(bps));
145
+ }
146
+
147
+ let fee_amount = amount
148
+ .checked_mul_bps(bps)
149
+ .ok_or_else(|| FeeError::Token("overflow in checked_mul_bps".to_string()))?;
150
+
151
+ let net_amount = amount
152
+ .sub(&fee_amount)
153
+ .map_err(|_| FeeError::FeeExceedsAmount { fee_bps: bps })?;
154
+
155
+ Ok(FeeCalculation {
156
+ gross_amount: amount,
157
+ fee_amount,
158
+ net_amount,
159
+ fee_recipient: self.fee_recipient,
160
+ fee_type,
161
+ })
162
+ }
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Tests
167
+ // ---------------------------------------------------------------------------
168
+
169
+ #[cfg(test)]
170
+ mod tests {
171
+ use super::*;
172
+ use alloy_primitives::Address;
173
+
174
+ fn usdc(amount: f64) -> TokenAmount {
175
+ TokenAmount::from_human(amount, 6)
176
+ }
177
+
178
+ fn eth(amount: f64) -> TokenAmount {
179
+ TokenAmount::from_human(amount, 18)
180
+ }
181
+
182
+ #[test]
183
+ fn default_fee_schedule_values() {
184
+ let s = FeeSchedule::default();
185
+ assert_eq!(s.tx_fee_bps, 77);
186
+ assert_eq!(s.swap_fee_bps, 30);
187
+ assert_eq!(s.fee_recipient, Address::ZERO);
188
+ }
189
+
190
+ #[test]
191
+ fn tx_fee_usdc_1000() {
192
+ let s = FeeSchedule::default();
193
+ let calc = s.calculate(usdc(1000.0), FeeType::Transaction).unwrap();
194
+ // 1000 USDC × 77/10000 = 7.7 USDC
195
+ assert!((calc.fee_amount.to_human() - 7.7).abs() < 0.000_001);
196
+ assert!((calc.net_amount.to_human() - 992.3).abs() < 0.000_001);
197
+ assert_eq!(calc.fee_type, FeeType::Transaction);
198
+ }
199
+
200
+ #[test]
201
+ fn tx_fee_usdc_1() {
202
+ let s = FeeSchedule::default();
203
+ let calc = s.calculate(usdc(1.0), FeeType::Transaction).unwrap();
204
+ // 1 USDC × 77/10000 = 0.0077 USDC
205
+ assert!((calc.fee_amount.to_human() - 0.0077).abs() < 0.000_001);
206
+ }
207
+
208
+ #[test]
209
+ fn swap_fee_usdc_1000() {
210
+ let s = FeeSchedule::default();
211
+ let calc = s.calculate(usdc(1000.0), FeeType::Swap).unwrap();
212
+ // 1000 USDC × 30/10000 = 3 USDC
213
+ assert!((calc.fee_amount.to_human() - 3.0).abs() < 0.000_001);
214
+ assert_eq!(calc.fee_type, FeeType::Swap);
215
+ }
216
+
217
+ #[test]
218
+ fn swap_fee_custom_50bps() {
219
+ let s = FeeSchedule::new(30, 50, Address::ZERO);
220
+ let calc = s.calculate(usdc(200.0), FeeType::Swap).unwrap();
221
+ // 200 USDC × 50/10000 = 1 USDC
222
+ assert!((calc.fee_amount.to_human() - 1.0).abs() < 0.000_001);
223
+ }
224
+
225
+ #[test]
226
+ fn tx_fee_eth_1() {
227
+ let s = FeeSchedule::default();
228
+ let calc = s.calculate(eth(1.0), FeeType::Transaction).unwrap();
229
+ // 1 ETH × 77/10000 = 0.0077 ETH
230
+ assert!((calc.fee_amount.to_human() - 0.0077).abs() < 0.000_001);
231
+ }
232
+
233
+ #[test]
234
+ fn swap_fee_eth_10() {
235
+ let s = FeeSchedule::default();
236
+ let calc = s.calculate(eth(10.0), FeeType::Swap).unwrap();
237
+ // 10 ETH × 30/10000 = 0.03 ETH
238
+ assert!((calc.fee_amount.to_human() - 0.03).abs() < 0.000_001);
239
+ }
240
+
241
+ #[test]
242
+ fn zero_amount_returns_zero_fee() {
243
+ let s = FeeSchedule::default();
244
+ let calc = s.calculate(usdc(0.0), FeeType::Transaction).unwrap();
245
+ assert!(calc.fee_amount.is_zero());
246
+ assert!(calc.net_amount.is_zero());
247
+ }
248
+
249
+ #[test]
250
+ fn custom_fee_type_100bps() {
251
+ let s = FeeSchedule::default();
252
+ let calc = s.calculate(usdc(100.0), FeeType::Custom(100)).unwrap();
253
+ // 100 USDC × 100/10000 = 1 USDC
254
+ assert!((calc.fee_amount.to_human() - 1.0).abs() < 0.000_001);
255
+ }
256
+
257
+ #[test]
258
+ fn fee_recipient_propagated() {
259
+ use alloy_primitives::address;
260
+ let recipient = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
261
+ let s = FeeSchedule::new(77, 30, recipient);
262
+ let calc = s.calculate(usdc(500.0), FeeType::Transaction).unwrap();
263
+ assert_eq!(calc.fee_recipient, recipient);
264
+ }
265
+
266
+ #[test]
267
+ fn gross_equals_fee_plus_net() {
268
+ let s = FeeSchedule::default();
269
+ let gross = usdc(123.456789);
270
+ let calc = s.calculate(gross.clone(), FeeType::Swap).unwrap();
271
+ let reconstructed = calc.fee_amount.add(&calc.net_amount).unwrap();
272
+ assert_eq!(reconstructed, calc.gross_amount);
273
+ }
274
+
275
+ #[test]
276
+ fn bps_over_10000_is_rejected() {
277
+ let s = FeeSchedule::new(10_001, 30, Address::ZERO);
278
+ let err = s.calculate(usdc(1.0), FeeType::Transaction).unwrap_err();
279
+ assert_eq!(err, FeeError::InvalidBps(10_001));
280
+ }
281
+ }
@@ -1,16 +1,16 @@
1
- [package]
2
- name = "clawpowers-index"
3
- version.workspace = true
4
- edition.workspace = true
5
- license.workspace = true
6
-
7
- [dependencies]
8
- serde = { workspace = true }
9
- serde_json = { workspace = true }
10
- thiserror = { workspace = true }
11
- tracing = { workspace = true }
12
- uuid = { workspace = true }
13
- clawpowers-compression = { path = "../compression" }
14
-
15
- [dev-dependencies]
16
- rand = { workspace = true }
1
+ [package]
2
+ name = "clawpowers-index"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ license.workspace = true
6
+
7
+ [dependencies]
8
+ serde = { workspace = true }
9
+ serde_json = { workspace = true }
10
+ thiserror = { workspace = true }
11
+ tracing = { workspace = true }
12
+ uuid = { workspace = true }
13
+ clawpowers-compression = { path = "../compression" }
14
+
15
+ [dev-dependencies]
16
+ rand = { workspace = true }