Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
30 changes: 26 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 }>,
): Promise<AssessmentResult> => {
const result = Object.fromEntries(
addresses.map((address) => [address.toLowerCase(), { sanctioned: false }]),
Expand All @@ -41,11 +49,25 @@ 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 };

// Add token and amount data if available
Copy link
Contributor

Choose a reason for hiding this comment

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

In what scenario would the token and amount data not be available?

Should we still send through a request to the sanctions service without this information?

Copy link
Author

Choose a reason for hiding this comment

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

yea the request should still be sent even when token and amount data are not available,

In what scenario would the token and amount data not be available?

like the sale context (where token/amount are determined later in the flow), early-stage widget initialization (before user selects token/amount), and generic address validation (where we just want to check if an address is sanctioned regardless of specific transaction details) etc

if (tokenData) {
const tokenInfo = tokenData.find((t) => t.address.toLowerCase() === address.toLowerCase());
if (tokenInfo) {
if (tokenInfo.tokenAddr) item.token_addr = tokenInfo.tokenAddr;
if (tokenInfo.amount) 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] - Optional 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,
selectedToken?.address && selectedAmount ? [{
address: toAddress,
tokenAddr: selectedToken.address,
amount: selectedAmount,
}] : undefined,
))
) {
viewDispatch({
payload: {
Expand Down Expand Up @@ -502,6 +510,18 @@ export function AddTokens({
&& (await checkSanctionedAddresses(
[fromAddress, toAddress],
checkout.config,
selectedToken?.address && selectedAmount ? [
{
address: fromAddress,
tokenAddr: selectedToken.address,
amount: selectedAmount,
},
{
address: toAddress,
tokenAddr: selectedToken.address,
amount: selectedAmount,
},
] : undefined,
))
) {
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 if available
const tokenData = formToken && formAmount ? [
{
address: from.walletAddress,
tokenAddr: formToken.token.address,
amount: formAmount,
},
...(to.walletAddress.toLowerCase() !== from.walletAddress.toLowerCase() ? [{
address: to.walletAddress,
tokenAddr: formToken.token.address,
amount: formAmount,
}] : []),
] : undefined;

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 if available
const tokenData = tokenAddress && tokenAmount ? [{
address: walletAddress,
tokenAddr: tokenAddress,
amount: tokenAmount,
}] : undefined;

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,6 +257,8 @@ export function SaleContextProvider(props: {
return;
}

// For sale context, we don't have specific token/amount data at this point
// as it's determined during the sale flow, so we pass undefined for tokenData
const assessment = await fetchRiskAssessment([address], checkout.config);
setRiskAssessment(assessment);
})();
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 if available
const tokenData = fromTokenAddress && amount ? [{
address,
tokenAddr: fromTokenAddress,
amount,
}] : undefined;

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