Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2d41268
Migrate to v2 sanction API and add token info
fang377 Jul 22, 2025
ea81125
Fix linter
fang377 Jul 22, 2025
e549752
Fix test
fang377 Jul 22, 2025
2a332f3
Add token info in sale context
fang377 Jul 24, 2025
0a18d90
Mark token info as non-optional in v2 sanction
fang377 Jul 24, 2025
389da8a
Fix linting
fang377 Jul 24, 2025
26ac3b5
Fix test
fang377 Jul 24, 2025
754805f
Fix linting
fang377 Jul 25, 2025
094778b
Avoid invalid token info
fang377 Jul 25, 2025
65f1387
Fix linting
fang377 Jul 25, 2025
dd6fa2d
Fix linting
fang377 Jul 25, 2025
ffeba32
Fix linting
fang377 Jul 25, 2025
3c4ee62
Fix linting
fang377 Jul 25, 2025
4fa0fb8
Make token addr and amount required in v2 request
fang377 Jul 28, 2025
24141fd
Fix build
fang377 Jul 28, 2025
3d2e8c2
Fix linting
fang377 Jul 28, 2025
8f5d2b1
Fix addToken
fang377 Jul 28, 2025
5917288
Fix addToken
fang377 Jul 28, 2025
99194c6
Fix addToken
fang377 Jul 28, 2025
df4292b
Fix widgets
fang377 Jul 28, 2025
da53818
Fix linting
fang377 Jul 28, 2025
d51ca86
Fix build
fang377 Jul 28, 2025
94a4ceb
Fix bridgeForm
fang377 Jul 28, 2025
0671d68
Update packages/checkout/widgets-lib/src/widgets/add-tokens/views/Add…
fang377 Jul 28, 2025
698ff0e
Update packages/checkout/widgets-lib/src/widgets/add-tokens/views/Add…
fang377 Jul 28, 2025
72aed4f
Update packages/checkout/widgets-lib/src/widgets/add-tokens/views/Add…
fang377 Jul 28, 2025
265cb8a
Update packages/checkout/widgets-lib/src/widgets/bridge/components/Br…
fang377 Jul 28, 2025
f0c9eca
Update packages/checkout/widgets-lib/src/widgets/bridge/components/Br…
fang377 Jul 28, 2025
62c0846
Update packages/checkout/widgets-lib/src/widgets/sale/context/SaleCon…
fang377 Jul 28, 2025
d689bb3
Fix linting
fang377 Jul 28, 2025
0d11549
Merge branch 'main' into CORE-2583-Integrate-V2-Sanction-API
keithbro-imx Aug 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/checkout/sdk/src/connect/connect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand All @@ -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'],
Expand All @@ -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 });
Expand Down
31 changes: 25 additions & 6 deletions packages/checkout/sdk/src/riskAssessment/riskAssessment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AssessmentResult> => {
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
Expand All @@ -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<RiskAssessment[]>(
`${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) {
Expand Down
11 changes: 7 additions & 4 deletions packages/checkout/sdk/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AssessmentResult>} - A promise that resolves to the risk assessment result.
*/
public async getRiskAssessment(addresses: string[]): Promise<AssessmentResult> {
return await fetchRiskAssessment(addresses, this.config);
public async getRiskAssessment(
assessmentData: Array<{ address: string; tokenAddr: string; amount: string }>,
): Promise<AssessmentResult> {
return await fetchRiskAssessment(this.config, assessmentData);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean> => {
const result = await fetchRiskAssessment(addresses, config);
const result = await fetchRiskAssessment(config, assessmentData);
return isAddressSanctioned(result, undefined);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,25 +178,35 @@ 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,
riskAssessment: assessment,
},
});
})();
}, [checkout, from, to]);
}, [checkout, from, to, formToken, formAmount]);

const canFetchEstimates = (silently: boolean): boolean => {
if (Number.isNaN(parseFloat(formAmount))) return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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,
Expand Down
14 changes: 11 additions & 3 deletions packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -205,15 +206,22 @@ 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,
riskAssessment: assessment,
},
});
})();
}, [checkout, provider]);
}, [checkout, provider, fromTokenAddress, amount]);

useEffect(() => {
swapDispatch({
Expand Down