Tests/sdk features stage 2 (#11)

* tests: enhance tests for existing functions

* tests: add test cases for existing functions

* tests: add test cases for three new functions

* tests: Introduce mock for getFeeRate function

* tests: implement workaround for Jest BigInt assertion issue

* tests: wip test cases getRoute, getAmountTo, runSwap

* tests: mocked functions runSwap, getLatestPrices

* temp commit

* tests: mocked functions getBalances, fetchSwappableCurrency

* tests: add additional tests for error scenarios

* tests: add more coverage to runSwap SDK method

* temp commit

* temp commit

* sync merge

* code linted + documentation update

* tests: SDK getWayPoints method

* tests: add lint fix command

* tests: add getFeeRate with custom route

* test: add external error tests

* fix: minor changes

* conflicts resolved

---------

Co-authored-by: ignacio.pena@coinfabrik.com <ignacio.pena@coinfabrik.com>
Co-authored-by: simsbluebox <simsbluebox@gmail.com>
Co-authored-by: david weil <david.weil@endlesstruction.com.ar>
This commit is contained in:
david weil
2024-07-15 10:25:14 -03:00
committed by GitHub
parent 4c9ee30faf
commit fc23587ce3
9 changed files with 609 additions and 111 deletions

111
README.md
View File

@@ -10,101 +10,28 @@ You can install Alex-SDK using npm:
npm install alex-sdk npm install alex-sdk
``` ```
## Methods ## Functions
### `fetchSwappableCurrency()` The AlexSDK class includes the following functions:
Fetches the list of currencies that can be swapped on the DEX. ```typescript
export declare class AlexSDK {
fetchSwappableCurrency(): Promise<TokenInfo[]>;
getAllPossibleRoutes(from: Currency, to: Currency): Promise<AMMRoute[]>;
getAmountTo(from: Currency, fromAmount: bigint, to: Currency): Promise<bigint>;
getBalances(stxAddress: string): Promise<Partial<{ [currency in Currency]: bigint }>>;
getFeeRate(from: Currency, to: Currency): Promise<bigint>;
getLatestPrices(): Promise<Partial<{ [currency in Currency]: number }>>;
getRoute(from: Currency, to: Currency): Promise<AMMRoute>;
getRouter(from: Currency, to: Currency): Promise<Currency[]>; // deprecated
getWayPoints(route: AMMRoute): Promise<TokenInfo[]>;
runSwap(stxAddress: string, currencyX: Currency,
currencyY: Currency, fromAmount: bigint,
minDy: bigint, customRoute: AMMRoute): Promise<TxToBroadCast>;
}
```
- **Returns**: `Promise<TokenInfo[]>` - A promise that resolves to an array of TokenInfo objects, representing the swappable currencies. (detailed list [here](./documentation.md)).
### `getAllPossibleRoutes(from: Currency, to: Currency)`
Retrieves all possible routes for swapping from one currency to another.
- **Parameters**:
- `from: Currency` - The currency to swap from.
- `to: Currency` - The currency to swap to.
- **Returns**: `Promise<AMMRoute[]>` - A promise that resolves to an array of AMMRoute objects, representing all possible routes for the swap.
### `getRoute(from: Currency, to: Currency)`
Retrieves the best route for swapping from one currency to another.
- **Parameters**:
- `from: Currency` - The currency to swap from.
- `to: Currency` - The currency to swap to.
- **Returns**: `Promise<AMMRoute>` - A promise that resolves to an AMMRoute object, representing the best route for the swap.
### `getWayPoints(route: AMMRoute)`
Displays the detailed route information.
- **Parameters**:
- `route: AMMRoute` - The route to display.
- **Returns**: `Promise<TokenInfo[]>` - A promise that resolves to an array of TokenInfo objects, representing the detailed information of the route.
### `getFeeRate(from: Currency, to: Currency, customRoute?: AMMRoute)`
Calculates the fee rate for a swap between two currencies.
- **Parameters**:
- `from: Currency` - The currency to swap from.
- `to: Currency` - The currency to swap to.
- `customRoute?: AMMRoute` - An optional custom route for the swap.
- **Returns**: `Promise<bigint>` - A promise that resolves to a bigint representing the fee rate for the swap.
### `getAmountTo(from: Currency, fromAmount: bigint, to: Currency, customRoute?: AMMRoute)`
Calculates the amount of the destination currency that will be received for a given amount of the source currency.
- **Parameters**:
- `from: Currency` - The currency to swap from.
- `fromAmount: bigint` - The amount of the source currency to swap.
- `to: Currency` - The currency to swap to.
- `customRoute?: AMMRoute` - An optional custom route for the swap.
- **Returns**: `Promise<bigint>` - A promise that resolves to a bigint representing the amount of the destination currency that will be received.
### `runSwap(stxAddress: string, currencyX: Currency, currencyY: Currency, fromAmount: bigint, minDy: bigint, customRoute?: AMMRoute)`
Executes a swap transaction between two currencies.
- **Parameters**:
- `stxAddress: string` - The Stacks (STX) address to execute the swap from.
- `currencyX: Currency` - The currency to swap from.
- `currencyY: Currency` - The currency to swap to.
- `fromAmount: bigint` - The amount of the source currency to swap.
- `minDy: bigint` - The minimum amount of the destination currency to receive.
- `customRoute?: AMMRoute` - An optional custom route for the swap.
- **Returns**: `Promise<TxToBroadCast>` - A promise that resolves to a TxToBroadCast object, representing the transaction to be broadcasted.
### `getLatestPrices()`
Retrieves the latest prices for all supported currencies.
- **Returns**: `Promise<Partial<{ [currency in Currency]: number }>>` - A promise that resolves to an object containing the latest prices for each currency.
### `getBalances(stxAddress: string)`
Retrieves the balances of all supported currencies for a given Stacks (STX) address.
- **Parameters**:
- `stxAddress: string` - The Stacks (STX) address to retrieve the balances for.
- **Returns**: `Promise<Partial<{ [currency in Currency]: bigint }>>` - A promise that resolves to an object containing the balances of each currency for the given address.
## Usage ## Usage

View File

@@ -6,15 +6,18 @@ The AlexSDK class includes the following functions:
```typescript ```typescript
export declare class AlexSDK { export declare class AlexSDK {
fetchSwappableCurrency(): Promise<TokenInfo[]>; fetchSwappableCurrency(): Promise<TokenInfo[]>;
getAmountTo(from: Currency, fromAmount: bigint, to: Currency): Promise<bigint>; getAllPossibleRoutes(from: Currency, to: Currency): Promise<AMMRoute[]>;
getBalances(stxAddress: string): Promise<Partial<{ [currency in Currency]: bigint }>>; getAmountTo(from: Currency, fromAmount: bigint, to: Currency): Promise<bigint>;
getFeeRate(from: Currency, to: Currency): Promise<bigint>; getBalances(stxAddress: string): Promise<Partial<{ [currency in Currency]: bigint }>>;
getLatestPrices(): Promise<Partial<{ [currency in Currency]: number }>>; getFeeRate(from: Currency, to: Currency): Promise<bigint>;
getRoute(from: Currency, to: Currency): Promise<Currency[]>; getLatestPrices(): Promise<Partial<{ [currency in Currency]: number }>>;
runSwap(stxAddress: string, currencyX: Currency, getRoute(from: Currency, to: Currency): Promise<AMMRoute>;
currencyY: Currency, fromAmount: bigint, getRouter(from: Currency, to: Currency): Promise<Currency[]>; // deprecated
minDy: bigint, customRoute: Currency[]): Promise<TxToBroadCast>; getWayPoints(route: AMMRoute): Promise<TokenInfo[]>;
runSwap(stxAddress: string, currencyX: Currency,
currencyY: Currency, fromAmount: bigint,
minDy: bigint, customRoute: AMMRoute): Promise<TxToBroadCast>;
} }
``` ```

View File

@@ -1,3 +1,5 @@
module.exports = { module.exports = {
testEnvironment: 'node', testEnvironment: 'node',
verbose: true,
maxWorkers: 1
}; };

View File

@@ -15,7 +15,9 @@
"build": "dts build", "build": "dts build",
"prepare": "pnpm run build", "prepare": "pnpm run build",
"test": "dts test", "test": "dts test",
"test:coverage": "dts test --coverage",
"lint": "dts lint", "lint": "dts lint",
"lint-fix": "dts lint --fix",
"size": "size-limit", "size": "size-limit",
"gen:contract": "rm -rf src/generated/smartContract/* && tsx ./scripts/gen-contract.ts && prettier --write 'src/generated/smartContract'", "gen:contract": "rm -rf src/generated/smartContract/* && tsx ./scripts/gen-contract.ts && prettier --write 'src/generated/smartContract'",
"analyze": "size-limit --why", "analyze": "size-limit --why",
@@ -62,6 +64,7 @@
"ajv": "^8.16.0", "ajv": "^8.16.0",
"dts-cli": "^2.0.5", "dts-cli": "^2.0.5",
"esm": "^3.2.25", "esm": "^3.2.25",
"fetch-mock": "^10.0.7",
"husky": "^8.0.3", "husky": "^8.0.3",
"prettier": "^2.8.4", "prettier": "^2.8.4",
"size-limit": "^8.2.4", "size-limit": "^8.2.4",

View File

@@ -0,0 +1,40 @@
import { AlexSDK, Currency } from '../src';
import * as ammRouteResolver from '../src/utils/ammRouteResolver';
import { configs } from '../src/config';
const sdk = new AlexSDK();
const tokenAlex = 'age000-governance-token' as Currency;
const tokenWUSDA = 'token-wusda' as Currency;
const dummyRoute = ['TokenA', 'TokenB', 'TokenC', 'TokenD', 'TokenE', 'TokenF'];
jest.mock('../src/utils/ammRouteResolver', () => ({
resolveAmmRoute: jest.fn(async () => dummyRoute),
}));
describe('AlexSDK - mock exceptions', () => {
it('Attempt to Get Fee Rate with more than 4 pools in route', async () => {
expect(jest.isMockFunction(ammRouteResolver.resolveAmmRoute)).toBeTruthy();
await expect(sdk.getFeeRate(tokenAlex, Currency.STX)).rejects.toThrow(
'Too many AMM pools in route'
);
}, 10000);
it('Attempt to getAmountTo with more than 4 pools in route', async () => {
await expect(
sdk.getAmountTo(Currency.STX, BigInt(2) * BigInt(1e8), tokenWUSDA)
).rejects.toThrow('Too many AMM pools in route');
}, 10000);
it('Attempt to run swap with more than 4 pools in route', async () => {
await expect(
sdk.runSwap(
configs.CONTRACT_DEPLOYER,
tokenAlex,
tokenWUSDA,
BigInt(2) * BigInt(1e8),
BigInt(0)
)
).rejects.toThrow('Too many AMM pools in route');
}, 10000);
});

View File

@@ -0,0 +1,146 @@
import { AlexSDK, Currency, TokenInfo } from '../src';
import fetchMock from 'fetch-mock';
import { configs } from '../src/config';
import { fetchBalanceForAccount, getPrices } from '../src/utils/fetchData';
import { transferFactory } from '../src/utils/postConditions';
const sdk = new AlexSDK();
const tokenAlex = 'age000-governance-token' as Currency;
const tokenWUSDA = 'token-wusda' as Currency;
const tokenMappings: TokenInfo[] = [
{
id: 'token-x' as Currency,
name: 'Token x',
icon: 'icon-x',
wrapToken: 'wrap-token-x',
wrapTokenDecimals: 8,
underlyingToken: 'underlying-token-x',
underlyingTokenDecimals: 8,
isRebaseToken: false,
},
];
const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT';
describe('AlexSDK - mock externals', () => {
beforeEach(() => {
fetchMock.get(configs.SDK_API_HOST, 500);
});
afterEach(() => {
fetchMock.restore();
});
it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => {
await expect(sdk.getLatestPrices()).rejects.toThrow(
'Failed to fetch token mappings'
);
}, 10000);
it('Attempt to Get Fee with incorrect Alex SDK Data', async () => {
await expect(sdk.getFeeRate(tokenAlex, Currency.STX)).rejects.toThrow(
'Failed to fetch token mappings'
);
}, 10000);
it('Attempt to Get Router with incorrect Alex SDK Data', async () => {
await expect(sdk.getRouter(tokenAlex, Currency.STX)).rejects.toThrow(
'Failed to fetch token mappings'
);
}, 10000);
it('Attempt to Get Amount with incorrect Alex SDK Data', async () => {
await expect(
sdk.getAmountTo(Currency.STX, BigInt(2) * BigInt(1e8), tokenWUSDA)
).rejects.toThrow('Failed to fetch token mappings');
}, 10000);
it('Attempt to Run Swap with incorrect Alex SDK Data', async () => {
await expect(
sdk.runSwap(
configs.CONTRACT_DEPLOYER,
tokenAlex,
tokenWUSDA,
BigInt(2) * BigInt(1e8),
BigInt(0)
)
).rejects.toThrow('Failed to fetch token mappings');
}, 10000);
it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => {
await expect(sdk.getLatestPrices()).rejects.toThrow(
'Failed to fetch token mappings'
);
}, 10000);
it('Attempt to Get Balances with incorrect Alex SDK Data', async () => {
const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT';
await expect(sdk.getBalances(stxAddress)).rejects.toThrow(
'Failed to fetch token mappings'
);
}, 10000);
it('Attempt to Fetch Swappable Currency with incorrect Alex SDK Data', async () => {
await expect(sdk.fetchSwappableCurrency()).rejects.toThrow(
'Failed to fetch token mappings'
);
}, 10000);
});
describe('AlexSDK - mock externals - BACKEND_API_HOST', () => {
beforeEach(() => {
fetchMock.get(`${configs.BACKEND_API_HOST}/v2/public/token-prices`, {
status: 500,
body: 'Internal Server Error',
});
});
afterEach(() => {
fetchMock.restore();
});
it('Attempt to get token prices with incorrect data', async () => {
await expect(getPrices(tokenMappings)).rejects.toThrow(
'Failed to fetch token mappings'
);
expect(
fetchMock.calls(`${configs.BACKEND_API_HOST}/v2/public/token-prices`)
.length
).toBe(1);
}, 10000);
});
describe('Transfer Factory', () => {
it('Throws error in Transfer Factory', () => {
const transfer = transferFactory(tokenMappings);
expect(() => transfer(stxAddress, tokenAlex, BigInt(1000))).toThrow(
'Token mapping not found'
);
});
});
describe('AlexSDK - mock externals - STACKS_API_HOST', () => {
beforeEach(() => {
fetchMock.get(
`${configs.STACKS_API_HOST}/extended/v1/address/${stxAddress}/balances`,
{
status: 500,
body: 'Internal Server Error',
}
);
});
afterEach(() => {
fetchMock.restore();
});
it('Attempt to Get Balances with incorrect data', async () => {
await expect(
fetchBalanceForAccount(stxAddress, tokenMappings)
).rejects.toThrow(
new SyntaxError(
'Unexpected token \'I\', "Internal S"... is not valid JSON'
)
);
}, 10000);
});

