@reown/appkit-controllers
Version:
The full stack toolkit to build onchain app UX.
144 lines • 7.06 kB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { OptionsController } from '../../src/controllers/OptionsController';
import { formatCaip19Asset, getApiUrl, getBuyStatus, getExchanges, getPayUrl } from '../../src/utils/ExchangeUtil';
describe('ExchangeUtil', () => {
beforeEach(() => {
vi.restoreAllMocks();
vi.stubGlobal('fetch', vi.fn());
// Ensure projectId is set for URL generation via snapshot
vi.spyOn(OptionsController, 'getSnapshot').mockReturnValue({
projectId: 'test-project-id',
sdkType: 'test-sdk-type',
sdkVersion: 'test-sdk-version'
});
});
afterEach(() => {
vi.unstubAllGlobals();
vi.resetAllMocks();
});
describe('getApiUrl', () => {
it('returns the correct WalletConnect JSON-RPC URL', () => {
const url = getApiUrl();
expect(url).toBe('https://rpc.walletconnect.org/v1/json-rpc?projectId=test-project-id&st=test-sdk-type&sv=test-sdk-version&source=fund-wallet');
});
});
describe('JSON-RPC requests', () => {
it('getExchanges sends request and returns result', async () => {
const params = {
page: 2,
asset: 'native',
amount: '100',
network: 'eip155:1',
projectId: 'test-project-id'
};
vi.mocked(global.fetch).mockResolvedValue({
json: vi.fn().mockResolvedValue({
jsonrpc: '2.0',
id: 1,
result: {
exchanges: [{ id: 'ex1', imageUrl: 'https://img', name: 'ExampleEx' }],
total: 1
}
})
});
const result = await getExchanges(params);
expect(global.fetch).toHaveBeenCalledTimes(1);
const [calledUrl, calledInit] = vi.mocked(global.fetch).mock.calls[0];
expect(String(calledUrl)).toBe('https://rpc.walletconnect.org/v1/json-rpc?projectId=test-project-id&st=test-sdk-type&sv=test-sdk-version&source=fund-wallet');
expect(calledInit?.method).toBe('POST');
expect(calledInit?.headers).toEqual({ 'Content-Type': 'application/json' });
const body = JSON.parse(String(calledInit?.body));
expect(body.method).toBe('reown_getExchanges');
expect(body.params).toEqual(params);
expect(result).toEqual({
exchanges: [{ id: 'ex1', imageUrl: 'https://img', name: 'ExampleEx' }],
total: 1
});
});
it('getPayUrl sends request and returns result', async () => {
const params = {
exchangeId: 'ex1',
asset: 'erc20:0xabc',
amount: '1',
recipient: '0x123',
projectId: 'test-project-id'
};
vi.mocked(global.fetch).mockResolvedValue({
json: vi.fn().mockResolvedValue({
jsonrpc: '2.0',
id: 1,
result: { url: 'https://pay.example/ex1', sessionId: 'sess-1' }
})
});
const result = await getPayUrl(params);
expect(global.fetch).toHaveBeenCalledTimes(1);
const [, calledInit] = vi.mocked(global.fetch).mock.calls[0];
const body = JSON.parse(String(calledInit?.body));
expect(body.method).toBe('reown_getExchangePayUrl');
expect(body.params).toEqual(params);
expect(result).toEqual({ url: 'https://pay.example/ex1', sessionId: 'sess-1' });
});
it('getBuyStatus sends request and returns result', async () => {
const params = { sessionId: 'sess-1', exchangeId: 'ex1', projectId: 'test-project-id' };
vi.mocked(global.fetch).mockResolvedValue({
json: vi.fn().mockResolvedValue({
jsonrpc: '2.0',
id: 1,
result: { status: 'IN_PROGRESS', txHash: undefined }
})
});
const result = await getBuyStatus(params);
expect(global.fetch).toHaveBeenCalledTimes(1);
const [, calledInit] = vi.mocked(global.fetch).mock.calls[0];
const body = JSON.parse(String(calledInit?.body));
expect(body.method).toBe('reown_getExchangeBuyStatus');
expect(body.params).toEqual(params);
expect(result).toEqual({ status: 'IN_PROGRESS', txHash: undefined });
});
it('throws when JSON-RPC returns an error', async () => {
vi.mocked(global.fetch).mockResolvedValue({
json: vi.fn().mockResolvedValue({ error: { message: 'RPC Error happened' } })
});
await expect(getExchanges({
page: 1,
asset: 'native',
amount: '1',
network: 'eip155:1'
})).rejects.toThrow('RPC Error happened');
});
});
describe('formatCaip19Asset', () => {
it('formats EVM native asset', () => {
const formatted = formatCaip19Asset('eip155:1', 'native');
expect(formatted).toBe('eip155:1/slip44:60');
});
it('formats EVM ERC20 token', () => {
const formatted = formatCaip19Asset('eip155:1', '0xabc');
expect(formatted).toBe('eip155:1/erc20:0xabc');
});
it('formats Solana native asset', () => {
const formatted = formatCaip19Asset('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', 'native');
expect(formatted).toBe('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501');
});
it('formats Solana token', () => {
const formatted = formatCaip19Asset('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', 'So11111111111111111111111111111111111111112');
expect(formatted).toBe('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:So11111111111111111111111111111111111111112');
});
it('throws for unsupported chain namespace', () => {
expect(() => formatCaip19Asset('cosmos:cosmoshub-4', 'native')).toThrow('Unsupported chain namespace for CAIP-19 formatting: cosmos');
});
it('formats BNB native asset on Binance Smart Chain with correct SLIP-44', () => {
const formatted = formatCaip19Asset('eip155:56', 'native');
expect(formatted).toBe('eip155:56/slip44:714');
});
it('formats BNB native asset on opBNB with correct SLIP-44', () => {
const formatted = formatCaip19Asset('eip155:204', 'native');
expect(formatted).toBe('eip155:204/slip44:714');
});
it('formats ERC20 token on Binance Smart Chain (not affected by override)', () => {
const formatted = formatCaip19Asset('eip155:56', '0xabc');
expect(formatted).toBe('eip155:56/erc20:0xabc');
});
});
});
//# sourceMappingURL=ExchangeUtil.test.js.map