88 isValidHex ,
99 isValid32ByteHex ,
1010 isValid256BitHex ,
11+ isValidFacilitatorFee ,
1112 validateSettlementRouter ,
1213 validateSettlementExtra ,
1314 validateNetwork ,
@@ -81,6 +82,110 @@ describe("validation utilities", () => {
8182 } ) ;
8283 } ) ;
8384
85+ describe ( "isValidFacilitatorFee" , ( ) => {
86+ describe ( "decimal format (atomic units)" , ( ) => {
87+ it ( "should return true for valid decimal strings" , ( ) => {
88+ expect ( isValidFacilitatorFee ( "0" ) ) . toBe ( true ) ;
89+ expect ( isValidFacilitatorFee ( "10000" ) ) . toBe ( true ) ;
90+ expect ( isValidFacilitatorFee ( "1000000" ) ) . toBe ( true ) ; // 1 USDC (6 decimals)
91+ expect ( isValidFacilitatorFee ( "1000000000000000000" ) ) . toBe ( true ) ; // 1 ETH (18 decimals)
92+ expect ( isValidFacilitatorFee ( "1" ) ) . toBe ( true ) ;
93+ expect ( isValidFacilitatorFee ( "123456789" ) ) . toBe ( true ) ;
94+ } ) ;
95+
96+ it ( "should return false for non-numeric decimal strings" , ( ) => {
97+ expect ( isValidFacilitatorFee ( "abc" ) ) . toBe ( false ) ;
98+ expect ( isValidFacilitatorFee ( "100abc" ) ) . toBe ( false ) ;
99+ expect ( isValidFacilitatorFee ( "12.34" ) ) . toBe ( false ) ; // decimal point not allowed
100+ expect ( isValidFacilitatorFee ( "-100" ) ) . toBe ( false ) ; // negative sign not allowed
101+ expect ( isValidFacilitatorFee ( "+100" ) ) . toBe ( false ) ; // plus sign not allowed
102+ } ) ;
103+
104+ it ( "should return false for decimal values that exceed uint256" , ( ) => {
105+ // A value with more than 78 digits exceeds uint256 max (2^256-1 ≈ 1.16e77)
106+ const tooLarge = "1" + "0" . repeat ( 78 ) ;
107+ expect ( isValidFacilitatorFee ( tooLarge ) ) . toBe ( false ) ;
108+
109+ // Test with an even larger number
110+ const wayTooLarge = "9" . repeat ( 100 ) ;
111+ expect ( isValidFacilitatorFee ( wayTooLarge ) ) . toBe ( false ) ;
112+ } ) ;
113+
114+ it ( "should accept values within uint256 range" , ( ) => {
115+ // Max uint256 is approximately 1.16e77, so 77-78 digits is the boundary
116+ expect ( isValidFacilitatorFee ( "1" + "0" . repeat ( 76 ) ) ) . toBe ( true ) ;
117+ expect ( isValidFacilitatorFee ( "115792089237316195423570985008687907853269984665640564039457584007913129639935" ) ) . toBe ( true ) ; // 2^256 - 1
118+ } ) ;
119+ } ) ;
120+
121+ describe ( "hex format" , ( ) => {
122+ it ( "should return true for valid hex strings" , ( ) => {
123+ expect ( isValidFacilitatorFee ( "0x0" ) ) . toBe ( true ) ;
124+ expect ( isValidFacilitatorFee ( "0x1" ) ) . toBe ( true ) ;
125+ expect ( isValidFacilitatorFee ( "0x2710" ) ) . toBe ( true ) ; // 10000 in decimal
126+ expect ( isValidFacilitatorFee ( "0x186A0" ) ) . toBe ( true ) ; // 100000 in decimal (0.1 USDC)
127+ expect ( isValidFacilitatorFee ( "0x" + "FF" . repeat ( 32 ) ) ) . toBe ( true ) ;
128+ expect ( isValidFacilitatorFee ( "0xFF" ) ) . toBe ( true ) ;
129+ expect ( isValidFacilitatorFee ( "0xabcdef" ) ) . toBe ( true ) ;
130+ expect ( isValidFacilitatorFee ( "0xABCDEF" ) ) . toBe ( true ) ;
131+ } ) ;
132+
133+ it ( "should return false for invalid hex strings" , ( ) => {
134+ expect ( isValidFacilitatorFee ( "0x" + "FF" . repeat ( 33 ) ) ) . toBe ( false ) ; // > 256 bits
135+ expect ( isValidFacilitatorFee ( "0xGG" ) ) . toBe ( false ) ;
136+ expect ( isValidFacilitatorFee ( "0x" ) ) . toBe ( false ) ; // empty hex
137+ } ) ;
138+
139+ it ( "should accept odd-length hex strings" , ( ) => {
140+ expect ( isValidFacilitatorFee ( "0x123" ) ) . toBe ( true ) ; // odd-length hex is valid numeric input
141+ expect ( isValidFacilitatorFee ( "0x1" ) ) . toBe ( true ) ; // single digit
142+ expect ( isValidFacilitatorFee ( "0xFFF" ) ) . toBe ( true ) ; // 4095
143+ } ) ;
144+ } ) ;
145+
146+ describe ( "edge cases" , ( ) => {
147+ it ( "should return false for empty string" , ( ) => {
148+ expect ( isValidFacilitatorFee ( "" ) ) . toBe ( false ) ;
149+ } ) ;
150+
151+ it ( "should return false for non-string types" , ( ) => {
152+ expect ( isValidFacilitatorFee ( null as unknown as string ) ) . toBe ( false ) ;
153+ expect ( isValidFacilitatorFee ( undefined as unknown as string ) ) . toBe ( false ) ;
154+ expect ( isValidFacilitatorFee ( 10000 as unknown as string ) ) . toBe ( false ) ;
155+ } ) ;
156+
157+ it ( "should handle uppercase 0X prefix" , ( ) => {
158+ expect ( isValidFacilitatorFee ( "0X2710" ) ) . toBe ( true ) ;
159+ expect ( isValidFacilitatorFee ( "0XABCDEF" ) ) . toBe ( true ) ;
160+ } ) ;
161+
162+ it ( "should return false for malformed inputs" , ( ) => {
163+ expect ( isValidFacilitatorFee ( "10000 " ) ) . toBe ( false ) ; // trailing space
164+ expect ( isValidFacilitatorFee ( " 10000" ) ) . toBe ( false ) ; // leading space
165+ expect ( isValidFacilitatorFee ( "0x 2710" ) ) . toBe ( false ) ; // space after prefix
166+ expect ( isValidFacilitatorFee ( "0x2710 " ) ) . toBe ( false ) ; // trailing space
167+ } ) ;
168+ } ) ;
169+
170+ describe ( "cross-format equivalence" , ( ) => {
171+ it ( "should accept equivalent values in both formats" , ( ) => {
172+ // 10000 in decimal and hex
173+ expect ( isValidFacilitatorFee ( "10000" ) ) . toBe ( true ) ;
174+ expect ( isValidFacilitatorFee ( "0x2710" ) ) . toBe ( true ) ;
175+
176+ // 0 in both formats
177+ expect ( isValidFacilitatorFee ( "0" ) ) . toBe ( true ) ;
178+ expect ( isValidFacilitatorFee ( "0x0" ) ) . toBe ( true ) ;
179+
180+ // Large number in both formats
181+ const largeDecimal = "1000000000000000000" ; // 1e18
182+ const largeHex = "0xde0b6b3a7640000" ; // Same value in hex
183+ expect ( isValidFacilitatorFee ( largeDecimal ) ) . toBe ( true ) ;
184+ expect ( isValidFacilitatorFee ( largeHex ) ) . toBe ( true ) ;
185+ } ) ;
186+ } ) ;
187+ } ) ;
188+
84189 describe ( "validateSettlementRouter" , ( ) => {
85190 it ( "should validate valid router address" , ( ) => {
86191 const result = validateSettlementRouter (
@@ -132,12 +237,26 @@ describe("validation utilities", () => {
132237 } ) ;
133238
134239 describe ( "validateSettlementExtra" , ( ) => {
135- it ( "should validate valid settlement extra" , ( ) => {
240+ it ( "should validate valid settlement extra with hex facilitatorFee " , ( ) => {
136241 const extra = {
137242 settlementRouter : MOCK_ADDRESSES . settlementRouter ,
138243 salt : MOCK_VALUES . salt ,
139244 payTo : MOCK_ADDRESSES . merchant ,
140- facilitatorFee : MOCK_VALUES . facilitatorFee ,
245+ facilitatorFee : MOCK_VALUES . facilitatorFee , // hex format
246+ hook : MOCK_ADDRESSES . hook ,
247+ hookData : MOCK_VALUES . hookData ,
248+ } ;
249+
250+ const result = validateSettlementExtra ( extra ) ;
251+ expect ( result ) . toEqual ( extra ) ;
252+ } ) ;
253+
254+ it ( "should validate valid settlement extra with decimal facilitatorFee" , ( ) => {
255+ const extra = {
256+ settlementRouter : MOCK_ADDRESSES . settlementRouter ,
257+ salt : MOCK_VALUES . salt ,
258+ payTo : MOCK_ADDRESSES . merchant ,
259+ facilitatorFee : "100000" , // decimal format (same as 0x186A0)
141260 hook : MOCK_ADDRESSES . hook ,
142261 hookData : MOCK_VALUES . hookData ,
143262 } ;
@@ -146,6 +265,34 @@ describe("validation utilities", () => {
146265 expect ( result ) . toEqual ( extra ) ;
147266 } ) ;
148267
268+ it ( "should validate facilitatorFee as decimal zero" , ( ) => {
269+ const extra = {
270+ settlementRouter : MOCK_ADDRESSES . settlementRouter ,
271+ salt : MOCK_VALUES . salt ,
272+ payTo : MOCK_ADDRESSES . merchant ,
273+ facilitatorFee : "0" , // decimal zero
274+ hook : MOCK_ADDRESSES . hook ,
275+ hookData : MOCK_VALUES . hookData ,
276+ } ;
277+
278+ const result = validateSettlementExtra ( extra ) ;
279+ expect ( result . facilitatorFee ) . toBe ( "0" ) ;
280+ } ) ;
281+
282+ it ( "should validate facilitatorFee as hex zero" , ( ) => {
283+ const extra = {
284+ settlementRouter : MOCK_ADDRESSES . settlementRouter ,
285+ salt : MOCK_VALUES . salt ,
286+ payTo : MOCK_ADDRESSES . merchant ,
287+ facilitatorFee : "0x0" , // hex zero
288+ hook : MOCK_ADDRESSES . hook ,
289+ hookData : MOCK_VALUES . hookData ,
290+ } ;
291+
292+ const result = validateSettlementExtra ( extra ) ;
293+ expect ( result . facilitatorFee ) . toBe ( "0x0" ) ;
294+ } ) ;
295+
149296 it ( "should throw for missing extra" , ( ) => {
150297 expect ( ( ) => {
151298 validateSettlementExtra ( null ) ;
@@ -195,6 +342,51 @@ describe("validation utilities", () => {
195342 validateSettlementExtra ( extra ) ;
196343 } ) . toThrow ( FacilitatorValidationError ) ;
197344 } ) ;
345+
346+ it ( "should throw for invalid facilitatorFee (non-numeric)" , ( ) => {
347+ const extra = {
348+ settlementRouter : MOCK_ADDRESSES . settlementRouter ,
349+ salt : MOCK_VALUES . salt ,
350+ payTo : MOCK_ADDRESSES . merchant ,
351+ facilitatorFee : "invalid-fee" ,
352+ hook : MOCK_ADDRESSES . hook ,
353+ hookData : MOCK_VALUES . hookData ,
354+ } ;
355+
356+ expect ( ( ) => {
357+ validateSettlementExtra ( extra ) ;
358+ } ) . toThrow ( FacilitatorValidationError ) ;
359+ } ) ;
360+
361+ it ( "should throw for invalid facilitatorFee (negative)" , ( ) => {
362+ const extra = {
363+ settlementRouter : MOCK_ADDRESSES . settlementRouter ,
364+ salt : MOCK_VALUES . salt ,
365+ payTo : MOCK_ADDRESSES . merchant ,
366+ facilitatorFee : "-10000" ,
367+ hook : MOCK_ADDRESSES . hook ,
368+ hookData : MOCK_VALUES . hookData ,
369+ } ;
370+
371+ expect ( ( ) => {
372+ validateSettlementExtra ( extra ) ;
373+ } ) . toThrow ( FacilitatorValidationError ) ;
374+ } ) ;
375+
376+ it ( "should throw for invalid facilitatorFee (malformed hex)" , ( ) => {
377+ const extra = {
378+ settlementRouter : MOCK_ADDRESSES . settlementRouter ,
379+ salt : MOCK_VALUES . salt ,
380+ payTo : MOCK_ADDRESSES . merchant ,
381+ facilitatorFee : "0xXYZ123" ,
382+ hook : MOCK_ADDRESSES . hook ,
383+ hookData : MOCK_VALUES . hookData ,
384+ } ;
385+
386+ expect ( ( ) => {
387+ validateSettlementExtra ( extra ) ;
388+ } ) . toThrow ( FacilitatorValidationError ) ;
389+ } ) ;
198390 } ) ;
199391
200392 describe ( "validateNetwork" , ( ) => {
0 commit comments