View File

@@ -0,0 +1,160 @@
import { AlexSDK, Currency } from '../src';
import * as FeeHelper from '../src/helpers/FeeHelper';
import * as RouteHelper from '../src/helpers/RouteHelper';
import * as RateHelper from '../src/helpers/RateHelper';
import * as SwapHelper from '../src/helpers/SwapHelper';
import * as fetchData from '../src/utils/fetchData';
import * as ammRouteResolver from '../src/utils/ammRouteResolver';
import { configs } from '../src/config';
// @ts-ignore
import {
dummyAlexSDKData,
dummyAmmRoute,
dummyBalances,
dummyCurrencies,
dummyFee,
dummyPrices,
dummyRate,
dummyTx,
parsedDummyPrices,
dummyTokenA,
dummyTokenB,
dummyFactorA,
dummyFactorB,
dummyTokenC,
} from './mock-data/alexSDKMockResponses';
import { cvToValue, FungibleConditionCode } from '@stacks/transactions';
const sdk = new AlexSDK();
const tokenAlex = 'age000-governance-token' as Currency;
const tokenWUSDA = 'token-wusda' as Currency;
// Mocked helpers and utilities for testing SDK functions
jest.mock('../src/helpers/FeeHelper', () => ({
getLiquidityProviderFee: jest.fn(async () => dummyFee),
}));
jest.mock('../src/helpers/RouteHelper', () => ({
getAllPossibleRoute: jest.fn(async () => [dummyAmmRoute, dummyAmmRoute]),
}));
jest.mock('../src/helpers/RateHelper', () => ({
getYAmountFromXAmount: jest.fn(async () => dummyRate),
}));
jest.mock('../src/helpers/SwapHelper', () => {
const originalModule = jest.requireActual('../src/helpers/SwapHelper');
return {
runSpot: jest.fn(async (deployer, ...args) => {
if (deployer !== configs.CONTRACT_DEPLOYER) {
return dummyTx;
}
return await originalModule.runSpot(deployer, ...args);
}),
};
});
jest.mock('../src/utils/fetchData', () => {
const originalModule = jest.requireActual('../src/utils/fetchData');
return {
__esModule: true,
...originalModule,
getPrices: jest.fn(async () => dummyPrices),
fetchBalanceForAccount: jest.fn(async () => dummyBalances),
getAlexSDKData: jest.fn(async () => dummyAlexSDKData),
};
});
jest.mock('../src/utils/ammRouteResolver', () => ({
resolveAmmRoute: jest.fn(() => dummyAmmRoute),
}));
describe('AlexSDK - mock helpers', () => {
it('Verify response value of getFeeRate function', async () => {
expect(jest.isMockFunction(FeeHelper.getLiquidityProviderFee)).toBeTruthy();
const result = await sdk.getFeeRate(tokenAlex, Currency.STX);
expect(result).toStrictEqual(dummyFee);
});
it('Verify response value of getAllPossibleRoute function', async () => {
expect(jest.isMockFunction(RouteHelper.getAllPossibleRoute)).toBeTruthy();
const result = await sdk.getRoute(Currency.STX, tokenWUSDA);
expect(result).toStrictEqual(dummyAmmRoute);
});
it('Verify response value of getRouter[deprecated] function', async () => {
expect(jest.isMockFunction(RouteHelper.getAllPossibleRoute)).toBeTruthy();
const result = await sdk.getRouter(Currency.STX, dummyTokenB);
expect(result).toStrictEqual([Currency.STX, dummyTokenC, dummyTokenB]);
});
it('Verify response value of getAmountTo function', async () => {
expect(jest.isMockFunction(RateHelper.getYAmountFromXAmount)).toBeTruthy();
const result = await sdk.getAmountTo(
Currency.STX,
BigInt(2) * BigInt(1e8),
tokenWUSDA
);
expect(result).toStrictEqual(dummyRate);
});
it('Verify response value of runSwap function', async () => {
expect(jest.isMockFunction(SwapHelper.runSpot)).toBeTruthy();
const result = await sdk.runSwap(
'SP111111111111111111111111111111111111111',
tokenAlex,
tokenWUSDA,
BigInt(1),
BigInt(0)
);
expect(result).toStrictEqual(dummyTx);
});
it('Verify response value of runSwap function (tx construct + rebase)', async () => {
expect(jest.isMockFunction(SwapHelper.runSpot)).toBeTruthy();
expect(jest.isMockFunction(ammRouteResolver.resolveAmmRoute)).toBeTruthy();
const amount = BigInt(2) * BigInt(1e8);
const result = await sdk.runSwap(
configs.CONTRACT_DEPLOYER,
dummyTokenA,
dummyTokenB,
amount,
BigInt(0)
);
expect(cvToValue(result.functionArgs[3])).toStrictEqual(dummyFactorA);
expect(cvToValue(result.functionArgs[4])).toStrictEqual(dummyFactorB);
expect(cvToValue(result.functionArgs[5])).toStrictEqual(amount);
expect(result.postConditions[0].conditionCode).toStrictEqual(
FungibleConditionCode.GreaterEqual
);
expect(result.postConditions[0].amount).toStrictEqual(BigInt(0));
});
it('Verify response value of getLatestPrices function', async () => {
expect(jest.isMockFunction(fetchData.getPrices)).toBeTruthy();
const result = await sdk.getLatestPrices();
expect(result).toStrictEqual(parsedDummyPrices);
});
it('Verify response value of getBalances function', async () => {
expect(jest.isMockFunction(fetchData.fetchBalanceForAccount)).toBeTruthy();
const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT';
const result = await sdk.getBalances(stxAddress);
expect(result).toStrictEqual(dummyBalances);
});
it('Verify response value of fetchSwappableCurrency function', async () => {
expect(jest.isMockFunction(fetchData.getAlexSDKData)).toBeTruthy();
const result = await sdk.fetchSwappableCurrency();
expect(result).toStrictEqual(dummyCurrencies);
});
it('Verify response value of getWayPoints function', async () => {
expect(jest.isMockFunction(fetchData.getAlexSDKData)).toBeTruthy();
expect(jest.isMockFunction(RouteHelper.getAllPossibleRoute)).toBeTruthy();
const mockedRoute = await sdk.getRoute(dummyTokenA, dummyTokenB);
const result = await sdk.getWayPoints(mockedRoute);
expect(result[0].id).toBe(dummyTokenA);
expect(result[1].id).toBe(dummyTokenC);
expect(result[2].id).toBe(dummyTokenB);
expect(result[0].isRebaseToken).toBe(true);
expect(result[1].isRebaseToken).toBe(false);
expect(result[2].isRebaseToken).toBe(false);
});
});

