@@ -284,6 +284,129 @@ describe('OneClick protocol', () => {
284284 const parsed = JSON . parse ( capturedBody ! )
285285 expect ( parsed . dry ) . toBe ( true )
286286 } )
287+
288+ it ( 'should populate affiliateFee from echoed quoteRequest.appFees matching affiliate address' , async ( ) => {
289+ const affiliateAddress = 'bc1qydqk2n5wwm2ugg05tv482w8p42734gft0ssze8'
290+ protocol = new OneClickProtocol ( {
291+ affiliateAddress,
292+ affiliateBps : 30 ,
293+ } )
294+
295+ let capturedBody : string | undefined
296+ mockFetch . mockImplementation ( ( url : string , options ?: RequestInit ) => {
297+ if ( url . includes ( '/v0/tokens' ) ) {
298+ return Promise . resolve ( { ok : true , json : ( ) => Promise . resolve ( mockTokens ) } )
299+ }
300+ if ( url . includes ( '/v0/quote' ) ) {
301+ capturedBody = options ?. body as string
302+ return Promise . resolve ( {
303+ ok : true ,
304+ json : ( ) =>
305+ Promise . resolve ( {
306+ quoteRequest : {
307+ appFees : [
308+ { recipient : affiliateAddress , fee : 15 } ,
309+ { recipient : '5880ad2b362620fadf759cbceb1cd5737ce8c6ed7fb8e9942881e6731f9247dd' , fee : 25 } ,
310+ ] ,
311+ } ,
312+ quote : {
313+ depositAddress : 'bc1qfakedeposit' ,
314+ amountOut : '99000' ,
315+ timeEstimate : 600 ,
316+ } ,
317+ } ) ,
318+ } )
319+ }
320+ return Promise . resolve ( { ok : false , status : 404 } )
321+ } )
322+
323+ // 1 ETH = 1e18 base units; echoed fee 15 bps → 1e18 * 15 / 10000 = 1.5e15
324+ const quote = await protocol . estimateSwap ( {
325+ fromAsset : AssetETH ,
326+ destinationAsset : AssetBTC ,
327+ amount : new CryptoAmount ( assetToBase ( assetAmount ( 1 , 18 ) ) , AssetETH ) ,
328+ fromAddress : '0xSender' ,
329+ destinationAddress : 'bc1qRecipient' ,
330+ } )
331+
332+ const parsed = JSON . parse ( capturedBody ! )
333+ expect ( parsed . appFees ) . toEqual ( [ { recipient : affiliateAddress , fee : 30 } ] )
334+ expect ( quote . fees . affiliateFee . baseAmount . amount ( ) . toString ( ) ) . toBe ( '1500000000000000' )
335+ expect ( assetToString ( quote . fees . affiliateFee . asset ) ) . toBe ( 'ETH.ETH' )
336+ expect ( quote . fees . affiliateFee . baseAmount . decimal ) . toBe ( 18 )
337+ } )
338+
339+ it ( 'should fall back to configured affiliateBps when quoteRequest.appFees is absent' , async ( ) => {
340+ const affiliateAddress = 'bc1qydqk2n5wwm2ugg05tv482w8p42734gft0ssze8'
341+ protocol = new OneClickProtocol ( {
342+ affiliateAddress,
343+ affiliateBps : 30 ,
344+ } )
345+
346+ mockFetch . mockImplementation ( ( url : string ) => {
347+ if ( url . includes ( '/v0/tokens' ) ) {
348+ return Promise . resolve ( { ok : true , json : ( ) => Promise . resolve ( mockTokens ) } )
349+ }
350+ if ( url . includes ( '/v0/quote' ) ) {
351+ return Promise . resolve ( {
352+ ok : true ,
353+ json : ( ) =>
354+ Promise . resolve ( {
355+ quote : {
356+ depositAddress : 'bc1qfakedeposit' ,
357+ amountOut : '99000' ,
358+ timeEstimate : 600 ,
359+ } ,
360+ } ) ,
361+ } )
362+ }
363+ return Promise . resolve ( { ok : false , status : 404 } )
364+ } )
365+
366+ // 1 BTC = 1e8 sats; 30 bps → 1e8 * 30 / 10000 = 300000
367+ const quote = await protocol . estimateSwap ( {
368+ fromAsset : AssetBTC ,
369+ destinationAsset : AssetETH ,
370+ amount : new CryptoAmount ( assetToBase ( assetAmount ( 1 , 8 ) ) , AssetBTC ) ,
371+ fromAddress : 'bc1qSender' ,
372+ destinationAddress : '0xRecipient' ,
373+ } )
374+
375+ expect ( quote . fees . affiliateFee . baseAmount . amount ( ) . toString ( ) ) . toBe ( '300000' )
376+ expect ( assetToString ( quote . fees . affiliateFee . asset ) ) . toBe ( 'BTC.BTC' )
377+ } )
378+
379+ it ( 'should leave affiliateFee at 0 when no affiliate is configured' , async ( ) => {
380+ mockFetch . mockImplementation ( ( url : string ) => {
381+ if ( url . includes ( '/v0/tokens' ) ) {
382+ return Promise . resolve ( { ok : true , json : ( ) => Promise . resolve ( mockTokens ) } )
383+ }
384+ if ( url . includes ( '/v0/quote' ) ) {
385+ return Promise . resolve ( {
386+ ok : true ,
387+ json : ( ) =>
388+ Promise . resolve ( {
389+ quote : {
390+ depositAddress : 'bc1qfakedeposit' ,
391+ amountOut : '99000' ,
392+ timeEstimate : 600 ,
393+ } ,
394+ } ) ,
395+ } )
396+ }
397+ return Promise . resolve ( { ok : false , status : 404 } )
398+ } )
399+
400+ const quote = await protocol . estimateSwap ( {
401+ fromAsset : AssetETH ,
402+ destinationAsset : AssetBTC ,
403+ amount : new CryptoAmount ( assetToBase ( assetAmount ( 1 , 18 ) ) , AssetETH ) ,
404+ fromAddress : '0xSender' ,
405+ destinationAddress : 'bc1qRecipient' ,
406+ } )
407+
408+ expect ( quote . fees . affiliateFee . baseAmount . amount ( ) . toString ( ) ) . toBe ( '0' )
409+ } )
287410 } )
288411
289412 describe ( 'doSwap' , ( ) => {
0 commit comments