diff --git a/README.md b/README.md index 908fd84..2553fba 100644 --- a/README.md +++ b/README.md @@ -10,101 +10,28 @@ You can install Alex-SDK using npm: 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; + getAllPossibleRoutes(from: Currency, to: Currency): Promise; + getAmountTo(from: Currency, fromAmount: bigint, to: Currency): Promise; + getBalances(stxAddress: string): Promise>; + getFeeRate(from: Currency, to: Currency): Promise; + getLatestPrices(): Promise>; + getRoute(from: Currency, to: Currency): Promise; + getRouter(from: Currency, to: Currency): Promise; // deprecated + getWayPoints(route: AMMRoute): Promise; + runSwap(stxAddress: string, currencyX: Currency, + currencyY: Currency, fromAmount: bigint, + minDy: bigint, customRoute: AMMRoute): Promise; +} +``` -- **Returns**: `Promise` - A promise that resolves to an array of TokenInfo objects, representing the swappable currencies. - -### `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` - 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` - 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` - 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` - 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` - 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` - 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>` - 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>` - A promise that resolves to an object containing the balances of each currency for the given address. +(detailed list [here](./documentation.md)). ## Usage diff --git a/documentation.md b/documentation.md index c9485e3..2183b5c 100644 --- a/documentation.md +++ b/documentation.md @@ -6,15 +6,18 @@ The AlexSDK class includes the following functions: ```typescript export declare class AlexSDK { - fetchSwappableCurrency(): Promise; - getAmountTo(from: Currency, fromAmount: bigint, to: Currency): Promise; - getBalances(stxAddress: string): Promise>; - getFeeRate(from: Currency, to: Currency): Promise; - getLatestPrices(): Promise>; - getRoute(from: Currency, to: Currency): Promise; - runSwap(stxAddress: string, currencyX: Currency, - currencyY: Currency, fromAmount: bigint, - minDy: bigint, customRoute: Currency[]): Promise; + fetchSwappableCurrency(): Promise; + getAllPossibleRoutes(from: Currency, to: Currency): Promise; + getAmountTo(from: Currency, fromAmount: bigint, to: Currency): Promise; + getBalances(stxAddress: string): Promise>; + getFeeRate(from: Currency, to: Currency): Promise; + getLatestPrices(): Promise>; + getRoute(from: Currency, to: Currency): Promise; + getRouter(from: Currency, to: Currency): Promise; // deprecated + getWayPoints(route: AMMRoute): Promise; + runSwap(stxAddress: string, currencyX: Currency, + currencyY: Currency, fromAmount: bigint, + minDy: bigint, customRoute: AMMRoute): Promise; } ``` diff --git a/jest.config.js b/jest.config.js index 25c9bac..3e7c647 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,5 @@ module.exports = { testEnvironment: 'node', + verbose: true, + maxWorkers: 1 }; diff --git a/package.json b/package.json index 555131d..197daf7 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "build": "dts build", "prepare": "pnpm run build", "test": "dts test", + "test:coverage": "dts test --coverage", "lint": "dts lint", + "lint-fix": "dts lint --fix", "size": "size-limit", "gen:contract": "rm -rf src/generated/smartContract/* && tsx ./scripts/gen-contract.ts && prettier --write 'src/generated/smartContract'", "analyze": "size-limit --why", @@ -62,6 +64,7 @@ "ajv": "^8.16.0", "dts-cli": "^2.0.5", "esm": "^3.2.25", + "fetch-mock": "^10.0.7", "husky": "^8.0.3", "prettier": "^2.8.4", "size-limit": "^8.2.4", diff --git a/test/alexSDK.mock-exceptions.test.ts b/test/alexSDK.mock-exceptions.test.ts new file mode 100644 index 0000000..9953cb0 --- /dev/null +++ b/test/alexSDK.mock-exceptions.test.ts @@ -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); +}); diff --git a/test/alexSDK.mock-externals.test.ts b/test/alexSDK.mock-externals.test.ts new file mode 100644 index 0000000..768c34c --- /dev/null +++ b/test/alexSDK.mock-externals.test.ts @@ -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); +}); diff --git a/test/alexSDK.mock-helpers.test.ts b/test/alexSDK.mock-helpers.test.ts new file mode 100644 index 0000000..4803590 --- /dev/null +++ b/test/alexSDK.mock-helpers.test.ts @@ -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); + }); +}); diff --git a/test/alexSDK.test.ts b/test/alexSDK.test.ts index 7b4ffca..7299230 100644 --- a/test/alexSDK.test.ts +++ b/test/alexSDK.test.ts @@ -40,19 +40,29 @@ const tokenDiko = 'token-wdiko' as Currency; const wrongTokenAlex = '' as Currency; const sdk = new AlexSDK(); +const CLARITY_MAX_UNSIGNED_INT = BigInt( + '340282366920938463463374607431768211455' +); describe('AlexSDK', () => { it('Verify response of getFeeRate function', async () => { const result = await sdk.getFeeRate(tokenAlex, Currency.STX); expect(typeof result).toBe('bigint'); 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 () => { await expect( sdk.getFeeRate(wrongTokenAlex, wrongTokenAlex) ).rejects.toThrow('No AMM pools in route'); - }); + }, 10000); it('Verify response of getRoute function', async () => { const result = await sdk.getRoute(Currency.STX, tokenDiko); @@ -65,13 +75,13 @@ describe('AlexSDK', () => { result.forEach((routeSegment) => { expect(typeof routeSegment.pool.tokenY).toBe('string'); }); - }); + }, 10000); it('Attempt to Get Route with wrong tokens', async () => { await expect(sdk.getRoute(wrongTokenAlex, wrongTokenAlex)).rejects.toThrow( "Can't find route" ); - }); + }, 10000); it('Verify response of getAmountTo function', async () => { const result = await sdk.getAmountTo( @@ -81,13 +91,13 @@ describe('AlexSDK', () => { ); expect(typeof result).toBe('bigint'); expect(result > BigInt(0)).toBeTruthy(); - }); + }, 10000); it('Attempt to Get Rate with a wrong From token', async () => { await expect( sdk.getAmountTo(wrongTokenAlex, BigInt(2) * BigInt(1e8), tokenDiko) ).rejects.toThrow('No AMM pool found for the given route'); - }); + }, 10000); it('Attempt to Get Rate with negative From amount', async () => { await expect( @@ -95,19 +105,19 @@ describe('AlexSDK', () => { ).rejects.toThrow( 'Cannot construct unsigned clarity integer from negative value' ); - }); + }, 10000); it('Attempt to Get Rate with an overflowing From amount (parseReadOnlyResponse)', async () => { await expect( sdk.getAmountTo(Currency.STX, BigInt(999999223372036854775807), tokenDiko) ).rejects.toThrow('ArithmeticOverflow'); - }); + }, 10000); it('Attempt to Get Rate with an overflowing From amount (decoders)', async () => { await expect( sdk.getAmountTo(Currency.STX, BigInt(99999223372036854775807), tokenDiko) ).rejects.toThrow('ClarityError: 2011'); - }); + }, 10000); it('Verify response of runSwap function', async () => { const result = await sdk.runSwap( @@ -134,7 +144,68 @@ describe('AlexSDK', () => { ]).toContain(result.functionName); expect(Array.isArray(result.functionArgs)).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 () => { 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 () => { const response = await getAlexSDKData(); expect(response).toMatchType('AlexSDKResponse'); @@ -178,4 +257,20 @@ describe('AlexSDK', () => { const tokens = await sdk.fetchSwappableCurrency(); 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); }); diff --git a/test/mock-data/alexSDKMockResponses.ts b/test/mock-data/alexSDKMockResponses.ts new file mode 100644 index 0000000..b9b5b3a --- /dev/null +++ b/test/mock-data/alexSDKMockResponses.ts @@ -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, +};