diff --git a/packages/checkout/sdk/src/connect/connect.test.ts b/packages/checkout/sdk/src/connect/connect.test.ts index e0051217f5..f99fe17e93 100644 --- a/packages/checkout/sdk/src/connect/connect.test.ts +++ b/packages/checkout/sdk/src/connect/connect.test.ts @@ -7,6 +7,9 @@ import { checkIsWalletConnected, connectSite, requestPermissions } from './conne import { WrappedBrowserProvider, WalletAction, WalletProviderName } from '../types'; import { CheckoutErrorType } from '../errors'; import { createProvider } from '../provider'; +import { RemoteConfigFetcher } from '../config/remoteConfigFetcher'; + +jest.mock('../config/remoteConfigFetcher'); let windowSpy: any; @@ -26,6 +29,13 @@ describe('connect', () => { addEventListener: jest.fn(), removeEventListener: jest.fn(), })); + + // Mock RemoteConfigFetcher to prevent test failures + (RemoteConfigFetcher as unknown as jest.Mock).mockReturnValue({ + getConfig: jest.fn().mockResolvedValue({ + segmentPublishableKey: 'test-key', + }), + }); }); afterEach(() => { diff --git a/packages/checkout/sdk/src/riskAssessment/riskAssessment.test.ts b/packages/checkout/sdk/src/riskAssessment/riskAssessment.test.ts index 49bee4460f..6bd422bb2f 100644 --- a/packages/checkout/sdk/src/riskAssessment/riskAssessment.test.ts +++ b/packages/checkout/sdk/src/riskAssessment/riskAssessment.test.ts @@ -43,8 +43,11 @@ describe('riskAssessment', () => { mockedAxios.post.mockResolvedValueOnce(mockRiskResponse); const sanctions = await fetchRiskAssessment( - [address1, address2], mockedConfig, + [ + { address: address1, tokenAddr: '0xtest1', amount: '100' }, + { address: address2, tokenAddr: '0xtest2', amount: '200' }, + ], ); expect(sanctions[address1.toLowerCase()]).toEqual({ sanctioned: false }); @@ -60,15 +63,15 @@ describe('riskAssessment', () => { const address1 = '0x1234567890'; const sanctions = await fetchRiskAssessment( - [address1], mockedConfig, + [{ address: address1, tokenAddr: 'native', amount: '100' }], // Include required fields even when disabled ); expect(sanctions[address1.toLowerCase()]).toEqual({ sanctioned: false }); expect(mockedAxios.post).not.toHaveBeenCalled(); }); - it('should return default risk assessment not found for address', async () => { + it('should return default risk assessment on empty response', async () => { mockRemoteConfig.mockResolvedValue({ enabled: true, levels: ['severe'], @@ -83,8 +86,8 @@ describe('riskAssessment', () => { mockedAxios.post.mockResolvedValueOnce(mockRiskResponse); const sanctions = await fetchRiskAssessment( - [address1], mockedConfig, + [{ address: address1, tokenAddr: '0xtest', amount: '100' }], ); expect(sanctions[address1.toLowerCase()]).toEqual({ sanctioned: false }); diff --git a/packages/checkout/sdk/src/riskAssessment/riskAssessment.ts b/packages/checkout/sdk/src/riskAssessment/riskAssessment.ts index cee58705d9..03c9ccf6d8 100644 --- a/packages/checkout/sdk/src/riskAssessment/riskAssessment.ts +++ b/packages/checkout/sdk/src/riskAssessment/riskAssessment.ts @@ -22,12 +22,26 @@ export type AssessmentResult = { }; }; +// New type for v2 request items +type SanctionsCheckV2RequestItem = { + address: string; + amount: string; + token_addr: string; +}; + +// Simplified assessment data - no redundant address info +type AssessmentData = { + address: string; + tokenAddr: string; + amount: string; +}; + export const fetchRiskAssessment = async ( - addresses: string[], config: CheckoutConfiguration, + assessmentData: AssessmentData[], ): Promise => { const result = Object.fromEntries( - addresses.map((address) => [address.toLowerCase(), { sanctioned: false }]), + assessmentData.map((data) => [data.address.toLowerCase(), { sanctioned: false }]), ); const riskConfig = (await config.remote.getConfig('riskAssessment')) as @@ -41,11 +55,16 @@ export const fetchRiskAssessment = async ( try { const riskLevels = riskConfig?.levels.map((l) => l.toLowerCase()) ?? []; + // Prepare v2 request payload - always include token data + const requestPayload: SanctionsCheckV2RequestItem[] = assessmentData.map((data) => ({ + address: data.address, + token_addr: data.tokenAddr, + amount: data.amount, + })); + const response = await axios.post( - `${IMMUTABLE_API_BASE_URL[config.environment]}/v1/sanctions/check`, - { - addresses, - }, + `${IMMUTABLE_API_BASE_URL[config.environment]}/v2/sanctions/check`, + requestPayload, ); for (const assessment of response.data) { diff --git a/packages/checkout/sdk/src/sdk.ts b/packages/checkout/sdk/src/sdk.ts index e290c037aa..950b1473b1 100644 --- a/packages/checkout/sdk/src/sdk.ts +++ b/packages/checkout/sdk/src/sdk.ts @@ -226,12 +226,15 @@ export class Checkout { } /** - * Fetches the risk assessment for the given addresses. - * @param {string[]} addresses - The addresses to assess. + * Fetches risk assessment for the given assessment data. + * @param {Array<{address: string; tokenAddr: string; amount: string}>} assessmentData + * Assessment data with addresses and required token context. * @returns {Promise} - A promise that resolves to the risk assessment result. */ - public async getRiskAssessment(addresses: string[]): Promise { - return await fetchRiskAssessment(addresses, this.config); + public async getRiskAssessment( + assessmentData: Array<{ address: string; tokenAddr: string; amount: string }>, + ): Promise { + return await fetchRiskAssessment(this.config, assessmentData); } /** diff --git a/packages/checkout/widgets-lib/src/functions/checkSanctionedAddresses.ts b/packages/checkout/widgets-lib/src/functions/checkSanctionedAddresses.ts index 598e305e33..315f74a0b5 100644 --- a/packages/checkout/widgets-lib/src/functions/checkSanctionedAddresses.ts +++ b/packages/checkout/widgets-lib/src/functions/checkSanctionedAddresses.ts @@ -5,9 +5,9 @@ import { } from '@imtbl/checkout-sdk'; export const checkSanctionedAddresses = async ( - addresses: string[], config: CheckoutConfiguration, + assessmentData: Array<{ address: string; tokenAddr: string; amount: string }>, ): Promise => { - const result = await fetchRiskAssessment(addresses, config); + const result = await fetchRiskAssessment(config, assessmentData); return isAddressSanctioned(result, undefined); }; diff --git a/packages/checkout/widgets-lib/src/widgets/add-tokens/views/AddTokens.tsx b/packages/checkout/widgets-lib/src/widgets/add-tokens/views/AddTokens.tsx index d075b0bd24..6a71a64468 100644 --- a/packages/checkout/widgets-lib/src/widgets/add-tokens/views/AddTokens.tsx +++ b/packages/checkout/widgets-lib/src/widgets/add-tokens/views/AddTokens.tsx @@ -423,10 +423,18 @@ export function AddTokens({ }, [checkout, id]); const sendRequestOnRampEvent = async () => { - if ( - toAddress - && (await checkSanctionedAddresses([toAddress], checkout.config)) - ) { + // Only check sanctions if we have meaningful data + const shouldCheckSanctions = toAddress && selectedToken?.address && selectedAmount; + const isSanctioned = shouldCheckSanctions && await checkSanctionedAddresses( + checkout.config, + [{ + address: toAddress, + tokenAddr: selectedToken.address!, + amount: selectedAmount, + }], + ); + + if (isSanctioned) { viewDispatch({ payload: { type: ViewActions.UPDATE_VIEW, @@ -496,14 +504,25 @@ export function AddTokens({ const handleReviewClick = async () => { if (!selectedRouteData || !selectedToken?.address) return; - if ( - fromAddress - && toAddress - && (await checkSanctionedAddresses( - [fromAddress, toAddress], - checkout.config, - )) - ) { + // Only check sanctions if we have meaningful amount data + const shouldCheckSanctions = selectedAmount && fromAddress && toAddress; + const isSanctioned = shouldCheckSanctions && await checkSanctionedAddresses( + checkout.config, + [ + { + address: fromAddress, + tokenAddr: selectedToken.address, + amount: selectedAmount, + }, + { + address: toAddress, + tokenAddr: selectedToken.address, + amount: selectedAmount, + }, + ], + ); + + if (isSanctioned) { viewDispatch({ payload: { type: ViewActions.UPDATE_VIEW, diff --git a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx index 510e7e10f4..70b2e3fcf6 100644 --- a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx +++ b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx @@ -178,17 +178,27 @@ export function BridgeForm(props: BridgeFormProps) { ); useEffect(() => { - if (!checkout || !from || !to) { + // Only run risk assessment when we have meaningful token and amount selection + if (!checkout || !from || !to || !formToken?.token.address || !formAmount) { return; } (async () => { - const addresses = [from.walletAddress]; - if (to.walletAddress.toLowerCase() !== from.walletAddress.toLowerCase()) { - addresses.push(to.walletAddress); - } + // We have meaningful token data - call enhanced risk assessment + const assessmentData = [ + { + address: from.walletAddress, + tokenAddr: formToken.token.address as string, + amount: formAmount as string, + }, + ...(to.walletAddress.toLowerCase() !== from.walletAddress.toLowerCase() ? [{ + address: to.walletAddress, + tokenAddr: formToken.token.address as string, + amount: formAmount as string, + }] : []), + ]; - const assessment = await fetchRiskAssessment(addresses, checkout.config); + const assessment = await fetchRiskAssessment(checkout.config, assessmentData); bridgeDispatch({ payload: { type: BridgeActions.SET_RISK_ASSESSMENT, @@ -196,7 +206,7 @@ export function BridgeForm(props: BridgeFormProps) { }, }); })(); - }, [checkout, from, to]); + }, [checkout, from, to, formToken, formAmount]); const canFetchEstimates = (silently: boolean): boolean => { if (Number.isNaN(parseFloat(formAmount))) return false; diff --git a/packages/checkout/widgets-lib/src/widgets/on-ramp/views/OnRampMain.tsx b/packages/checkout/widgets-lib/src/widgets/on-ramp/views/OnRampMain.tsx index 7d30a41dee..a9dddb2d1f 100644 --- a/packages/checkout/widgets-lib/src/widgets/on-ramp/views/OnRampMain.tsx +++ b/packages/checkout/widgets-lib/src/widgets/on-ramp/views/OnRampMain.tsx @@ -224,14 +224,22 @@ export function OnRampMain({ }; useEffect(() => { - if (!checkout || !provider) return; + // Only run risk assessment when we have meaningful token and amount data + if (!checkout || !provider || !tokenAddress || !tokenAmount) return; let userWalletAddress = ''; (async () => { const walletAddress = await (await provider.getSigner()).getAddress(); - const assessment = await fetchRiskAssessment([walletAddress], checkout.config); + // We have meaningful token data - call enhanced risk assessment + const assessmentData = [{ + address: walletAddress, + tokenAddr: tokenAddress, + amount: tokenAmount, + }]; + + const assessment = await fetchRiskAssessment(checkout.config, assessmentData); if (isAddressSanctioned(assessment)) { viewDispatch({ diff --git a/packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx b/packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx index 0e934136f6..2485c09204 100644 --- a/packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx +++ b/packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx @@ -246,7 +246,8 @@ export function SaleContextProvider(props: { }, [provider]); useEffect(() => { - if (!checkout || riskAssessment) { + // Only run risk assessment when we have meaningful token selection + if (!checkout || riskAssessment || !selectedCurrency) { return; } @@ -257,10 +258,17 @@ export function SaleContextProvider(props: { return; } - const assessment = await fetchRiskAssessment([address], checkout.config); + // We have meaningful token data - call enhanced risk assessment + const assessmentData = [{ + address, + tokenAddr: selectedCurrency.address, + amount: orderQuote.totalAmount[selectedCurrency.name].amount.toString(), + }]; + + const assessment = await fetchRiskAssessment(checkout.config, assessmentData); setRiskAssessment(assessment); })(); - }, [checkout, provider]); + }, [checkout, provider, selectedCurrency, orderQuote]); const { sign: signOrder, diff --git a/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx b/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx index 5555bf1a06..3951553dbb 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx @@ -194,7 +194,8 @@ export default function SwapWidget({ }, [checkout, provider]); useEffect(() => { - if (!checkout || swapState.riskAssessment) { + // Only run risk assessment when we have meaningful token and amount data + if (!checkout || swapState.riskAssessment || !fromTokenAddress || !amount) { return; } @@ -205,7 +206,14 @@ export default function SwapWidget({ return; } - const assessment = await fetchRiskAssessment([address], checkout.config); + // We have meaningful token data - call enhanced risk assessment + const assessmentData = [{ + address, + tokenAddr: fromTokenAddress, + amount, + }]; + + const assessment = await fetchRiskAssessment(checkout.config, assessmentData); swapDispatch({ payload: { type: SwapActions.SET_RISK_ASSESSMENT, @@ -213,7 +221,7 @@ export default function SwapWidget({ }, }); })(); - }, [checkout, provider]); + }, [checkout, provider, fromTokenAddress, amount]); useEffect(() => { swapDispatch({