33 asArray ,
44 asDate ,
55 asEither ,
6+ asJSON ,
7+ asMaybe ,
68 asNull ,
79 asNumber ,
810 asObject ,
@@ -19,7 +21,8 @@ import {
1921 EdgeSwapRequest ,
2022 SwapAboveLimitError ,
2123 SwapBelowLimitError ,
22- SwapCurrencyError
24+ SwapCurrencyError ,
25+ SwapPermissionError
2326} from 'edge-core-js/types'
2427
2528import { nexchange as nexchangeMapping } from '../../mappings/nexchange'
@@ -104,6 +107,12 @@ function formatCurrency(
104107 }
105108}
106109
110+ // Cleaner for Nexchange error responses, e.g. {"non_field_errors":["User's IP has risk."]}
111+ // Uses asMaybe so it returns null instead of throwing on unexpected shapes
112+ const asErrorResponse = asMaybe (
113+ asJSON ( asObject ( { non_field_errors : asArray ( asString ) } ) )
114+ )
115+
107116const asRateV2 = asObject ( {
108117 // Fields validated but not currently used: pair, from, to, withdrawal_fee
109118 // These are kept for API validation and potential future use
@@ -187,9 +196,20 @@ export function makeNexchangePlugin(
187196 const text = await response . text ( )
188197 if ( ! response . ok ) {
189198 log . warn ( 'Nexchange response:' , text )
199+
190200 if ( response . status === 404 && request != null ) {
191201 throw new SwapCurrencyError ( swapInfo , request )
192202 }
203+
204+ if ( response . status === 400 ) {
205+ const errorData = asErrorResponse ( text )
206+ if (
207+ errorData ?. non_field_errors . includes ( "User's IP has risk." ) === true
208+ ) {
209+ throw new SwapPermissionError ( swapInfo , 'geoRestriction' )
210+ }
211+ }
212+
193213 throw new Error (
194214 `Nexchange returned error code ${ response . status } : ${ text } `
195215 )
0 commit comments