View File

@@ -40,19 +40,29 @@ const tokenDiko = 'token-wdiko' as Currency;
const wrongTokenAlex = '' as Currency; const wrongTokenAlex = '' as Currency;
const sdk = new AlexSDK(); const sdk = new AlexSDK();
const CLARITY_MAX_UNSIGNED_INT = BigInt(
'340282366920938463463374607431768211455'
);
describe('AlexSDK', () => { describe('AlexSDK', () => {
it('Verify response of getFeeRate function', async () => { it('Verify response of getFeeRate function', async () => {
const result = await sdk.getFeeRate(tokenAlex, Currency.STX); const result = await sdk.getFeeRate(tokenAlex, Currency.STX);
expect(typeof result).toBe('bigint'); expect(typeof result).toBe('bigint');
expect(result >= BigInt(0)).toBeTruthy(); expect(result >= BigInt(0)).toBeTruthy();
}); }, 10000);
it('Verify response of getFeeRate function (custom route)', async () => {
const customRoute = await sdk.getRoute(tokenAlex, Currency.STX);
const result = await sdk.getFeeRate(tokenAlex, Currency.STX, customRoute);
expect(typeof result).toBe('bigint');
expect(result >= BigInt(0)).toBeTruthy();
}, 10000);
it('Attempt to Get Fee Rate with wrong tokens', async () => { it('Attempt to Get Fee Rate with wrong tokens', async () => {
await expect( await expect(
sdk.getFeeRate(wrongTokenAlex, wrongTokenAlex) sdk.getFeeRate(wrongTokenAlex, wrongTokenAlex)
).rejects.toThrow('No AMM pools in route'); ).rejects.toThrow('No AMM pools in route');
}); }, 10000);
it('Verify response of getRoute function', async () => { it('Verify response of getRoute function', async () => {
const result = await sdk.getRoute(Currency.STX, tokenDiko); const result = await sdk.getRoute(Currency.STX, tokenDiko);
@@ -65,13 +75,13 @@ describe('AlexSDK', () => {
result.forEach((routeSegment) => { result.forEach((routeSegment) => {
expect(typeof routeSegment.pool.tokenY).toBe('string'); expect(typeof routeSegment.pool.tokenY).toBe('string');
}); });
}); }, 10000);
it('Attempt to Get Route with wrong tokens', async () => { it('Attempt to Get Route with wrong tokens', async () => {
await expect(sdk.getRoute(wrongTokenAlex, wrongTokenAlex)).rejects.toThrow( await expect(sdk.getRoute(wrongTokenAlex, wrongTokenAlex)).rejects.toThrow(
"Can't find route" "Can't find route"
); );
}); }, 10000);
it('Verify response of getAmountTo function', async () => { it('Verify response of getAmountTo function', async () => {
const result = await sdk.getAmountTo( const result = await sdk.getAmountTo(
@@ -81,13 +91,13 @@ describe('AlexSDK', () => {
); );
expect(typeof result).toBe('bigint'); expect(typeof result).toBe('bigint');
expect(result > BigInt(0)).toBeTruthy(); expect(result > BigInt(0)).toBeTruthy();
}); }, 10000);
it('Attempt to Get Rate with a wrong From token', async () => { it('Attempt to Get Rate with a wrong From token', async () => {
await expect( await expect(
sdk.getAmountTo(wrongTokenAlex, BigInt(2) * BigInt(1e8), tokenDiko) sdk.getAmountTo(wrongTokenAlex, BigInt(2) * BigInt(1e8), tokenDiko)
).rejects.toThrow('No AMM pool found for the given route'); ).rejects.toThrow('No AMM pool found for the given route');
}); }, 10000);
it('Attempt to Get Rate with negative From amount', async () => { it('Attempt to Get Rate with negative From amount', async () => {
await expect( await expect(
@@ -95,19 +105,19 @@ describe('AlexSDK', () => {
).rejects.toThrow( ).rejects.toThrow(
'Cannot construct unsigned clarity integer from negative value' 'Cannot construct unsigned clarity integer from negative value'
); );
}); }, 10000);
it('Attempt to Get Rate with an overflowing From amount (parseReadOnlyResponse)', async () => { it('Attempt to Get Rate with an overflowing From amount (parseReadOnlyResponse)', async () => {
await expect( await expect(
sdk.getAmountTo(Currency.STX, BigInt(999999223372036854775807), tokenDiko) sdk.getAmountTo(Currency.STX, BigInt(999999223372036854775807), tokenDiko)
).rejects.toThrow('ArithmeticOverflow'); ).rejects.toThrow('ArithmeticOverflow');
}); }, 10000);
it('Attempt to Get Rate with an overflowing From amount (decoders)', async () => { it('Attempt to Get Rate with an overflowing From amount (decoders)', async () => {
await expect( await expect(
sdk.getAmountTo(Currency.STX, BigInt(99999223372036854775807), tokenDiko) sdk.getAmountTo(Currency.STX, BigInt(99999223372036854775807), tokenDiko)
).rejects.toThrow('ClarityError: 2011'); ).rejects.toThrow('ClarityError: 2011');
}); }, 10000);
it('Verify response of runSwap function', async () => { it('Verify response of runSwap function', async () => {
const result = await sdk.runSwap( const result = await sdk.runSwap(
@@ -134,7 +144,68 @@ describe('AlexSDK', () => {
]).toContain(result.functionName); ]).toContain(result.functionName);
expect(Array.isArray(result.functionArgs)).toBeTruthy(); expect(Array.isArray(result.functionArgs)).toBeTruthy();
expect(Array.isArray(result.postConditions)).toBeTruthy(); expect(Array.isArray(result.postConditions)).toBeTruthy();
}); }, 10000);
it('Attempt to Get Tx with an invalid stx address (checksum mismatch)', async () => {
await expect(
sdk.runSwap(
'SP25DP4A9EXT42KC40QDMYQPMQCT1P0R5234GWEGS',
Currency.STX,
tokenDiko,
BigInt(100),
BigInt(0)
)
).rejects.toThrow('Invalid c32check string: checksum mismatch');
}, 10000);
it('Attempt to run swap with wrong token', async () => {
await expect(
sdk.runSwap(
'SP25DP4A9EXT42KC40QDMYQPMQCT1P0R5234GWEGS',
Currency.STX,
wrongTokenAlex,
BigInt(100),
BigInt(0)
)
).rejects.toThrow("Can't find AMM route");
}, 10000);
it('Attempt to runSwap with an invalid minDy value', async () => {
const wrongValue = CLARITY_MAX_UNSIGNED_INT + BigInt(1);
await expect(
sdk.runSwap(
configs.CONTRACT_DEPLOYER,
Currency.STX,
tokenDiko,
BigInt(0),
wrongValue
)
).rejects.toThrow(
`Cannot construct unsigned clarity integer greater than ${CLARITY_MAX_UNSIGNED_INT}`
);
}, 10000);
it('Verify response of getLatestPrices function', async () => {
const result = await sdk.getLatestPrices();
expect(result).toBeDefined();
expect(typeof result).toBe('object');
Object.values(result).forEach((value) => {
expect(typeof value).toBe('number');
expect(isNaN(Number(value))).toBe(false);
});
}, 10000);
it('Verify response of getBalances function', async () => {
const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT';
const balances = await sdk.getBalances(stxAddress);
expect(balances).toBeDefined();
expect(typeof balances).toBe('object');
Object.keys(balances).forEach((currency) => {
if (Object.values(Currency).includes(currency as Currency)) {
expect(typeof balances[currency as Currency]).toBe('bigint');
}
});
}, 10000);
it('Attempt to Get Tx with an invalid stx address (checksum mismatch)', async () => { it('Attempt to Get Tx with an invalid stx address (checksum mismatch)', async () => {
await expect( await expect(
@@ -168,6 +239,14 @@ describe('AlexSDK', () => {
}); });
}); });
it('Attempt to get balances with invalid address', async () => {
// TODO: Implement principal address verification in the SDK methods.
const wrongAddress = 'ABC';
await expect(sdk.getBalances(wrongAddress)).rejects.toThrow(
"Cannot read properties of undefined (reading 'balance')"
);
}, 10000);
it('getAlexSDKData response', async () => { it('getAlexSDKData response', async () => {
const response = await getAlexSDKData(); const response = await getAlexSDKData();
expect(response).toMatchType('AlexSDKResponse'); expect(response).toMatchType('AlexSDKResponse');
@@ -178,4 +257,20 @@ describe('AlexSDK', () => {
const tokens = await sdk.fetchSwappableCurrency(); const tokens = await sdk.fetchSwappableCurrency();
expect(await getPrices(tokens)).toMatchType('BackendAPIPriceResponse'); expect(await getPrices(tokens)).toMatchType('BackendAPIPriceResponse');
}); });
it('Verify response of getWayPoints function', async () => {
const route = await sdk.getRoute(tokenAlex, Currency.STX);
const result = await sdk.getWayPoints(route);
expect(result[0].id).toBe(tokenAlex);
expect(result[1].id).toBe(Currency.STX);
result.forEach((token) => {
expect(typeof token.name).toBe('string');
expect(typeof token.icon).toBe('string');
expect(typeof token.wrapToken).toBe('string');
expect(typeof token.underlyingToken).toBe('string');
expect(typeof token.underlyingTokenDecimals).toBe('number');
expect(token.wrapTokenDecimals).toBe(8);
expect(token.isRebaseToken).toBe(false);
});
}, 10000);
}); });

View File

@@ -0,0 +1,122 @@
import {
AlexSDKResponse,
PoolData,
PriceData,
TokenInfo,
} from '../../src/types';
import { TxToBroadCast } from '../../src/helpers/SwapHelper';
import { Currency } from '../../src';
import { AMMRouteSegment } from '../../src/utils/ammRouteResolver';
import { configs } from '../../src/config';
const validDeployer = configs.CONTRACT_DEPLOYER;
export const dummyFee = BigInt(777);
export const dummyTokenA = 'TokenA' as Currency;
export const dummyTokenB = 'TokenB' as Currency;
export const dummyTokenC = 'TokenC' as Currency;
export const dummyRoute = [dummyTokenA, dummyTokenB, dummyTokenC];
export const dummyFactorA = BigInt(670000000);
export const dummyFactorB = BigInt(680000000);
export const dummyAmmRoute: AMMRouteSegment[] = [
{
neighbour: dummyTokenC,
pool: {
tokenX: dummyTokenA,
tokenY: dummyTokenC,
factor: dummyFactorA,
},
},
{
neighbour: dummyTokenB,
pool: {
tokenX: dummyTokenC,
tokenY: dummyTokenB,
factor: dummyFactorB,
},
},
];
export const dummyRate = BigInt(1001);
export const dummyTx: TxToBroadCast = {
contractName: 'amm-pool-v2-01',
functionName: 'swap-helper',
functionArgs: [],
contractAddress: validDeployer,
postConditions: [],
};
export const dummyPrices: PriceData[] = [
{
token: dummyTokenA,
price: 1.1,
},
{
token: dummyTokenB,
price: 2.2,
},
];
export const parsedDummyPrices = {
TokenA: 1.1,
TokenB: 2.2,
};
export const dummyBalances = {
TokenA: BigInt(86794603901),
TokenB: BigInt(86794603902),
};
export const dummyCurrencies: TokenInfo[] = [
{
id: dummyTokenA,
name: 'TKA',
icon: '',
wrapTokenDecimals: 8,
wrapToken: `${validDeployer}.token-a::tka`,
underlyingToken: `${validDeployer}.token-a::tka`,
underlyingTokenDecimals: 6,
isRebaseToken: true,
},
{
id: dummyTokenB,
name: 'TKB',
icon: '',
wrapTokenDecimals: 8,
wrapToken: `${validDeployer}.token-b::tkb`,
underlyingToken: `${validDeployer}.token-b::tkb`,
underlyingTokenDecimals: 6,
isRebaseToken: false,
},
{
id: dummyTokenC,
name: 'TKC',
icon: '',
wrapTokenDecimals: 8,
wrapToken: `${validDeployer}.token-c::tkc`,
underlyingToken: `${validDeployer}.token-c::tkc`,
underlyingTokenDecimals: 6,
isRebaseToken: false,
},
];
export const dummyPools: PoolData[] = [
{
tokenX: dummyTokenA,
tokenY: dummyTokenC,
factor: BigInt(5000000),
},
{
tokenX: dummyTokenC,
tokenY: dummyTokenB,
factor: BigInt(5000000),
},
];
export const dummyAlexSDKData: AlexSDKResponse = {
tokens: dummyCurrencies,
pools: dummyPools,
};