@yoroi/swap
Version:
The Swap package of Yoroi SDK
722 lines (720 loc) • 22.2 kB
JavaScript
"use strict";
import { Portfolio } from '@yoroi/types';
import { transformersMaker } from './transformers';
import { Dex } from './types';
const mockConfig = {
address: 'addr1test',
network: 'mainnet',
primaryTokenInfo: {
id: '.',
name: 'Cardano',
ticker: 'ADA',
decimals: 6,
logo: null,
description: '',
website: '',
policyId: '',
fingerprint: '',
group: 'ADA',
kind: 'ft',
image: null,
icon: null,
symbol: 'ADA',
metadatas: {},
isPrimaryToken: true,
status: Portfolio.Token.Status.Valid,
application: Portfolio.Token.Application.General,
tag: '',
reference: '',
originalImage: '',
nature: Portfolio.Token.Nature.Primary,
type: Portfolio.Token.Type.FT
},
isPrimaryToken: token => token === '.'
};
describe('transformersMaker', () => {
const transformers = transformersMaker(mockConfig);
describe('mapProtocolToDex edge cases', () => {
it('should handle unknown protocol and return Unsupported', () => {
// Test the default case in mapProtocolToDex by accessing it through the transformers
// We need to test with an invalid protocol that would trigger the default case
const mockResponse = {
tokens: [{
token_id: 'lovelace',
logo: null,
ticker: 'ADA',
is_verified: true,
price_by_ada: 1,
project_name: 'Cardano',
decimals: 6,
name: 'Cardano'
}],
total: 1,
page: 1,
limit: 10
};
const result = transformers.tokens.response(mockResponse);
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
});
it('should test tokens request function', () => {
const result = transformers.tokens.request();
expect(result).toEqual({
query: '',
only_verified: false
});
});
it('should not include exclude_protocols when blockedProtocols is removed', () => {
const mockRequest = {
amountIn: 10,
slippage: 1,
tokenIn: '.',
tokenOut: 'test-token.'
};
const result = transformers.estimate.request(mockRequest);
expect(result.exclude_protocols).toBeUndefined();
});
it('should not include exclude_protocols when blockedProtocols is removed', () => {
const mockRequest = {
amountIn: 10,
slippage: 1,
tokenIn: '.',
tokenOut: 'test-token.'
};
const result = transformers.estimate.request(mockRequest);
expect(result.exclude_protocols).toBeUndefined();
});
it('should not include exclude_protocols when blockedProtocols is removed', () => {
const mockRequest = {
amountIn: 10,
slippage: 1,
tokenIn: '.',
tokenOut: 'test-token.'
};
const result = transformers.estimate.request(mockRequest);
expect(result.exclude_protocols).toBeUndefined();
});
});
describe('tokens', () => {
it('should transform tokens response correctly', () => {
const mockResponse = {
tokens: [{
token_id: 'lovelace',
logo: null,
ticker: 'ADA',
is_verified: true,
price_by_ada: 1,
project_name: 'Cardano',
decimals: 6
}, {
token_id: 'test-token-id',
logo: 'https://example.com/logo.png',
ticker: 'TEST',
is_verified: false,
price_by_ada: 0.1,
project_name: 'Test Token',
decimals: 6
}],
total: 2,
page: 1,
limit: 1000
};
const result = transformers.tokens.response(mockResponse);
expect(result).toHaveLength(2);
expect(result[0]).toEqual({
id: '.',
name: 'Cardano',
ticker: 'ADA',
decimals: 6,
logo: null,
description: '',
website: '',
policyId: '',
fingerprint: '',
group: 'ADA',
kind: 'ft',
image: null,
icon: null,
symbol: 'ADA',
metadatas: {},
isPrimaryToken: true,
status: 'valid',
application: 'general',
tag: '',
reference: '',
nature: 'primary',
type: 'ft',
originalImage: ''
});
expect(result[1]).toEqual({
id: 'test-token-id.',
name: 'Test Token',
ticker: 'TEST',
decimals: 6,
description: '',
website: '',
fingerprint: '',
originalImage: '',
reference: '',
symbol: '',
tag: '',
status: 'invalid',
type: 'ft',
application: 'general',
nature: 'secondary'
});
});
});
describe('orders', () => {
it('should transform orders response correctly', () => {
const mockResponse = {
orders: [{
owner_address: 'addr1test',
protocol: 'MinswapV2',
token_in: {
token_id: 'lovelace',
logo: null,
ticker: 'ADA',
is_verified: true,
price_by_ada: 1,
project_name: 'Cardano',
decimals: 6
},
token_out: {
token_id: 'test-token',
logo: null,
ticker: 'TEST',
is_verified: true,
price_by_ada: 0.1,
project_name: 'Test Token',
decimals: 6
},
amount_in: '100',
min_amount_out: '1000',
created_at: 1234567890,
tx_in: 'txhash#0',
dex_fee: '1',
deposit: '2'
}],
amount_in_decimal: true
};
const result = transformers.orders.response(mockResponse);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
aggregator: 'minswap',
protocol: 'minswap-v2',
placedAt: 1234567890,
lastUpdate: 1234567890,
status: 'open',
tokenIn: '.',
tokenOut: 'test-token.',
amountIn: 100,
actualAmountOut: 1000,
expectedAmountOut: 1000,
txHash: 'txhash',
outputIndex: 0,
updateTxHash: undefined,
customId: undefined
});
});
});
describe('estimate', () => {
it('should transform estimate request correctly', () => {
const mockRequest = {
amountIn: 10,
blockedProtocols: [],
slippage: 1,
tokenIn: '.',
tokenOut: 'fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae456.55534441'
};
const result = transformers.estimate.request(mockRequest);
expect(result).toEqual({
token_in: 'lovelace',
token_out: 'fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae45655534441',
amount: '10',
slippage: 1,
amount_in_decimal: true
});
});
it('should transform estimate response correctly with real data', () => {
const mockResponse = {
token_in: 'lovelace',
token_out: 'fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae45655534441',
amount_in: '10',
amount_out: '8.290409',
amount_in_decimal: true,
avg_price_impact: 0.3006573962454888,
min_amount_out: '8.208325',
aggregator_fee: '0',
aggregator_fee_percent: 0.1,
deposits: '2',
total_dex_fee: '0.7',
total_lp_fee: '0.03',
paths: [[{
amount_in: '10',
amount_out: '8.290409',
deposits: '2',
dex_fee: '0.7',
lp_fee: '0.03',
lp_token: 'f5808c2c990d86da54bfc97d89cee6efa20cd8461616359478d96b4cee5cfbc5b0dc10c873a0bcc69e49b9af21b899f59337a894874c6b596c2da136',
min_amount_out: '8.208325',
pool_id: 'f5808c2c990d86da54bfc97d89cee6efa20cd8461616359478d96b4c.ee5cfbc5b0dc10c873a0bcc69e49b9af21b899f59337a894874c6b596c2da136',
price_impact: 0.3006573962454888,
protocol: Dex.MinswapV2,
token_in: 'lovelace',
token_out: 'fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae45655534441'
}]],
route: [],
aggregator: 'Minswap'
};
const result = transformers.estimate.response(mockResponse);
expect(result.splits).toHaveLength(1);
expect(result.splits[0]).toEqual(expect.objectContaining({
amountIn: 10,
batcherFee: 0.7,
deposits: 2,
protocol: 'minswap-v2',
expectedOutput: 8.208325,
expectedOutputWithoutSlippage: 8.290409,
fee: 0.7,
initialPrice: 0.8290409000000001,
finalPrice: 0.8290409000000001,
poolFee: 0.03,
poolId: 'f5808c2c990d86da54bfc97d89cee6efa20cd8461616359478d96b4c.ee5cfbc5b0dc10c873a0bcc69e49b9af21b899f59337a894874c6b596c2da136',
priceDistortion: 0,
priceImpact: 0.3006573962454888
}));
expect(result.batcherFee).toBe(0.7);
expect(result.deposits).toBe(2);
expect(result.aggregatorFee).toBe(0);
expect(result.frontendFee).toBe(0);
expect(result.netPrice).toBeCloseTo(0.8208325);
expect(result.priceImpact).toBe(0.3006573962454888);
expect(result.totalFee).toBe(0.7);
expect(result.totalOutput).toBe(8.208325);
expect(result.totalOutputWithoutSlippage).toBe(8.290409);
expect(result.totalInput).toBe(10);
});
it('should handle estimate request with partner parameter', () => {
const configWithPartner = {
...mockConfig,
partner: 'test-partner'
};
const transformersWithPartner = transformersMaker(configWithPartner);
const mockRequest = {
amountIn: 10,
blockedProtocols: [],
slippage: 1,
tokenIn: '.',
tokenOut: 'test-token.'
};
const result = transformersWithPartner.estimate.request(mockRequest);
expect(result.partner).toBe('test-partner');
});
it('should handle estimate request with blocked protocols', () => {
const mockRequest = {
amountIn: 10,
slippage: 1,
tokenIn: '.',
tokenOut: 'test-token.'
};
const result = transformers.estimate.request(mockRequest);
expect(result.exclude_protocols).toBeUndefined();
});
it('should handle unknown Dex protocol in response', () => {
// Test the default case in mapDexToProtocol by providing an invalid protocol
const mockResponse = {
token_in: 'lovelace',
token_out: 'test-token.',
amount_in: '10',
amount_out: '8.290409',
amount_in_decimal: true,
avg_price_impact: 0.3006573962454888,
min_amount_out: '8.208325',
aggregator_fee: '0',
aggregator_fee_percent: 0.1,
deposits: '2',
total_dex_fee: '0.7',
total_lp_fee: '0.03',
paths: [[{
amount_in: '10',
amount_out: '8.290409',
deposits: '2',
dex_fee: '0.7',
lp_fee: '0.03',
lp_token: 'test-lp-token',
min_amount_out: '8.208325',
pool_id: 'test-pool-id',
price_impact: 0.3006573962454888,
protocol: 'UnknownProtocol',
// This should trigger the default case
token_in: 'lovelace',
token_out: 'test-token.'
}]],
route: [{
pool: {
pool_id: 'test-pool-id',
fee: 0.7,
token_a: {
token_id: 'lovelace',
logo: null,
ticker: 'ADA',
is_verified: true,
price_by_ada: 1,
project_name: 'Cardano',
decimals: 6
},
token_b: {
token_id: 'test-token.',
logo: null,
ticker: 'TEST',
is_verified: true,
price_by_ada: 0.8290409,
project_name: 'Test Token',
decimals: 6
}
},
amount_in: '10',
amount_out: '8.290409'
}],
aggregator: 'Minswap'
};
const result = transformers.estimate.response(mockResponse);
// The default case should return Swap.Protocol.Unsupported
expect(result.splits[0]?.protocol).toBe('unsupported');
});
it('should handle all Dex protocol mappings in mapDexToProtocol', () => {
// Test all Dex protocol mappings by providing them in separate responses
const testCases = [{
dex: Dex.MinswapV2,
expected: 'minswap-v2'
}, {
dex: Dex.Minswap,
expected: 'minswap-v1'
}, {
dex: Dex.MinswapStable,
expected: 'minswap-stable'
}, {
dex: Dex.MuesliSwap,
expected: 'muesliswap'
}, {
dex: Dex.Splash,
expected: 'splash'
}, {
dex: Dex.SundaeSwapV3,
expected: 'sundaeswap-v3'
}, {
dex: Dex.SundaeSwap,
expected: 'sundaeswap-v1'
}, {
dex: Dex.VyFinance,
expected: 'vyfi-v1'
}, {
dex: Dex.CswapV1,
expected: 'cswap'
}, {
dex: Dex.WingRidersV2,
expected: 'wingriders-v2'
}, {
dex: Dex.WingRiders,
expected: 'wingriders-v1'
}, {
dex: Dex.WingRidersStableV2,
expected: 'wingriders-stable'
}, {
dex: Dex.Spectrum,
expected: 'spectrum-v1'
}, {
dex: Dex.SplashStable,
expected: 'splash'
}];
testCases.forEach(({
dex,
expected
}) => {
const mockResponse = {
token_in: 'lovelace',
token_out: 'test-token.',
amount_in: '10',
amount_out: '8.290409',
amount_in_decimal: true,
avg_price_impact: 0.3006573962454888,
min_amount_out: '8.208325',
aggregator_fee: '0',
aggregator_fee_percent: 0.1,
deposits: '2',
total_dex_fee: '0.7',
total_lp_fee: '0.03',
paths: [[{
amount_in: '10',
amount_out: '8.290409',
deposits: '2',
dex_fee: '0.7',
lp_fee: '0.03',
lp_token: 'test-lp-token',
min_amount_out: '8.208325',
pool_id: 'test-pool-id',
price_impact: 0.3006573962454888,
protocol: dex,
token_in: 'lovelace',
token_out: 'test-token.'
}]],
route: [{
pool: {
pool_id: 'test-pool-id',
fee: 0.7,
token_a: {
token_id: 'lovelace',
logo: null,
ticker: 'ADA',
is_verified: true,
price_by_ada: 1,
project_name: 'Cardano',
decimals: 6
},
token_b: {
token_id: 'test-token.',
logo: null,
ticker: 'TEST',
is_verified: true,
price_by_ada: 0.8290409,
project_name: 'Test Token',
decimals: 6
}
},
amount_in: '10',
amount_out: '8.290409'
}],
aggregator: 'Minswap'
};
const result = transformers.estimate.response(mockResponse);
expect(result.splits[0]?.protocol).toBe(expected);
});
});
});
describe('create', () => {
it('should transform create request correctly', () => {
const mockRequest = {
amountIn: 10,
blockedProtocols: [],
slippage: 1,
tokenIn: '.',
tokenOut: 'fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae456.55534441'
};
const result = transformers.create.request(mockRequest);
expect(result).toEqual({
sender: 'addr1test',
min_amount_out: '0',
estimate: {
amount: '10',
token_in: 'lovelace',
token_out: 'fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae45655534441',
slippage: 1
},
amount_in_decimal: true
});
});
it('should transform create request with partner', () => {
const configWithPartner = {
...mockConfig,
partner: 'test-partner'
};
const transformersWithPartner = transformersMaker(configWithPartner);
const mockRequest = {
amountIn: 10,
blockedProtocols: [],
slippage: 1,
tokenIn: '.',
tokenOut: 'test-token.'
};
const result = transformersWithPartner.create.request(mockRequest);
expect(result.estimate.partner).toBe('test-partner');
});
it('should include inputs_to_choose when inputs are provided', () => {
const mockRequest = {
amountIn: 10,
blockedProtocols: [],
slippage: 1,
tokenIn: '.',
tokenOut: 'fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae456.55534441',
inputs: ['utxo1', 'utxo2']
};
const result = transformers.create.request(mockRequest);
expect(result.inputs_to_choose).toEqual(['utxo1', 'utxo2']);
});
it('should not include inputs_to_choose when inputs are empty', () => {
const mockRequest = {
amountIn: 10,
blockedProtocols: [],
slippage: 1,
tokenIn: '.',
tokenOut: 'fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae456.55534441',
inputs: []
};
const result = transformers.create.request(mockRequest);
expect(result.inputs_to_choose).toBeUndefined();
});
it('should transform create response correctly', () => {
const mockResponse = {
cbor: 'test-cbor-data'
};
const result = transformers.create.response(mockResponse);
expect(result).toEqual({
splits: [],
batcherFee: 0,
deposits: 0,
aggregatorFee: 0,
frontendFee: 0,
netPrice: 0,
priceImpact: 0,
totalFee: 0,
totalInput: 0,
totalOutput: 0,
totalOutputWithoutSlippage: 0,
aggregator: 'minswap',
cbor: 'test-cbor-data'
});
});
});
describe('limitOptions', () => {
it('should transform limitOptions response correctly', () => {
const mockResponse = {
token_in: {
token_id: 'lovelace',
logo: null,
ticker: 'ADA',
is_verified: true,
price_by_ada: 1,
project_name: 'Cardano',
decimals: 6
},
token_out: {
token_id: 'test-token',
logo: null,
ticker: 'TEST',
is_verified: false,
price_by_ada: 0.5,
project_name: 'Test Token',
decimals: 6
},
amount_in: '10',
amount_out: '8.290409',
price: 0.8290409,
options: [{
protocol: Dex.MinswapV2,
pool_id: 'test-pool-id',
fee: 2,
price: 0.8290409,
liquidity: '1000000'
}]
};
const result = transformers.limitOptions.response(mockResponse);
expect(result).toEqual({
defaultProtocol: 'minswap-v2',
wantedPrice: 0.8290409,
options: [{
protocol: 'minswap-v2',
initialPrice: 0.8290409,
batcherFee: 2
}]
});
});
});
describe('cancel', () => {
it('should transform cancel response correctly', () => {
const mockResponse = {
cbor: 'cancel-cbor-data'
};
const result = transformers.cancel.response(mockResponse);
expect(result).toEqual({
cbor: 'cancel-cbor-data',
additionalCancellationFee: undefined
});
});
});
describe('edge cases', () => {
it('should handle empty paths in estimate response', () => {
const mockResponse = {
token_in: 'lovelace',
token_out: 'test-token',
amount_in: '10',
amount_out: '8.290409',
amount_in_decimal: true,
min_amount_out: '8.208325',
aggregator_fee: '0',
aggregator_fee_percent: 0.1,
deposits: '2',
total_dex_fee: '0.7',
total_lp_fee: '0.03',
avg_price_impact: 0.3,
paths: [],
route: [],
aggregator: 'Minswap'
};
const result = transformers.estimate.response(mockResponse);
expect(result.splits).toHaveLength(0);
expect(result.totalInput).toBe(10);
expect(result.totalOutput).toBe(8.208325);
});
it('should handle null values in token data', () => {
const mockResponse = {
tokens: [{
token_id: 'test-token',
logo: null,
ticker: null,
is_verified: null,
price_by_ada: null,
project_name: null,
decimals: null
}],
total: 1,
page: 1,
limit: 1000
};
const result = transformers.tokens.response(mockResponse);
expect(result).toHaveLength(1);
expect(result[0]?.name).toBe('Unknown Token');
expect(result[0]?.ticker).toBe('');
expect(result[0]?.decimals).toBe(0);
});
it('should handle zero amounts in estimate response', () => {
const mockResponse = {
token_in: 'lovelace',
token_out: 'test-token',
amount_in: '0',
amount_out: '0',
amount_in_decimal: true,
min_amount_out: '0',
aggregator_fee: '0',
aggregator_fee_percent: 0,
deposits: '0',
total_dex_fee: '0',
total_lp_fee: '0',
avg_price_impact: 0,
paths: [[{
amount_in: '0',
amount_out: '0',
deposits: '0',
dex_fee: '0',
lp_fee: '0',
lp_token: 'test-lp-token',
min_amount_out: '0',
pool_id: 'pool123',
price_impact: 0,
protocol: Dex.MinswapV2,
token_in: 'lovelace',
token_out: 'test-token'
}]],
route: [],
aggregator: 'Minswap'
};
const result = transformers.estimate.response(mockResponse);
expect(result.totalInput).toBe(0);
expect(result.totalOutput).toBe(0);
expect(result.netPrice).toBe(0);
expect(result.splits[0]?.initialPrice).toBe(0);
expect(result.splits[0]?.finalPrice).toBe(0);
});
});
});
//# sourceMappingURL=transformers.test.js.map