@@ -7,46 +7,46 @@ mode: "wide"
77---
88
99export const CURRENCIES = [
10- { code: ' USD' , name: ' US Dollar' , region: ' Americas' , usdMid: 1 , gridSpread : 0 , wiseSpread: 0 },
11- { code: ' CAD' , name: ' Canadian Dollar' , region: ' Americas' , usdMid: 1.362 , gridSpread : 0.35 , wiseSpread: 0.55 },
12- { code: ' MXN' , name: ' Mexican Peso' , region: ' Americas' , usdMid: 17.45 , gridSpread : 0.5 , wiseSpread: 0.95 },
13- { code: ' BRL' , name: ' Brazilian Real' , region: ' Americas' , usdMid: 5.05 , gridSpread : 0.55 , wiseSpread: 1.00 },
14- { code: ' CRC' , name: ' Costa Rican Colón ' , region: ' Americas' , usdMid: 512 , gridSpread: 0.8 , wiseSpread: 1.60 },
15- { code: ' EUR' , name: ' Euro' , region: ' Europe' , usdMid: 0.921 , gridSpread : 0.25 , wiseSpread: 0.55 },
16- { code: ' GBP' , name: ' British Pound' , region: ' Europe' , usdMid: 0.791 , gridSpread : 0.25 , wiseSpread: 0.55 },
17- { code: ' CHF' , name: ' Swiss Franc' , region: ' Europe' , usdMid: 0.881 , gridSpread : 0.3 , wiseSpread: 0.55 },
18- { code: ' DKK' , name: ' Danish Krone' , region: ' Europe' , usdMid: 6.87 , gridSpread : 0.3 , wiseSpread: 0.55 },
19- { code: ' SEK' , name: ' Swedish Krona' , region: ' Europe' , usdMid: 10.45 , gridSpread : 0.4 , wiseSpread: 0.90 },
20- { code: ' NOK' , name: ' Norwegian Krone' , region: ' Europe' , usdMid: 10.55 , gridSpread : 0.4 , wiseSpread: 0.90 },
21- { code: ' CZK' , name: ' Czech Koruna' , region: ' Europe' , usdMid: 23.15 , gridSpread : 0.45 , wiseSpread: 0.95 },
22- { code: ' HUF' , name: ' Hungarian Forint' , region: ' Europe' , usdMid: 362 , gridSpread : 0.5 , wiseSpread: 1.00 },
23- { code: ' PLN' , name: ' Polish Zloty' , region: ' Europe' , usdMid: 4.02 , gridSpread : 0.45 , wiseSpread: 0.90 },
24- { code: ' RON' , name: ' Romanian Leu' , region: ' Europe' , usdMid: 4.58 , gridSpread : 0.5 , wiseSpread: 1.00 },
25- { code: ' BGN' , name: ' Bulgarian Lev' , region: ' Europe' , usdMid: 1.80 , gridSpread : 0.45 , wiseSpread: 0.95 },
26- { code: ' ISK' , name: ' Icelandic Króna ' , region: ' Europe' , usdMid: 138 , gridSpread : 0.8 , wiseSpread: 1.50 },
27- { code: ' NGN' , name: ' Nigerian Naira' , region: ' Africa' , usdMid: 1550 , gridSpread : 0.8 , wiseSpread: 2.50 },
28- { code: ' KES' , name: ' Kenyan Shilling' , region: ' Africa' , usdMid: 129.5 , gridSpread : 0.7 , wiseSpread: 2.00 },
29- { code: ' ZAR' , name: ' South African Rand' , region: ' Africa' , usdMid: 18.5 , gridSpread : 0.5 , wiseSpread: 0.95 },
30- { code: ' GHS' , name: ' Ghanaian Cedi' , region: ' Africa' , usdMid: 14.8 , gridSpread : 0.9 , wiseSpread: 2.50 },
31- { code: ' UGX' , name: ' Ugandan Shilling' , region: ' Africa' , usdMid: 3750 , gridSpread : 0.9 , wiseSpread: 2.80 },
32- { code: ' TZS' , name: ' Tanzanian Shilling' , region: ' Africa' , usdMid: 2530 , gridSpread : 0.9 , wiseSpread: 2.50 },
33- { code: ' ZMW' , name: ' Zambian Kwacha' , region: ' Africa' , usdMid: 26.5 , gridSpread : 1.0 , wiseSpread: 2.80 },
34- { code: ' MWK' , name: ' Malawian Kwacha' , region: ' Africa' , usdMid: 1730 , gridSpread : 1.1 , wiseSpread: 3.00 },
35- { code: ' XOF' , name: ' West African CFA' , region: ' Africa' , usdMid: 605 , gridSpread : 0.8 , wiseSpread: 2.00 },
36- { code: ' XAF' , name: ' Central African CFA' , region: ' Africa' , usdMid: 605 , gridSpread : 0.85 , wiseSpread: 2.00 },
37- { code: ' CDF' , name: ' Congolese Franc' , region: ' Africa' , usdMid: 2780 , gridSpread : 1.2 , wiseSpread: 3.00 },
38- { code: ' BWP' , name: ' Botswana Pula' , region: ' Africa' , usdMid: 13.6 , gridSpread : 0.9 , wiseSpread: 2.00 },
39- { code: ' INR' , name: ' Indian Rupee' , region: ' Asia-Pacific' , usdMid: 83.5 , gridSpread : 0.45 , wiseSpread: 0.90 },
40- { code: ' PHP' , name: ' Philippine Peso' , region: ' Asia-Pacific' , usdMid: 56.2 , gridSpread : 0.5 , wiseSpread: 1.00 },
41- { code: ' IDR' , name: ' Indonesian Rupiah' , region: ' Asia-Pacific' , usdMid: 15650 , gridSpread : 0.7 , wiseSpread: 1.10 },
42- { code: ' SGD' , name: ' Singapore Dollar' , region: ' Asia-Pacific' , usdMid: 1.34 , gridSpread : 0.3 , wiseSpread: 0.55 },
43- { code: ' HKD' , name: ' Hong Kong Dollar' , region: ' Asia-Pacific' , usdMid: 7.81 , gridSpread : 0.2 , wiseSpread: 0.50 },
44- { code: ' CNY' , name: ' Chinese Yuan' , region: ' Asia-Pacific' , usdMid: 7.24 , gridSpread : 0.6 , wiseSpread: 1.10 },
45- { code: ' KRW' , name: ' South Korean Won' , region: ' Asia-Pacific' , usdMid: 1325 , gridSpread : 0.45 , wiseSpread: 0.90 },
46- { code: ' THB' , name: ' Thai Baht' , region: ' Asia-Pacific' , usdMid: 35.2 , gridSpread : 0.5 , wiseSpread: 0.95 },
47- { code: ' VND' , name: ' Vietnamese Dong' , region: ' Asia-Pacific' , usdMid: 25200 , gridSpread: 0.8 , wiseSpread: 1.10 },
48- { code: ' MYR' , name: ' Malaysian Ringgit' , region: ' Asia-Pacific' , usdMid: 4.65 , gridSpread : 0.5 , wiseSpread: 1.00 },
49- { code: ' LKR' , name: ' Sri Lankan Rupee' , region: ' Asia-Pacific' , usdMid: 298 , gridSpread : 0.9 , wiseSpread: 1.80 },
10+ { code: ' USD' , name: ' US Dollar' , region: ' Americas' , usdMid: 1 , rateCardSpread : 0 , wiseSpread: 0 },
11+ { code: ' CAD' , name: ' Canadian Dollar' , region: ' Americas' , usdMid: 1.362 , rateCardSpread : 0.15 , wiseSpread: 0.55 },
12+ { code: ' MXN' , name: ' Mexican Peso' , region: ' Americas' , usdMid: 17.45 , rateCardSpread : 0.36 , wiseSpread: 0.95 },
13+ { code: ' BRL' , name: ' Brazilian Real' , region: ' Americas' , usdMid: 5.05 , rateCardSpread : 0.45 , wiseSpread: 1.00 },
14+ { code: ' CRC' , name: ' Costa Rican Colon ' , region: ' Americas' , usdMid: 512 , rateCardSpread: 1.50 , wiseSpread: 1.60 },
15+ { code: ' EUR' , name: ' Euro' , region: ' Europe' , usdMid: 0.921 , rateCardSpread : 0.13 , wiseSpread: 0.55 },
16+ { code: ' GBP' , name: ' British Pound' , region: ' Europe' , usdMid: 0.791 , rateCardSpread : 0.13 , wiseSpread: 0.55 },
17+ { code: ' CHF' , name: ' Swiss Franc' , region: ' Europe' , usdMid: 0.881 , rateCardSpread : 0.21 , wiseSpread: 0.55 },
18+ { code: ' DKK' , name: ' Danish Krone' , region: ' Europe' , usdMid: 6.87 , rateCardSpread : 0.17 , wiseSpread: 0.55 },
19+ { code: ' SEK' , name: ' Swedish Krona' , region: ' Europe' , usdMid: 10.45 , rateCardSpread : 0.17 , wiseSpread: 0.90 },
20+ { code: ' NOK' , name: ' Norwegian Krone' , region: ' Europe' , usdMid: 10.55 , rateCardSpread : 0.16 , wiseSpread: 0.90 },
21+ { code: ' CZK' , name: ' Czech Koruna' , region: ' Europe' , usdMid: 23.15 , rateCardSpread : 0.24 , wiseSpread: 0.95 },
22+ { code: ' HUF' , name: ' Hungarian Forint' , region: ' Europe' , usdMid: 362 , rateCardSpread : 0.31 , wiseSpread: 1.00 },
23+ { code: ' PLN' , name: ' Polish Zloty' , region: ' Europe' , usdMid: 4.02 , rateCardSpread : 0.22 , wiseSpread: 0.90 },
24+ { code: ' RON' , name: ' Romanian Leu' , region: ' Europe' , usdMid: 4.58 , rateCardSpread : 0.30 , wiseSpread: 1.00 },
25+ { code: ' BGN' , name: ' Bulgarian Lev' , region: ' Europe' , usdMid: 1.80 , rateCardSpread : 0.34 , wiseSpread: 0.95 },
26+ { code: ' ISK' , name: ' Icelandic Krona ' , region: ' Europe' , usdMid: 138 , rateCardSpread : 0.80 , wiseSpread: 1.50 },
27+ { code: ' NGN' , name: ' Nigerian Naira' , region: ' Africa' , usdMid: 1550 , rateCardSpread : 0.80 , wiseSpread: 2.50 },
28+ { code: ' KES' , name: ' Kenyan Shilling' , region: ' Africa' , usdMid: 129.5 , rateCardSpread : 0.36 , wiseSpread: 2.00 },
29+ { code: ' ZAR' , name: ' South African Rand' , region: ' Africa' , usdMid: 18.5 , rateCardSpread : 0.50 , wiseSpread: 0.95 },
30+ { code: ' GHS' , name: ' Ghanaian Cedi' , region: ' Africa' , usdMid: 14.8 , rateCardSpread : 0.41 , wiseSpread: 2.50 },
31+ { code: ' UGX' , name: ' Ugandan Shilling' , region: ' Africa' , usdMid: 3750 , rateCardSpread : 0.90 , wiseSpread: 2.80 },
32+ { code: ' TZS' , name: ' Tanzanian Shilling' , region: ' Africa' , usdMid: 2530 , rateCardSpread : 0.90 , wiseSpread: 2.50 },
33+ { code: ' ZMW' , name: ' Zambian Kwacha' , region: ' Africa' , usdMid: 26.5 , rateCardSpread : 1.00 , wiseSpread: 2.80 },
34+ { code: ' MWK' , name: ' Malawian Kwacha' , region: ' Africa' , usdMid: 1730 , rateCardSpread : 1.10 , wiseSpread: 3.00 },
35+ { code: ' XOF' , name: ' West African CFA' , region: ' Africa' , usdMid: 605 , rateCardSpread : 0.80 , wiseSpread: 2.00 },
36+ { code: ' XAF' , name: ' Central African CFA' , region: ' Africa' , usdMid: 605 , rateCardSpread : 0.85 , wiseSpread: 2.00 },
37+ { code: ' CDF' , name: ' Congolese Franc' , region: ' Africa' , usdMid: 2780 , rateCardSpread : 1.20 , wiseSpread: 3.00 },
38+ { code: ' BWP' , name: ' Botswana Pula' , region: ' Africa' , usdMid: 13.6 , rateCardSpread : 0.90 , wiseSpread: 2.00 },
39+ { code: ' INR' , name: ' Indian Rupee' , region: ' Asia-Pacific' , usdMid: 83.5 , rateCardSpread : 0.32 , wiseSpread: 0.90 },
40+ { code: ' PHP' , name: ' Philippine Peso' , region: ' Asia-Pacific' , usdMid: 56.2 , rateCardSpread : 0.20 , wiseSpread: 1.00 },
41+ { code: ' IDR' , name: ' Indonesian Rupiah' , region: ' Asia-Pacific' , usdMid: 15650 , rateCardSpread : 0.35 , wiseSpread: 1.10 },
42+ { code: ' SGD' , name: ' Singapore Dollar' , region: ' Asia-Pacific' , usdMid: 1.34 , rateCardSpread : 0.15 , wiseSpread: 0.55 },
43+ { code: ' HKD' , name: ' Hong Kong Dollar' , region: ' Asia-Pacific' , usdMid: 7.81 , rateCardSpread : 0.20 , wiseSpread: 0.50 },
44+ { code: ' CNY' , name: ' Chinese Yuan' , region: ' Asia-Pacific' , usdMid: 7.24 , rateCardSpread : 0.60 , wiseSpread: 1.10 },
45+ { code: ' KRW' , name: ' South Korean Won' , region: ' Asia-Pacific' , usdMid: 1325 , rateCardSpread : 0.45 , wiseSpread: 0.90 },
46+ { code: ' THB' , name: ' Thai Baht' , region: ' Asia-Pacific' , usdMid: 35.2 , rateCardSpread : 0.34 , wiseSpread: 0.95 },
47+ { code: ' VND' , name: ' Vietnamese Dong' , region: ' Asia-Pacific' , usdMid: 25200 , rateCardSpread: 1.18 , wiseSpread: 1.10 },
48+ { code: ' MYR' , name: ' Malaysian Ringgit' , region: ' Asia-Pacific' , usdMid: 4.65 , rateCardSpread : 0.31 , wiseSpread: 1.00 },
49+ { code: ' LKR' , name: ' Sri Lankan Rupee' , region: ' Asia-Pacific' , usdMid: 298 , rateCardSpread : 0.42 , wiseSpread: 1.80 },
5050];
5151
5252export const CURRENCY_TO_COUNTRY = {
@@ -430,7 +430,7 @@ export const RateExplorer = () => {
430430 const [viewMode, setViewMode] = React .useState (' bps' );
431431 const [liveRates, setLiveRates] = React .useState (() => {
432432 try {
433- const cached = sessionStorage .getItem (' grid_live_rates ' );
433+ const cached = sessionStorage .getItem (' coinbase_mid_rates ' );
434434 if (cached ) {
435435 const parsed = JSON .parse (cached );
436436 if (parsed && parsed .ts && Date .now () - parsed .ts < CACHE_TTL ) {
@@ -440,6 +440,7 @@ export const RateExplorer = () => {
440440 } catch (e ) {}
441441 return null ;
442442 });
443+ const [gridCostRates, setGridCostRates] = React .useState (null );
443444 const [showSourceDropdown, setShowSourceDropdown] = React .useState (false );
444445 const [sourceDropdownQuery, setSourceDropdownQuery] = React .useState (' ' );
445446 const [sourceHighlightIdx, setSourceHighlightIdx] = React .useState (- 1 );
@@ -475,27 +476,64 @@ export const RateExplorer = () => {
475476 React .useEffect (() => {
476477 if (! isVisible ) return ;
477478 if (liveRates !== null ) return ;
478- fetch (' https://api.lightspark .com/grid/2025-10-13/ exchange-rates?sourceCurrency =USD' )
479+ fetch (' https://api.coinbase .com/v2/ exchange-rates?currency =USD' )
479480 .then (res => {
480481 if (! res .ok ) throw new Error (res .status );
481482 return res .json ();
482483 })
483484 .then (json => {
484485 const rates = {};
485- if (json && json .data && json .data .length > 0 ) {
486- json .data .forEach (r => {
487- rates [r . destinationCurrency . code ] = String ( r . exchangeRate ) ;
486+ if (json && json .data && json .data .rates ) {
487+ Object . entries ( json .data .rates ). forEach (([ code , val ]) => {
488+ rates [code ] = val ;
488489 });
489490 }
490491 setLiveRates (rates );
491- try { sessionStorage .setItem (' grid_live_rates ' , JSON .stringify ({ ts: Date .now (), rates })); } catch (e ) {}
492+ try { sessionStorage .setItem (' coinbase_mid_rates ' , JSON .stringify ({ ts: Date .now (), rates })); } catch (e ) {}
492493 })
493494 .catch (() => {
494495 setLiveRates ({});
495- try { sessionStorage .setItem (' grid_live_rates ' , JSON .stringify ({ ts: Date .now (), rates: {} })); } catch (e ) {}
496+ try { sessionStorage .setItem (' coinbase_mid_rates ' , JSON .stringify ({ ts: Date .now (), rates: {} })); } catch (e ) {}
496497 });
497498 }, [isVisible ]);
498499
500+ React .useEffect (() => {
501+ if (! isVisible ) return ;
502+ const bucketed = bucketAmount (debouncedAmount );
503+ const sendingCents = bucketed * 100 ;
504+ const cacheKey = ' grid_cost_' + sendingCents ;
505+ try {
506+ const cached = sessionStorage .getItem (cacheKey );
507+ if (cached ) {
508+ const parsed = JSON .parse (cached );
509+ if (parsed && parsed .ts && Date .now () - parsed .ts < CACHE_TTL ) {
510+ setGridCostRates (parsed .rates );
511+ return ;
512+ }
513+ }
514+ } catch (e ) {}
515+ fetch (' https://api.lightspark.com/grid/2025-10-13/public/exchange-rates?sourceCurrency=USD&sendingAmount=' + sendingCents )
516+ .then (res => {
517+ if (! res .ok ) throw new Error (res .status );
518+ return res .json ();
519+ })
520+ .then (json => {
521+ const rates = {};
522+ if (json && json .data && json .data .length > 0 ) {
523+ json .data .forEach (r => {
524+ var destDecimals = r .destinationCurrency .decimals || 0 ;
525+ var actualRate = (r .receivingAmount / Math .pow (10 , destDecimals )) / (r .sendingAmount / 100 );
526+ rates [r .destinationCurrency .code ] = String (actualRate );
527+ });
528+ }
529+ setGridCostRates (rates );
530+ try { sessionStorage .setItem (cacheKey , JSON .stringify ({ ts: Date .now (), rates })); } catch (e ) {}
531+ })
532+ .catch (() => {
533+ setGridCostRates ({});
534+ });
535+ }, [isVisible , debouncedAmount ]);
536+
499537 React .useEffect (() => {
500538 if (! isVisible ) return ;
501539 const bucketed = bucketAmount (debouncedAmount );
@@ -572,9 +610,18 @@ export const RateExplorer = () => {
572610 el .style .setProperty (' --re-thead-top' , (navH + hH ) + ' px' );
573611 };
574612
575- requestAnimationFrame (updateVars );
613+ const measureBelow = () => {
614+ var prev = el .style .minHeight ;
615+ el .style .minHeight = ' 0px' ;
616+ var elBottom = el .getBoundingClientRect ().bottom + window .scrollY ;
617+ var pageH = document .documentElement .scrollHeight ;
618+ el .style .minHeight = prev ;
619+ el .style .setProperty (' --re-below-h' , (pageH - elBottom ) + ' px' );
620+ };
621+
622+ requestAnimationFrame (() => { updateVars (); measureBelow (); });
576623
577- const ro = new ResizeObserver (updateVars );
624+ const ro = new ResizeObserver (() => { updateVars (); measureBelow (); } );
578625 ro .observe (header );
579626 if (navbar ) ro .observe (navbar );
580627
@@ -661,12 +708,25 @@ export const RateExplorer = () => {
661708 setTimeout (() => e .target .select (), 0 );
662709 };
663710
711+ const scrollToHeader = () => {
712+ const header = headerBarRef .current ;
713+ const el = explorerRef .current ;
714+ if (header ) header .classList .remove (' re-header-hidden' );
715+ if (! el || window .scrollY < 200 ) return ;
716+ el .style .minHeight = el .offsetHeight + ' px' ;
717+ setTimeout (() => {
718+ el .scrollIntoView ({ behavior: ' instant' , block: ' start' });
719+ requestAnimationFrame (() => { el .style .minHeight = ' ' ; });
720+ }, 0 );
721+ };
722+
664723 const addCurrency = (code ) => {
665724 if (! selectedCurrencies .includes (code )) {
666725 setSelectedCurrencies ([... selectedCurrencies , code ]);
667726 }
668727 setShowDropdown (false );
669728 setDropdownQuery (' ' );
729+ scrollToHeader ();
670730 };
671731
672732 const removeCurrency = (code ) => {
@@ -675,6 +735,7 @@ export const RateExplorer = () => {
675735
676736 const clearCurrencies = () => {
677737 setSelectedCurrencies ([]);
738+ scrollToHeader ();
678739 };
679740
680741 const getUsdMid = (code ) => {
@@ -694,8 +755,21 @@ export const RateExplorer = () => {
694755 const midRate = destUsdMid / sourceUsdMid ;
695756 const cd = competitorData [dest .code ];
696757 const midReceive = cd && cd .midReceive ? cd .midReceive : debouncedAmount * midRate ;
697- const gridBps = Math .round (dest .gridSpread * 100 );
698- const gridReceive = midReceive * (1 - dest .gridSpread / 100 );
758+ const rateCard = dest .rateCardSpread ;
759+ let costFloorSpread = 0 ;
760+ if (gridCostRates && gridCostRates [dest .code ] && destUsdMid > 0 ) {
761+ const gridApiRate = parseFloat (gridCostRates [dest .code ]);
762+ const coinbaseMid = midRate ;
763+ const coinbaseDestPerUnit = destUsdMid ;
764+ const gridDestPerUnit = gridApiRate ;
765+ if (coinbaseDestPerUnit > 0 ) {
766+ costFloorSpread = ((coinbaseDestPerUnit - gridDestPerUnit ) / coinbaseDestPerUnit ) * 100 ;
767+ if (costFloorSpread < 0 ) costFloorSpread = 0 ;
768+ }
769+ }
770+ const effectiveSpread = Math .max (rateCard , costFloorSpread );
771+ const gridBps = Math .round (effectiveSpread * 100 );
772+ const gridReceive = midReceive * (1 - effectiveSpread / 100 );
699773 const providers = {};
700774 if (cd && cd .providers ) {
701775 cd .providers .forEach (p => {
@@ -733,7 +807,7 @@ export const RateExplorer = () => {
733807 code: dest .code , name: dest .name ,
734808 midRate: midRate , midReceive: midReceive ,
735809 gridReceive: gridReceive , gridBps: gridBps ,
736- providers: providers , gridSpread: dest . gridSpread ,
810+ providers: providers , effectiveSpread: effectiveSpread ,
737811 stripeBps: stripeBps , stripeReceive: stripeReceive ,
738812 airwallexBps: airwallexBps , airwallexReceive: airwallexReceive ,
739813 bankAvgBps: bankAvgBps , bankAvgReceive: bankAvgReceive ,
0 commit comments