Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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 @@ -45,6 +45,10 @@ describe('riskAssessment', () => {
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 @@ -62,13 +66,14 @@ describe('riskAssessment', () => {
const sanctions = await fetchRiskAssessment(
[address1],
mockedConfig,
[{ address: address1, tokenAddr: '0xtest', amount: '100' }],
);

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 @@ -85,6 +90,7 @@ describe('riskAssessment', () => {
const sanctions = await fetchRiskAssessment(
[address1],
mockedConfig,
[{ address: address1, tokenAddr: '0xtest', amount: '100' }],
);

expect(sanctions[address1.toLowerCase()]).toEqual({ sanctioned: false });
Expand Down
32 changes: 28 additions & 4 deletions packages/checkout/sdk/src/riskAssessment/riskAssessment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,17 @@ export type AssessmentResult = {
};
};

// New type for v2 request items
type SanctionsCheckV2RequestItem = {
address: string;
amount: string;
token_addr: string;
};

export const fetchRiskAssessment = async (
addresses: string[],
config: CheckoutConfiguration,
tokenData: Array<{ address: string; tokenAddr: string; amount: string }>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is address here the same as the address is addresses - can we remove one of them so that the caller doesn't have the provide the same info twice?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep will update

): Promise<AssessmentResult> => {
const result = Object.fromEntries(
addresses.map((address) => [address.toLowerCase(), { sanctioned: false }]),
Expand All @@ -41,11 +49,27 @@ export const fetchRiskAssessment = async (
try {
const riskLevels = riskConfig?.levels.map((l) => l.toLowerCase()) ?? [];

// Prepare v2 request payload
const requestPayload: SanctionsCheckV2RequestItem[] = addresses.map((address) => {
const item: SanctionsCheckV2RequestItem = {
address,
token_addr: '',
amount: '0',
};

// Add token and amount data
const tokenInfo = tokenData.find((t) => t.address.toLowerCase() === address.toLowerCase());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like if tokenInfo can't be found, we'll send

        token_addr: '',
        amount: '0',

to the API.. what will happen? Will this cause an error for the user?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the token_addr and amount are not used in determining the risk level actually so there should not be any impact, but let me update the logic here to only call api when token info's valid

if (tokenInfo) {
item.token_addr = tokenInfo.tokenAddr;
item.amount = tokenInfo.amount;
}

return item;
});

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
10 changes: 7 additions & 3 deletions packages/checkout/sdk/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,16 @@ export class Checkout {
}

/**
* Fetches the risk assessment for the given addresses.
* Fetches risk assessment for the given addresses.
* @param {string[]} addresses - The addresses to assess.
* @param {Array<{address: string; tokenAddr: string; amount: string}>} tokenData - Required token and amount data for each address.
* @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(
addresses: string[],
tokenData: Array<{ address: string; tokenAddr: string; amount: string }>,
): Promise<AssessmentResult> {
return await fetchRiskAssessment(addresses, this.config, tokenData);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
export const checkSanctionedAddresses = async (
addresses: string[],
config: CheckoutConfiguration,
tokenData: Array<{ address: string; tokenAddr: string; amount: string }>,
): Promise<boolean> => {
const result = await fetchRiskAssessment(addresses, config);
const result = await fetchRiskAssessment(addresses, config, tokenData);
return isAddressSanctioned(result, undefined);
};
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,15 @@ export function AddTokens({
const sendRequestOnRampEvent = async () => {
if (
toAddress
&& (await checkSanctionedAddresses([toAddress], checkout.config))
&& (await checkSanctionedAddresses(
[toAddress],
checkout.config,
[{
address: toAddress,
tokenAddr: selectedToken?.address || '',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be checking that a token has been selected before calling this - rather than going ahead with the empty string?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep good callout, updated

amount: selectedAmount || '0',
}],
))
) {
viewDispatch({
payload: {
Expand Down Expand Up @@ -502,6 +510,18 @@ export function AddTokens({
&& (await checkSanctionedAddresses(
[fromAddress, toAddress],
checkout.config,
[
{
address: fromAddress,
tokenAddr: selectedToken?.address || '',
amount: selectedAmount || '0',
},
{
address: toAddress,
tokenAddr: selectedToken?.address || '',
amount: selectedAmount || '0',
},
],
))
) {
viewDispatch({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,29 @@ export function BridgeForm(props: BridgeFormProps) {
addresses.push(to.walletAddress);
}

const assessment = await fetchRiskAssessment(addresses, checkout.config);
// Prepare token data for v2 API - now required
const tokenData = [
{
address: from.walletAddress,
tokenAddr: formToken?.token.address || '',
amount: formAmount || '0',
},
...(to.walletAddress.toLowerCase() !== from.walletAddress.toLowerCase() ? [{
address: to.walletAddress,
tokenAddr: formToken?.token.address || '',
amount: formAmount || '0',
}] : []),
];

const assessment = await fetchRiskAssessment(addresses, checkout.config, tokenData);
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 @@ -231,7 +231,14 @@ export function OnRampMain({
(async () => {
const walletAddress = await (await provider.getSigner()).getAddress();

const assessment = await fetchRiskAssessment([walletAddress], checkout.config);
// Prepare token data for v2 API - now required
const tokenData = [{
address: walletAddress,
tokenAddr: tokenAddress || '',
amount: tokenAmount || '0',
}];

const assessment = await fetchRiskAssessment([walletAddress], checkout.config, tokenData);

if (isAddressSanctioned(assessment)) {
viewDispatch({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,20 @@ export function SaleContextProvider(props: {
return;
}

const assessment = await fetchRiskAssessment([address], checkout.config);
// Prepare token data for v2 API - now required
// Use selectedCurrency and orderQuote to get token address and amount when they exist
const tokenData = [{
address,
tokenAddr: selectedCurrency?.address || '',
amount: (selectedCurrency?.address && orderQuote.totalAmount[selectedCurrency.name])
? orderQuote.totalAmount[selectedCurrency.name].amount.toString()
: '0',
}];

const assessment = await fetchRiskAssessment([address], checkout.config, tokenData);
setRiskAssessment(assessment);
})();
}, [checkout, provider]);
}, [checkout, provider, selectedCurrency, orderQuote]);

const {
sign: signOrder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,14 @@ export default function SwapWidget({
return;
}

const assessment = await fetchRiskAssessment([address], checkout.config);
// Prepare token data for v2 API - now required
const tokenData = [{
address,
tokenAddr: fromTokenAddress || '',
amount: amount || '0',
}];

const assessment = await fetchRiskAssessment([address], checkout.config, tokenData);
swapDispatch({
payload: {
type: SwapActions.SET_RISK_ASSESSMENT,
Expand Down