├── .github └── workflows │ ├── ci.yaml │ └── docs.yaml ├── .gitignore ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── pnpm-lock.yaml ├── scripts └── gen-contract.ts ├── src ├── alexSDK.ts ├── config.ts ├── currency.ts ├── generated │ ├── smartContract │ │ ├── contract_Alex_alex-amm-pool-v2-01-get-helper.ts │ │ ├── contract_Alex_amm-pool-v2-01.ts │ │ ├── contract_Alex_sponsor-dex-v01.ts │ │ └── contracts_Alex.ts │ └── smartContractHelpers │ │ ├── codegenImport.ts │ │ └── errorCodes.json ├── helpers │ ├── FeeHelper.ts │ ├── RateHelper.ts │ ├── RouteHelper.ts │ ├── SponsorTxHelper.ts │ └── SwapHelper.ts ├── index.ts ├── types.ts └── utils │ ├── ammRouteResolver.ts │ ├── arrayHelper.ts │ ├── fetchData.ts │ ├── postConditions.ts │ ├── promiseHelpers.ts │ ├── readonlyCallExecutor.ts │ ├── sampleResponse.json │ └── utils.ts ├── test ├── alexSDK.mock-exceptions.test.ts ├── alexSDK.mock-externals.test.ts ├── alexSDK.mock-helpers.test.ts ├── alexSDK.test.ts ├── ammRouteResolver.test.ts └── mock-data │ └── alexSDKMockResponses.ts └── tsconfig.json /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | timeout-minutes: 20 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Setup pnpm 12 | run: corepack enable 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: "18" 16 | cache: "pnpm" 17 | - name: Install Deps 18 | run: pnpm install --frozen-lockfile --verify-store-integrity 19 | - name: Test 20 | run: pnpm run ci 21 | env: 22 | CI: true 23 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # or your default branch name 7 | 8 | # Add this permissions block 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | jobs: 15 | build-and-deploy: 16 | runs-on: ubuntu-latest 17 | environment: 18 | name: github-pages 19 | url: ${{ steps.deployment.outputs.page_url }} 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: '20' 29 | 30 | - name: Install pnpm 31 | uses: pnpm/action-setup@v2 32 | with: 33 | version: 9 34 | 35 | - name: Install dependencies 36 | run: pnpm install 37 | 38 | - name: Build docs 39 | run: pnpm run docs 40 | 41 | - name: Setup Pages 42 | uses: actions/configure-pages@v4 43 | 44 | - name: Upload artifact 45 | uses: actions/upload-pages-artifact@v3 46 | with: 47 | path: './docs' 48 | 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v4 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | docs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 alexgo.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alex-SDK 2 | 3 | Alex-SDK is an easy-to-use library that exposes the swap functionality from [alexlab.co](https://app.alexlab.co/swap) to be integrated into any app or wallet. It enables users to perform swaps with a wide variety of supported currencies. 4 | 5 | ## Installation 6 | 7 | You can install Alex-SDK using npm: 8 | 9 | ```bash 10 | npm install alex-sdk 11 | ``` 12 | 13 | ## Documentation 14 | 15 | For detailed API documentation, including a full list of available methods and their usage, please refer to: 16 | 17 | [SDK API Documentation](https://alexgo-io.github.io/alex-sdk/). 18 | 19 | ## Usage 20 | 21 | ```typescript 22 | import { AlexSDK, Currency } from 'alex-sdk'; 23 | 24 | const alex = new AlexSDK(); 25 | 26 | (async () => { 27 | // Get swap fee between STX and ALEX 28 | const feeRate = await alex.getFeeRate(Currency.STX, Currency.ALEX); 29 | console.log('Swap fee:', feeRate); 30 | 31 | // Get the router path for swapping STX to ALEX 32 | const router = await alex.getRoute(Currency.STX, Currency.ALEX); 33 | console.log('Router path:', router); 34 | 35 | // Get the amount of USDA that will be received when swapping 100 ALEX 36 | const amountTo = await alex.getAmountTo( 37 | Currency.STX, 38 | BigInt(100 * 1e8), // all decimals are multiplied by 1e8 39 | Currency.ALEX 40 | ); 41 | console.log('Amount to receive:', Number(amountTo) / 1e8); 42 | 43 | // To get the transaction to broadcast 44 | const tx = await alex.runSwap( 45 | stxAddress, 46 | Currency.STX, 47 | Currency.ALEX, 48 | BigInt(Number(amount) * 1e8), 49 | BigInt(0) 50 | ); 51 | 52 | // Then broadcast the transaction yourself 53 | await openContractCall(tx); 54 | 55 | // Get the latest prices for all supported currencies 56 | const latestPrices = await alex.getLatestPrices(); 57 | console.log('Latest prices:', latestPrices); 58 | 59 | // Get balances for a specific STX address 60 | const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; 61 | const balances = await alex.getBalances(stxAddress); 62 | console.log('Balances:', balances); 63 | 64 | // Fetch information about all swappable currencies 65 | const swappableCurrencies = await alex.fetchSwappableCurrency(); 66 | console.log('Swappable currencies:', swappableCurrencies); 67 | 68 | // Get all possible routes for swapping currencies 69 | const allRoutes = await alex.getAllPossibleRoutes(Currency.STX, Currency.ALEX); 70 | console.log('All possible routes:', allRoutes); 71 | 72 | // Get way points 73 | const someRoute: AMMRoute = [x1, x2]; 74 | const wayPoints = await sdk.getWayPoints(someRoute); 75 | console.log('Way points for the route:', wayPoints); 76 | })(); 77 | ``` 78 | 79 | There is a fully working example in the [alex-sdk-example](https://github.com/alexgo-io/alex-sdk-example). 80 | 81 | ## Contributing 82 | 83 | Contributions to the project are welcome. Please fork the repository, make your changes, and submit a pull request. Ensure your changes follow the code style and conventions used. 84 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | verbose: true, 4 | maxWorkers: 1 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "pnpm@9.4.0", 3 | "version": "3.2.1", 4 | "license": "MIT", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "files": [ 8 | "dist", 9 | "src" 10 | ], 11 | "engines": { 12 | "node": ">=10" 13 | }, 14 | "scripts": { 15 | "start": "dts watch", 16 | "build": "dts build", 17 | "prepare": "pnpm run build", 18 | "test": "dts test", 19 | "test:coverage": "dts test --coverage", 20 | "lint": "dts lint", 21 | "lint-fix": "dts lint --fix", 22 | "size": "size-limit", 23 | "gen:contract": "rm -rf src/generated/smartContract/* && tsx ./scripts/gen-contract.ts && prettier --write 'src/generated/smartContract'", 24 | "docs": "typedoc src/index.ts", 25 | "docs:watch": "typedoc src/index.ts --watch", 26 | "analyze": "size-limit --why", 27 | "ci": "pnpm run lint && pnpm run test && pnpm run size" 28 | }, 29 | "husky": { 30 | "hooks": { 31 | "pre-commit": "dts lint" 32 | } 33 | }, 34 | "prettier": { 35 | "printWidth": 80, 36 | "semi": true, 37 | "singleQuote": true, 38 | "trailingComma": "es5" 39 | }, 40 | "name": "alex-sdk", 41 | "author": "Kyle Fang", 42 | "module": "dist/alex-sdk.esm.js", 43 | "size-limit": [ 44 | { 45 | "path": "dist/alex-sdk.cjs.production.min.js", 46 | "limit": "20 KB" 47 | }, 48 | { 49 | "path": "dist/alex-sdk.esm.js", 50 | "limit": "20 KB" 51 | } 52 | ], 53 | "dependencies": { 54 | "clarity-codegen": "1.0.0-beta.1" 55 | }, 56 | "peerDependencies": { 57 | "@stacks/network": "^7.0.2", 58 | "@stacks/transactions": "^7.0.2" 59 | }, 60 | "devDependencies": { 61 | "@size-limit/preset-small-lib": "^8.2.4", 62 | "@stacks/network": "^7.0.2", 63 | "@stacks/stacks-blockchain-api-types": "^7.11.0", 64 | "@stacks/transactions": "^7.0.2", 65 | "@types/jest": "^29.5.12", 66 | "@types/node": "^20.14.2", 67 | "ajv": "^8.16.0", 68 | "dts-cli": "^2.0.5", 69 | "esm": "^3.2.25", 70 | "fetch-mock": "^10.0.7", 71 | "husky": "^8.0.3", 72 | "prettier": "^2.8.4", 73 | "size-limit": "^8.2.4", 74 | "ts-json-schema-generator": "^2.3.0", 75 | "tslib": "^2.6.3", 76 | "tsx": "^4.15.5", 77 | "typedoc": "^0.26.6", 78 | "typescript": "^5.4.5" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /scripts/gen-contract.ts: -------------------------------------------------------------------------------- 1 | import { generateContracts } from 'clarity-codegen/lib/generate'; 2 | import * as path from 'node:path'; 3 | import { configs } from '../src/config'; 4 | 5 | const API_HOST = configs.READONLY_CALL_API_HOST; 6 | const ALEX_CONTRACT_DEPLOYER = configs.CONTRACT_DEPLOYER; 7 | const QUOTE_CONTRACT_DEPLOYER = configs.QUOTE_CONTRACT_DEPLOYER; 8 | const SPONSOR_CONTRACT_DEPLOYER = configs.SPONSOR_TX_DEPLOYER; 9 | 10 | const contracts = ['amm-pool-v2-01', 'sponsor-dex-v01', 'alex-amm-pool-v2-01-get-helper']; 11 | 12 | (async function main() { 13 | await generateContracts( 14 | API_HOST, 15 | (contract) => contract === 'sponsor-dex-v01' ? SPONSOR_CONTRACT_DEPLOYER : contract === 'alex-amm-pool-v2-01-get-helper' ? QUOTE_CONTRACT_DEPLOYER : ALEX_CONTRACT_DEPLOYER, 16 | contracts, 17 | path.resolve(__dirname, '../src/generated/smartContract/'), 18 | 'Alex', 19 | '../smartContractHelpers/codegenImport' 20 | ); 21 | })().catch(console.error); 22 | -------------------------------------------------------------------------------- /src/alexSDK.ts: -------------------------------------------------------------------------------- 1 | import { Currency } from './currency'; 2 | import { getLiquidityProviderFee } from './helpers/FeeHelper'; 3 | import { getYAmountFromXAmount } from './helpers/RateHelper'; 4 | import { getAllPossibleRoute, getDetailedRoute } from './helpers/RouteHelper'; 5 | import { 6 | broadcastSponsoredTx, 7 | getSponsorData, 8 | requiredStxAmountForSponsorTx, 9 | runSponsoredSpotTx, 10 | SponsoredTxError, 11 | SponsoredTxErrorCode, 12 | } from './helpers/SponsorTxHelper'; 13 | import { runSpot, type TxToBroadCast } from './helpers/SwapHelper'; 14 | import { 15 | type AlexSDKResponse, 16 | type DetailedAMMRoutes as DetailedAMMRoute, 17 | type PoolData, 18 | type PriceData, 19 | type TokenInfo, 20 | } from './types'; 21 | import type { AMMRoute } from './utils/ammRouteResolver'; 22 | import { 23 | fetchBalanceForAccount, 24 | getAlexSDKData, 25 | getPrices, 26 | } from './utils/fetchData'; 27 | import { props } from './utils/promiseHelpers'; 28 | import { fromEntries, isNotNull } from './utils/utils'; 29 | 30 | /** 31 | * The AlexSDK class provides methods for interacting with a decentralized exchange (DEX) system, 32 | * allowing users to fetch currency information, calculate routes for swaps, estimate fees and 33 | * amounts, perform swaps, retrieve latest prices, and check balances. 34 | */ 35 | export class AlexSDK { 36 | private alexSDKData?: Promise; 37 | 38 | private async getAlexSDKData(): Promise { 39 | if (this.alexSDKData == null) { 40 | this.alexSDKData = getAlexSDKData(); 41 | } 42 | return this.alexSDKData; 43 | } 44 | 45 | private async getTokenInfos(): Promise { 46 | return (await this.getAlexSDKData()).tokens; 47 | } 48 | 49 | private async getTokenMappings(): Promise<{ [P in Currency]: TokenInfo }> { 50 | return fromEntries((await this.getTokenInfos()).map((x) => [x.id, x])); 51 | } 52 | 53 | private async getContractId(): Promise<(currency: Currency) => string> { 54 | const mappings = await this.getTokenMappings(); 55 | return (currency) => mappings[currency].wrapToken.split('::')[0]; 56 | } 57 | 58 | private async getPools(): Promise { 59 | return (await this.getAlexSDKData()).pools; 60 | } 61 | 62 | /** 63 | * This function returns an array of TokenInfo objects, each containing detailed 64 | * information about a supported swappable currency. 65 | * 66 | * @returns {Promise} - A promise that resolves to an array of 67 | * `TokenInfo` objects representing the currencies available for swaps. 68 | */ 69 | fetchSwappableCurrency(): Promise { 70 | return this.getTokenInfos(); 71 | } 72 | 73 | /** 74 | * Fetch the token info for a given token address. 75 | * @param tokenAddress - The address of the token to fetch info for. e.g SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex 76 | * @returns A promise that resolves to a `TokenInfo` object or `null` if the token is not found. 77 | */ 78 | async fetchTokenInfo(tokenAddress: string): Promise { 79 | return ( 80 | (await this.getTokenInfos()).find( 81 | (x) => 82 | x.wrapToken.split('::')[0] === tokenAddress.split('::')[0] || 83 | x.underlyingToken.split('::')[0] === tokenAddress.split('::')[0] 84 | ) ?? null 85 | ); 86 | } 87 | 88 | private async getPrices(): Promise { 89 | return getPrices(await this.getTokenInfos()); 90 | } 91 | 92 | /** 93 | * This function returns all possible routes for swapping between two specified currencies. 94 | * It returns an array of AMMRoute, representing possible swap routes. 95 | * 96 | * @param {Currency} from - The currency to swap from. 97 | * @param {Currency} to - The currency to swap to. 98 | * @returns {Promise} - A promise that resolves to an array of AMMRoute objects, 99 | * representing all possible swap routes between the two specified currencies. 100 | */ 101 | async getAllPossibleRoutes( 102 | from: Currency, 103 | to: Currency 104 | ): Promise { 105 | return await getAllPossibleRoute(from, to, await this.getPools()); 106 | } 107 | 108 | /** 109 | * This function returns all possible routes for swapping between two specified currencies, 110 | * along with additional details for each route. 111 | * 112 | * @param {Currency} from - The currency to swap from. 113 | * @param {Currency} to - The currency to swap to. 114 | * @param {bigint} fromAmount - The amount of the source currency to swap, 115 | * if `fromAmount` is 0, the `toAmount` of the `DetailedAMMRoute` will be 0. 116 | * @returns {Promise} - A promise that resolves to an array of DetailedAMMRoute objects, 117 | * representing all possible swap routes between the two specified currencies with additional details. 118 | */ 119 | async getAllPossibleRoutesWithDetails( 120 | from: Currency, 121 | to: Currency, 122 | fromAmount: bigint 123 | ): Promise { 124 | const routes = await this.getAllPossibleRoutes(from, to); 125 | const detailedRoutes = await Promise.all( 126 | routes.map((r) => this.getDetailedRoute(r, fromAmount)) 127 | ); 128 | return detailedRoutes.filter(isNotNull); 129 | } 130 | 131 | /** 132 | * This function returns a detailed route for a given AMMRoute. 133 | * 134 | * @param {AMMRoute} route - The AMM route to get details for. 135 | * @param {bigint} fromAmount - The amount of the source currency to swap, 136 | * if `fromAmount` is 0, the `toAmount` of the `DetailedAMMRoute` will be 0. 137 | * @returns {Promise} - A promise that resolves to a DetailedAMMRoute object 138 | * if the route details can be fetched, or undefined if any token information is missing. 139 | * 140 | * The function processes each segment of the route, fetching token information and constructing 141 | * a detailed representation of the swap path. It includes information such as the from and to 142 | * currencies, token addresses, and pool details for each step in the route. 143 | */ 144 | async getDetailedRoute( 145 | route: AMMRoute, 146 | fromAmount: bigint 147 | ): Promise { 148 | return getDetailedRoute( 149 | await props({ 150 | ammPools: this.getPools(), 151 | getContractId: this.getContractId(), 152 | tokenInfoMappings: this.getTokenMappings(), 153 | }), 154 | route, 155 | fromAmount 156 | ); 157 | } 158 | 159 | /** 160 | * Get the router path for swapping between two currencies. 161 | * 162 | * @param {Currency} from - The currency to swap from. 163 | * @param {Currency} to - The currency to swap to. 164 | * @returns {Promise} - A promise that resolves to an AMMRoute object, representing the best route for the swap. 165 | */ 166 | async getRoute(from: Currency, to: Currency): Promise { 167 | const allPossibleRoutes = await this.getAllPossibleRoutes(from, to); 168 | if (allPossibleRoutes.length === 0) { 169 | throw new Error("Can't find route"); 170 | } 171 | return allPossibleRoutes[0]; 172 | } 173 | 174 | /** 175 | * This function takes an AMMRoute and returns an array of TokenInfo objects representing 176 | * the tokens involved in each step of the route, including the origin token. 177 | * 178 | * @param {AMMRoute} route - The route to display. 179 | * @returns {Promise} - A promise that resolves to an array of TokenInfo objects, 180 | * representing the detailed information of the route. 181 | */ 182 | async getWayPoints(route: AMMRoute): Promise { 183 | const { neighbour, pool } = route[0]; 184 | const origin = neighbour === pool.tokenY ? pool.tokenX : pool.tokenY; 185 | const tokenMappings = await this.getTokenMappings(); 186 | return [origin, ...route.map((x) => x.neighbour)].map( 187 | (x) => tokenMappings[x] 188 | ); 189 | } 190 | 191 | /** 192 | * Get the swap fee (liquidity provider fee) between two currencies. 193 | * 194 | * @param {Currency} from - The currency to swap from. 195 | * @param {Currency} to - The currency to swap to. 196 | * @param {AMMRoute} [customRoute] - An optional custom route for the swap. 197 | * @returns {Promise} - A promise that resolves to a bigint representing the fee rate for the swap. 198 | */ 199 | async getFeeRate( 200 | from: Currency, 201 | to: Currency, 202 | customRoute?: AMMRoute 203 | ): Promise { 204 | return getLiquidityProviderFee( 205 | from, 206 | to, 207 | await this.getPools(), 208 | await this.getContractId(), 209 | customRoute 210 | ); 211 | } 212 | 213 | /** 214 | * Get the amount of destination currency that will be received when swapping from one currency to another. 215 | * 216 | * @param {Currency} from - The currency to swap from. 217 | * @param {bigint} fromAmount - The amount of the source currency to swap. 218 | * @param {Currency} to - The currency to swap to. 219 | * @param {AMMRoute} [customRoute] - An optional custom route for the swap. 220 | * @returns {Promise} - A promise that resolves to a bigint representing the amount of the destination currency that will be received. 221 | */ 222 | async getAmountTo( 223 | from: Currency, 224 | fromAmount: bigint, 225 | to: Currency, 226 | customRoute?: AMMRoute 227 | ): Promise { 228 | return getYAmountFromXAmount( 229 | from, 230 | to, 231 | fromAmount, 232 | await this.getPools(), 233 | await this.getContractId(), 234 | customRoute 235 | ); 236 | } 237 | 238 | /** 239 | * Check if the sponsor service is available. 240 | * 241 | * @returns {Promise} - A promise that resolves to true if the sponsor service is available, false otherwise. 242 | */ 243 | async isSponsoredTxServiceAvailable(): Promise { 244 | const { status } = await getSponsorData(); 245 | return status === 'ok'; 246 | } 247 | 248 | /** 249 | * Get the amount of destination currency that will be received when swapping from one currency to another 250 | * in the context of sponsor tx. 251 | * 252 | * @param {Currency} from - The currency to swap from. 253 | * @param {bigint} fromAmount - The amount of the source currency to swap. 254 | * @param {Currency} to - The currency to swap to. 255 | * @param {AMMRoute} [customRoute] - An optional custom route for the swap. 256 | * @returns {Promise} - A promise that resolves to a bigint representing the amount of the destination currency that will be received. 257 | */ 258 | async getAmountToForSponsoredTx( 259 | from: Currency, 260 | fromAmount: bigint, 261 | to: Currency, 262 | customRoute?: AMMRoute 263 | ): Promise { 264 | const route = customRoute ?? (await this.getRoute(from, to)); 265 | const stxAmount = await requiredStxAmountForSponsorTx(from, to, route); 266 | const sponsorFeeAmount = 267 | from === Currency.STX 268 | ? stxAmount 269 | : await this.getAmountTo(Currency.STX, BigInt(1e8), from).then( 270 | (x) => (x * stxAmount) / BigInt(1e8) 271 | ); 272 | if (sponsorFeeAmount > fromAmount) { 273 | return BigInt(0); 274 | } 275 | return getYAmountFromXAmount( 276 | from, 277 | to, 278 | fromAmount - sponsorFeeAmount, 279 | await this.getPools(), 280 | await this.getContractId(), 281 | customRoute 282 | ); 283 | } 284 | 285 | /** 286 | * Perform a swap between two currencies using the specified route and amount. 287 | * 288 | * @param {string} stxAddress - The Stacks (STX) address to execute the swap from. 289 | * @param {Currency} currencyX - The currency to swap from. 290 | * @param {Currency} currencyY - The currency to swap to. 291 | * @param {bigint} fromAmount - The amount of the source currency to swap. 292 | * @param {bigint} minDy - The minimum amount of the destination currency to receive. 293 | * @param {AMMRoute} [customRoute] - An optional custom route for the swap. 294 | * @returns {Promise} - A promise that resolves to a TxToBroadCast object, representing the transaction to be broadcasted. 295 | */ 296 | async runSwap( 297 | stxAddress: string, 298 | currencyX: Currency, 299 | currencyY: Currency, 300 | fromAmount: bigint, 301 | minDy: bigint, 302 | customRoute?: AMMRoute 303 | ): Promise { 304 | return runSpot( 305 | stxAddress, 306 | currencyX, 307 | currencyY, 308 | fromAmount, 309 | minDy, 310 | await this.getPools(), 311 | await this.getTokenInfos(), 312 | customRoute 313 | ); 314 | } 315 | 316 | /** 317 | * Perform a swap between two currencies using the specified route and amount. 318 | * Targetting sponsor tx. 319 | * 320 | * @param {string} stxAddress - The Stacks (STX) address to execute the swap from. 321 | * @param {Currency} currencyX - The currency to swap from. 322 | * @param {Currency} currencyY - The currency to swap to. 323 | * @param {bigint} fromAmount - The amount of the source currency to swap. 324 | * @param {bigint} minDy - The minimum amount of the destination currency to receive. 325 | * @param {AMMRoute} [customRoute] - An optional custom route for the swap. 326 | * @returns {Promise} - A promise that resolves to a TxToBroadCast object, representing the transaction to be broadcasted. 327 | */ 328 | async runSwapForSponsoredTx( 329 | stxAddress: string, 330 | currencyX: Currency, 331 | currencyY: Currency, 332 | fromAmount: bigint, 333 | minDy: bigint, 334 | customRoute?: AMMRoute 335 | ): Promise { 336 | const route = customRoute ?? (await this.getRoute(currencyX, currencyY)); 337 | const stxAmount = await requiredStxAmountForSponsorTx( 338 | currencyX, 339 | currencyY, 340 | route 341 | ); 342 | const sponsorFeeAmount = 343 | currencyX === Currency.STX 344 | ? stxAmount 345 | : await this.getAmountTo(Currency.STX, BigInt(1e8), currencyX).then( 346 | (x) => (x * stxAmount) / BigInt(1e8) 347 | ); 348 | if (sponsorFeeAmount > fromAmount) { 349 | throw new SponsoredTxError( 350 | SponsoredTxErrorCode.insufficient_funds, 351 | 'Insufficient funds to cover sponsor fee' 352 | ); 353 | } 354 | return runSponsoredSpotTx( 355 | stxAddress, 356 | currencyX, 357 | currencyY, 358 | fromAmount, 359 | minDy, 360 | sponsorFeeAmount, 361 | await this.getPools(), 362 | await this.getTokenInfos(), 363 | customRoute 364 | ); 365 | } 366 | 367 | /** 368 | * Broadcast a sponsored transaction. 369 | * 370 | * @param {string} tx - The signed sponsor transaction to be broadcast. 371 | * @returns {Promise} - A promise that resolves to the transaction ID. 372 | */ 373 | async broadcastSponsoredTx(tx: string): Promise { 374 | return broadcastSponsoredTx(tx); 375 | } 376 | 377 | /** 378 | * This function fetches the current price data for all supported tokens. It returns an object where 379 | * the keys are the currency identifiers (as defined in the Currency enum) and the values are the corresponding prices in USD. 380 | * 381 | * @returns {Promise>} - A promise that resolves to an object containing the latest prices for each currency. 382 | */ 383 | async getLatestPrices(): Promise< 384 | Partial<{ 385 | [currency in Currency]: number; 386 | }> 387 | > { 388 | const priceData = await this.getPrices(); 389 | return fromEntries(priceData.map((x) => [x.token, x.price])); 390 | } 391 | 392 | /** 393 | * This function fetches the current balances of all supported tokens for a specified STX address. 394 | * It returns an object where the keys are the currency identifiers (as defined in the Currency enum) 395 | * and the values are the corresponding balances as bigint values. 396 | * 397 | * @param {string} stxAddress - The Stacks (STX) address to retrieve the balances for. 398 | * @returns {Promise>} - A promise that resolves to an object containing the balances of each currency for the given address. 399 | */ 400 | async getBalances( 401 | stxAddress: string 402 | ): Promise> { 403 | return fetchBalanceForAccount(stxAddress, await this.getTokenInfos()); 404 | } 405 | 406 | // @deprecated use getRoute + displayRoute instead 407 | async getRouter(from: Currency, to: Currency): Promise { 408 | const route = await this.getRoute(from, to); 409 | return [from, ...route.map((x) => x.neighbour)]; 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | export const configs = { 2 | CONTRACT_DEPLOYER: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM', 3 | QUOTE_CONTRACT_DEPLOYER: 'SP3MZM9WJ34Y4311XBJDBKQ41SXX5DY68424HSED2', 4 | SPONSOR_TX_DEPLOYER: 'SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER', 5 | SDK_API_HOST: 'https://alex-sdk-api.alexlab.co', 6 | BACKEND_API_HOST: 'https://api.alexgo.io', 7 | STACKS_API_HOST: 'https://api.hiro.so', 8 | READONLY_CALL_API_HOST: 'https://stacks-node.alexlab.co', 9 | SPONSORED_TX_EXECUTOR: 'https://api.stxer.xyz/sponsor/execute', 10 | SPONSORED_TX_STATUS: 'https://api.stxer.xyz/sponsor/status', 11 | }; 12 | -------------------------------------------------------------------------------- /src/currency.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This type is a string used to represent different tokens in the AlexSDK. 3 | * It use a unique symbol to distinguish it from regular strings and refers to specific currencies such as `STX`, `ALEX`, and others. 4 | */ 5 | export type Currency = string & { 6 | readonly brand: unique symbol; 7 | }; 8 | 9 | /** The `Currency` namespace contains predefined constants for tokens in the AlexSDK.*/ 10 | // eslint-disable-next-line @typescript-eslint/no-redeclare 11 | export namespace Currency { 12 | /** Represents the `STX` token */ 13 | export const STX = createCurrency('token-wstx'); 14 | /** Represents the `ALEX` token*/ 15 | export const ALEX = createCurrency('age000-governance-token'); 16 | } 17 | 18 | function createCurrency(value: string): Currency { 19 | return value as Currency; 20 | } 21 | -------------------------------------------------------------------------------- /src/generated/smartContract/contract_Alex_alex-amm-pool-v2-01-get-helper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineContract, 3 | principalT, 4 | uintT, 5 | responseSimpleT, 6 | } from '../smartContractHelpers/codegenImport'; 7 | 8 | export const alexAmmPoolV201GetHelper = defineContract({ 9 | 'alex-amm-pool-v2-01-get-helper': { 10 | 'get-helper-with-fee': { 11 | input: [ 12 | { name: 'token-x', type: principalT }, 13 | { name: 'token-y', type: principalT }, 14 | { name: 'factor', type: uintT }, 15 | { name: 'dx', type: uintT }, 16 | ], 17 | output: responseSimpleT(uintT), 18 | mode: 'readonly', 19 | }, 20 | 'get-helper-with-fee-a': { 21 | input: [ 22 | { name: 'token-x', type: principalT }, 23 | { name: 'token-y', type: principalT }, 24 | { name: 'token-z', type: principalT }, 25 | { name: 'factor-x', type: uintT }, 26 | { name: 'factor-y', type: uintT }, 27 | { name: 'dx', type: uintT }, 28 | ], 29 | output: responseSimpleT(uintT), 30 | mode: 'readonly', 31 | }, 32 | 'get-helper-with-fee-b': { 33 | input: [ 34 | { name: 'token-x', type: principalT }, 35 | { name: 'token-y', type: principalT }, 36 | { name: 'token-z', type: principalT }, 37 | { name: 'token-w', type: principalT }, 38 | { name: 'factor-x', type: uintT }, 39 | { name: 'factor-y', type: uintT }, 40 | { name: 'factor-z', type: uintT }, 41 | { name: 'dx', type: uintT }, 42 | ], 43 | output: responseSimpleT(uintT), 44 | mode: 'readonly', 45 | }, 46 | 'get-helper-with-fee-c': { 47 | input: [ 48 | { name: 'token-x', type: principalT }, 49 | { name: 'token-y', type: principalT }, 50 | { name: 'token-z', type: principalT }, 51 | { name: 'token-w', type: principalT }, 52 | { name: 'token-v', type: principalT }, 53 | { name: 'factor-x', type: uintT }, 54 | { name: 'factor-y', type: uintT }, 55 | { name: 'factor-z', type: uintT }, 56 | { name: 'factor-w', type: uintT }, 57 | { name: 'dx', type: uintT }, 58 | ], 59 | output: responseSimpleT(uintT), 60 | mode: 'readonly', 61 | }, 62 | }, 63 | } as const); 64 | -------------------------------------------------------------------------------- /src/generated/smartContract/contract_Alex_amm-pool-v2-01.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineContract, 3 | traitT, 4 | uintT, 5 | optionalT, 6 | responseSimpleT, 7 | tupleT, 8 | principalT, 9 | booleanT, 10 | noneT, 11 | } from '../smartContractHelpers/codegenImport'; 12 | 13 | export const ammPoolV201 = defineContract({ 14 | 'amm-pool-v2-01': { 15 | 'add-to-position': { 16 | input: [ 17 | { name: 'token-x-trait', type: traitT }, 18 | { name: 'token-y-trait', type: traitT }, 19 | { name: 'factor', type: uintT }, 20 | { name: 'dx', type: uintT }, 21 | { name: 'max-dy', type: optionalT(uintT) }, 22 | ], 23 | output: responseSimpleT(tupleT({ dx: uintT, dy: uintT, supply: uintT })), 24 | mode: 'public', 25 | }, 26 | 'create-pool': { 27 | input: [ 28 | { name: 'token-x-trait', type: traitT }, 29 | { name: 'token-y-trait', type: traitT }, 30 | { name: 'factor', type: uintT }, 31 | { name: 'pool-owner', type: principalT }, 32 | { name: 'dx', type: uintT }, 33 | { name: 'dy', type: uintT }, 34 | ], 35 | output: responseSimpleT(tupleT({ dx: uintT, dy: uintT, supply: uintT })), 36 | mode: 'public', 37 | }, 38 | pause: { 39 | input: [{ name: 'new-paused', type: booleanT }], 40 | output: responseSimpleT(booleanT), 41 | mode: 'public', 42 | }, 43 | 'reduce-position': { 44 | input: [ 45 | { name: 'token-x-trait', type: traitT }, 46 | { name: 'token-y-trait', type: traitT }, 47 | { name: 'factor', type: uintT }, 48 | { name: 'percent', type: uintT }, 49 | ], 50 | output: responseSimpleT(tupleT({ dx: uintT, dy: uintT })), 51 | mode: 'public', 52 | }, 53 | 'set-end-block': { 54 | input: [ 55 | { name: 'token-x', type: principalT }, 56 | { name: 'token-y', type: principalT }, 57 | { name: 'factor', type: uintT }, 58 | { name: 'new-end-block', type: uintT }, 59 | ], 60 | output: responseSimpleT(booleanT), 61 | mode: 'public', 62 | }, 63 | 'set-fee-rate-x': { 64 | input: [ 65 | { name: 'token-x', type: principalT }, 66 | { name: 'token-y', type: principalT }, 67 | { name: 'factor', type: uintT }, 68 | { name: 'fee-rate-x', type: uintT }, 69 | ], 70 | output: responseSimpleT(booleanT), 71 | mode: 'public', 72 | }, 73 | 'set-fee-rate-y': { 74 | input: [ 75 | { name: 'token-x', type: principalT }, 76 | { name: 'token-y', type: principalT }, 77 | { name: 'factor', type: uintT }, 78 | { name: 'fee-rate-y', type: uintT }, 79 | ], 80 | output: responseSimpleT(booleanT), 81 | mode: 'public', 82 | }, 83 | 'set-max-in-ratio': { 84 | input: [ 85 | { name: 'token-x', type: principalT }, 86 | { name: 'token-y', type: principalT }, 87 | { name: 'factor', type: uintT }, 88 | { name: 'new-max-in-ratio', type: uintT }, 89 | ], 90 | output: responseSimpleT(booleanT), 91 | mode: 'public', 92 | }, 93 | 'set-max-out-ratio': { 94 | input: [ 95 | { name: 'token-x', type: principalT }, 96 | { name: 'token-y', type: principalT }, 97 | { name: 'factor', type: uintT }, 98 | { name: 'new-max-out-ratio', type: uintT }, 99 | ], 100 | output: responseSimpleT(booleanT), 101 | mode: 'public', 102 | }, 103 | 'set-oracle-average': { 104 | input: [ 105 | { name: 'token-x', type: principalT }, 106 | { name: 'token-y', type: principalT }, 107 | { name: 'factor', type: uintT }, 108 | { name: 'new-oracle-average', type: uintT }, 109 | ], 110 | output: responseSimpleT(booleanT), 111 | mode: 'public', 112 | }, 113 | 'set-oracle-enabled': { 114 | input: [ 115 | { name: 'token-x', type: principalT }, 116 | { name: 'token-y', type: principalT }, 117 | { name: 'factor', type: uintT }, 118 | { name: 'enabled', type: booleanT }, 119 | ], 120 | output: responseSimpleT(booleanT), 121 | mode: 'public', 122 | }, 123 | 'set-start-block': { 124 | input: [ 125 | { name: 'token-x', type: principalT }, 126 | { name: 'token-y', type: principalT }, 127 | { name: 'factor', type: uintT }, 128 | { name: 'new-start-block', type: uintT }, 129 | ], 130 | output: responseSimpleT(booleanT), 131 | mode: 'public', 132 | }, 133 | 'set-threshold-x': { 134 | input: [ 135 | { name: 'token-x', type: principalT }, 136 | { name: 'token-y', type: principalT }, 137 | { name: 'factor', type: uintT }, 138 | { name: 'new-threshold', type: uintT }, 139 | ], 140 | output: responseSimpleT(booleanT), 141 | mode: 'public', 142 | }, 143 | 'set-threshold-y': { 144 | input: [ 145 | { name: 'token-x', type: principalT }, 146 | { name: 'token-y', type: principalT }, 147 | { name: 'factor', type: uintT }, 148 | { name: 'new-threshold', type: uintT }, 149 | ], 150 | output: responseSimpleT(booleanT), 151 | mode: 'public', 152 | }, 153 | 'swap-helper': { 154 | input: [ 155 | { name: 'token-x-trait', type: traitT }, 156 | { name: 'token-y-trait', type: traitT }, 157 | { name: 'factor', type: uintT }, 158 | { name: 'dx', type: uintT }, 159 | { name: 'min-dy', type: optionalT(uintT) }, 160 | ], 161 | output: responseSimpleT(uintT), 162 | mode: 'public', 163 | }, 164 | 'swap-helper-a': { 165 | input: [ 166 | { name: 'token-x-trait', type: traitT }, 167 | { name: 'token-y-trait', type: traitT }, 168 | { name: 'token-z-trait', type: traitT }, 169 | { name: 'factor-x', type: uintT }, 170 | { name: 'factor-y', type: uintT }, 171 | { name: 'dx', type: uintT }, 172 | { name: 'min-dz', type: optionalT(uintT) }, 173 | ], 174 | output: responseSimpleT(uintT), 175 | mode: 'public', 176 | }, 177 | 'swap-helper-b': { 178 | input: [ 179 | { name: 'token-x-trait', type: traitT }, 180 | { name: 'token-y-trait', type: traitT }, 181 | { name: 'token-z-trait', type: traitT }, 182 | { name: 'token-w-trait', type: traitT }, 183 | { name: 'factor-x', type: uintT }, 184 | { name: 'factor-y', type: uintT }, 185 | { name: 'factor-z', type: uintT }, 186 | { name: 'dx', type: uintT }, 187 | { name: 'min-dw', type: optionalT(uintT) }, 188 | ], 189 | output: responseSimpleT(uintT), 190 | mode: 'public', 191 | }, 192 | 'swap-helper-c': { 193 | input: [ 194 | { name: 'token-x-trait', type: traitT }, 195 | { name: 'token-y-trait', type: traitT }, 196 | { name: 'token-z-trait', type: traitT }, 197 | { name: 'token-w-trait', type: traitT }, 198 | { name: 'token-v-trait', type: traitT }, 199 | { name: 'factor-x', type: uintT }, 200 | { name: 'factor-y', type: uintT }, 201 | { name: 'factor-z', type: uintT }, 202 | { name: 'factor-w', type: uintT }, 203 | { name: 'dx', type: uintT }, 204 | { name: 'min-dv', type: optionalT(uintT) }, 205 | ], 206 | output: responseSimpleT(uintT), 207 | mode: 'public', 208 | }, 209 | 'swap-x-for-y': { 210 | input: [ 211 | { name: 'token-x-trait', type: traitT }, 212 | { name: 'token-y-trait', type: traitT }, 213 | { name: 'factor', type: uintT }, 214 | { name: 'dx', type: uintT }, 215 | { name: 'min-dy', type: optionalT(uintT) }, 216 | ], 217 | output: responseSimpleT(tupleT({ dx: uintT, dy: uintT })), 218 | mode: 'public', 219 | }, 220 | 'swap-y-for-x': { 221 | input: [ 222 | { name: 'token-x-trait', type: traitT }, 223 | { name: 'token-y-trait', type: traitT }, 224 | { name: 'factor', type: uintT }, 225 | { name: 'dy', type: uintT }, 226 | { name: 'min-dx', type: optionalT(uintT) }, 227 | ], 228 | output: responseSimpleT(tupleT({ dx: uintT, dy: uintT })), 229 | mode: 'public', 230 | }, 231 | 'check-pool-status': { 232 | input: [ 233 | { name: 'token-x', type: principalT }, 234 | { name: 'token-y', type: principalT }, 235 | { name: 'factor', type: uintT }, 236 | ], 237 | output: responseSimpleT(booleanT), 238 | mode: 'readonly', 239 | }, 240 | 'fee-helper': { 241 | input: [ 242 | { name: 'token-x', type: principalT }, 243 | { name: 'token-y', type: principalT }, 244 | { name: 'factor', type: uintT }, 245 | ], 246 | output: responseSimpleT(uintT), 247 | mode: 'readonly', 248 | }, 249 | 'fee-helper-a': { 250 | input: [ 251 | { name: 'token-x', type: principalT }, 252 | { name: 'token-y', type: principalT }, 253 | { name: 'token-z', type: principalT }, 254 | { name: 'factor-x', type: uintT }, 255 | { name: 'factor-y', type: uintT }, 256 | ], 257 | output: responseSimpleT(uintT), 258 | mode: 'readonly', 259 | }, 260 | 'fee-helper-b': { 261 | input: [ 262 | { name: 'token-x', type: principalT }, 263 | { name: 'token-y', type: principalT }, 264 | { name: 'token-z', type: principalT }, 265 | { name: 'token-w', type: principalT }, 266 | { name: 'factor-x', type: uintT }, 267 | { name: 'factor-y', type: uintT }, 268 | { name: 'factor-z', type: uintT }, 269 | ], 270 | output: responseSimpleT(uintT), 271 | mode: 'readonly', 272 | }, 273 | 'fee-helper-c': { 274 | input: [ 275 | { name: 'token-x', type: principalT }, 276 | { name: 'token-y', type: principalT }, 277 | { name: 'token-z', type: principalT }, 278 | { name: 'token-w', type: principalT }, 279 | { name: 'token-v', type: principalT }, 280 | { name: 'factor-x', type: uintT }, 281 | { name: 'factor-y', type: uintT }, 282 | { name: 'factor-z', type: uintT }, 283 | { name: 'factor-w', type: uintT }, 284 | ], 285 | output: responseSimpleT(uintT), 286 | mode: 'readonly', 287 | }, 288 | 'get-balances': { 289 | input: [ 290 | { name: 'token-x', type: principalT }, 291 | { name: 'token-y', type: principalT }, 292 | { name: 'factor', type: uintT }, 293 | ], 294 | output: responseSimpleT( 295 | tupleT({ 'balance-x': uintT, 'balance-y': uintT }) 296 | ), 297 | mode: 'readonly', 298 | }, 299 | 'get-end-block': { 300 | input: [ 301 | { name: 'token-x', type: principalT }, 302 | { name: 'token-y', type: principalT }, 303 | { name: 'factor', type: uintT }, 304 | ], 305 | output: responseSimpleT(uintT), 306 | mode: 'readonly', 307 | }, 308 | 'get-fee-rate-x': { 309 | input: [ 310 | { name: 'token-x', type: principalT }, 311 | { name: 'token-y', type: principalT }, 312 | { name: 'factor', type: uintT }, 313 | ], 314 | output: responseSimpleT(uintT), 315 | mode: 'readonly', 316 | }, 317 | 'get-fee-rate-y': { 318 | input: [ 319 | { name: 'token-x', type: principalT }, 320 | { name: 'token-y', type: principalT }, 321 | { name: 'factor', type: uintT }, 322 | ], 323 | output: responseSimpleT(uintT), 324 | mode: 'readonly', 325 | }, 326 | 'get-fee-rebate': { 327 | input: [ 328 | { name: 'token-x', type: principalT }, 329 | { name: 'token-y', type: principalT }, 330 | { name: 'factor', type: uintT }, 331 | ], 332 | output: responseSimpleT(uintT), 333 | mode: 'readonly', 334 | }, 335 | 'get-helper': { 336 | input: [ 337 | { name: 'token-x', type: principalT }, 338 | { name: 'token-y', type: principalT }, 339 | { name: 'factor', type: uintT }, 340 | { name: 'dx', type: uintT }, 341 | ], 342 | output: responseSimpleT(uintT), 343 | mode: 'readonly', 344 | }, 345 | 'get-helper-a': { 346 | input: [ 347 | { name: 'token-x', type: principalT }, 348 | { name: 'token-y', type: principalT }, 349 | { name: 'token-z', type: principalT }, 350 | { name: 'factor-x', type: uintT }, 351 | { name: 'factor-y', type: uintT }, 352 | { name: 'dx', type: uintT }, 353 | ], 354 | output: responseSimpleT(uintT), 355 | mode: 'readonly', 356 | }, 357 | 'get-helper-b': { 358 | input: [ 359 | { name: 'token-x', type: principalT }, 360 | { name: 'token-y', type: principalT }, 361 | { name: 'token-z', type: principalT }, 362 | { name: 'token-w', type: principalT }, 363 | { name: 'factor-x', type: uintT }, 364 | { name: 'factor-y', type: uintT }, 365 | { name: 'factor-z', type: uintT }, 366 | { name: 'dx', type: uintT }, 367 | ], 368 | output: responseSimpleT(uintT), 369 | mode: 'readonly', 370 | }, 371 | 'get-helper-c': { 372 | input: [ 373 | { name: 'token-x', type: principalT }, 374 | { name: 'token-y', type: principalT }, 375 | { name: 'token-z', type: principalT }, 376 | { name: 'token-w', type: principalT }, 377 | { name: 'token-v', type: principalT }, 378 | { name: 'factor-x', type: uintT }, 379 | { name: 'factor-y', type: uintT }, 380 | { name: 'factor-z', type: uintT }, 381 | { name: 'factor-w', type: uintT }, 382 | { name: 'dx', type: uintT }, 383 | ], 384 | output: responseSimpleT(uintT), 385 | mode: 'readonly', 386 | }, 387 | 'get-invariant': { 388 | input: [ 389 | { name: 'balance-x', type: uintT }, 390 | { name: 'balance-y', type: uintT }, 391 | { name: 't', type: uintT }, 392 | ], 393 | output: uintT, 394 | mode: 'readonly', 395 | }, 396 | 'get-max-in-ratio': { 397 | input: [ 398 | { name: 'token-x', type: principalT }, 399 | { name: 'token-y', type: principalT }, 400 | { name: 'factor', type: uintT }, 401 | ], 402 | output: responseSimpleT(uintT), 403 | mode: 'readonly', 404 | }, 405 | 'get-max-out-ratio': { 406 | input: [ 407 | { name: 'token-x', type: principalT }, 408 | { name: 'token-y', type: principalT }, 409 | { name: 'factor', type: uintT }, 410 | ], 411 | output: responseSimpleT(uintT), 412 | mode: 'readonly', 413 | }, 414 | 'get-max-ratio-limit': { input: [], output: uintT, mode: 'readonly' }, 415 | 'get-oracle-average': { 416 | input: [ 417 | { name: 'token-x', type: principalT }, 418 | { name: 'token-y', type: principalT }, 419 | { name: 'factor', type: uintT }, 420 | ], 421 | output: responseSimpleT(uintT), 422 | mode: 'readonly', 423 | }, 424 | 'get-oracle-enabled': { 425 | input: [ 426 | { name: 'token-x', type: principalT }, 427 | { name: 'token-y', type: principalT }, 428 | { name: 'factor', type: uintT }, 429 | ], 430 | output: responseSimpleT(booleanT), 431 | mode: 'readonly', 432 | }, 433 | 'get-oracle-instant': { 434 | input: [ 435 | { name: 'token-x', type: principalT }, 436 | { name: 'token-y', type: principalT }, 437 | { name: 'factor', type: uintT }, 438 | ], 439 | output: responseSimpleT(uintT), 440 | mode: 'readonly', 441 | }, 442 | 'get-oracle-resilient': { 443 | input: [ 444 | { name: 'token-x', type: principalT }, 445 | { name: 'token-y', type: principalT }, 446 | { name: 'factor', type: uintT }, 447 | ], 448 | output: responseSimpleT(uintT), 449 | mode: 'readonly', 450 | }, 451 | 'get-pool-details': { 452 | input: [ 453 | { name: 'token-x', type: principalT }, 454 | { name: 'token-y', type: principalT }, 455 | { name: 'factor', type: uintT }, 456 | ], 457 | output: responseSimpleT( 458 | tupleT({ 459 | 'balance-x': uintT, 460 | 'balance-y': uintT, 461 | 'end-block': uintT, 462 | 'fee-rate-x': uintT, 463 | 'fee-rate-y': uintT, 464 | 'fee-rebate': uintT, 465 | 'max-in-ratio': uintT, 466 | 'max-out-ratio': uintT, 467 | 'oracle-average': uintT, 468 | 'oracle-enabled': booleanT, 469 | 'oracle-resilient': uintT, 470 | 'pool-id': uintT, 471 | 'pool-owner': principalT, 472 | 'start-block': uintT, 473 | 'threshold-x': uintT, 474 | 'threshold-y': uintT, 475 | 'total-supply': uintT, 476 | }) 477 | ), 478 | mode: 'readonly', 479 | }, 480 | 'get-pool-details-by-id': { 481 | input: [{ name: 'pool-id', type: uintT }], 482 | output: responseSimpleT( 483 | tupleT({ factor: uintT, 'token-x': principalT, 'token-y': principalT }) 484 | ), 485 | mode: 'readonly', 486 | }, 487 | 'get-pool-exists': { 488 | input: [ 489 | { name: 'token-x', type: principalT }, 490 | { name: 'token-y', type: principalT }, 491 | { name: 'factor', type: uintT }, 492 | ], 493 | output: optionalT( 494 | tupleT({ 495 | 'balance-x': uintT, 496 | 'balance-y': uintT, 497 | 'end-block': uintT, 498 | 'fee-rate-x': uintT, 499 | 'fee-rate-y': uintT, 500 | 'fee-rebate': uintT, 501 | 'max-in-ratio': uintT, 502 | 'max-out-ratio': uintT, 503 | 'oracle-average': uintT, 504 | 'oracle-enabled': booleanT, 505 | 'oracle-resilient': uintT, 506 | 'pool-id': uintT, 507 | 'pool-owner': principalT, 508 | 'start-block': uintT, 509 | 'threshold-x': uintT, 510 | 'threshold-y': uintT, 511 | 'total-supply': uintT, 512 | }) 513 | ), 514 | mode: 'readonly', 515 | }, 516 | 'get-pool-owner': { 517 | input: [ 518 | { name: 'token-x', type: principalT }, 519 | { name: 'token-y', type: principalT }, 520 | { name: 'factor', type: uintT }, 521 | ], 522 | output: responseSimpleT(principalT), 523 | mode: 'readonly', 524 | }, 525 | 'get-position-given-burn': { 526 | input: [ 527 | { name: 'token-x', type: principalT }, 528 | { name: 'token-y', type: principalT }, 529 | { name: 'factor', type: uintT }, 530 | { name: 'token-amount', type: uintT }, 531 | ], 532 | output: responseSimpleT(tupleT({ dx: uintT, dy: uintT })), 533 | mode: 'readonly', 534 | }, 535 | 'get-position-given-mint': { 536 | input: [ 537 | { name: 'token-x', type: principalT }, 538 | { name: 'token-y', type: principalT }, 539 | { name: 'factor', type: uintT }, 540 | { name: 'token-amount', type: uintT }, 541 | ], 542 | output: responseSimpleT(tupleT({ dx: uintT, dy: uintT })), 543 | mode: 'readonly', 544 | }, 545 | 'get-price': { 546 | input: [ 547 | { name: 'token-x', type: principalT }, 548 | { name: 'token-y', type: principalT }, 549 | { name: 'factor', type: uintT }, 550 | ], 551 | output: responseSimpleT(uintT), 552 | mode: 'readonly', 553 | }, 554 | 'get-start-block': { 555 | input: [ 556 | { name: 'token-x', type: principalT }, 557 | { name: 'token-y', type: principalT }, 558 | { name: 'factor', type: uintT }, 559 | ], 560 | output: responseSimpleT(uintT), 561 | mode: 'readonly', 562 | }, 563 | 'get-switch-threshold': { input: [], output: uintT, mode: 'readonly' }, 564 | 'get-threshold-x': { 565 | input: [ 566 | { name: 'token-x', type: principalT }, 567 | { name: 'token-y', type: principalT }, 568 | { name: 'factor', type: uintT }, 569 | ], 570 | output: responseSimpleT(uintT), 571 | mode: 'readonly', 572 | }, 573 | 'get-threshold-y': { 574 | input: [ 575 | { name: 'token-x', type: principalT }, 576 | { name: 'token-y', type: principalT }, 577 | { name: 'factor', type: uintT }, 578 | ], 579 | output: responseSimpleT(uintT), 580 | mode: 'readonly', 581 | }, 582 | 'get-token-given-position': { 583 | input: [ 584 | { name: 'token-x', type: principalT }, 585 | { name: 'token-y', type: principalT }, 586 | { name: 'factor', type: uintT }, 587 | { name: 'dx', type: uintT }, 588 | { name: 'max-dy', type: optionalT(uintT) }, 589 | ], 590 | output: responseSimpleT(tupleT({ dy: uintT, token: uintT })), 591 | mode: 'readonly', 592 | }, 593 | 'get-x-given-price': { 594 | input: [ 595 | { name: 'token-x', type: principalT }, 596 | { name: 'token-y', type: principalT }, 597 | { name: 'factor', type: uintT }, 598 | { name: 'price', type: uintT }, 599 | ], 600 | output: responseSimpleT(uintT), 601 | mode: 'readonly', 602 | }, 603 | 'get-x-given-y': { 604 | input: [ 605 | { name: 'token-x', type: principalT }, 606 | { name: 'token-y', type: principalT }, 607 | { name: 'factor', type: uintT }, 608 | { name: 'dy', type: uintT }, 609 | ], 610 | output: responseSimpleT(uintT), 611 | mode: 'readonly', 612 | }, 613 | 'get-x-in-given-y-out': { 614 | input: [ 615 | { name: 'token-x', type: principalT }, 616 | { name: 'token-y', type: principalT }, 617 | { name: 'factor', type: uintT }, 618 | { name: 'dy', type: uintT }, 619 | ], 620 | output: responseSimpleT(uintT), 621 | mode: 'readonly', 622 | }, 623 | 'get-y-given-price': { 624 | input: [ 625 | { name: 'token-x', type: principalT }, 626 | { name: 'token-y', type: principalT }, 627 | { name: 'factor', type: uintT }, 628 | { name: 'price', type: uintT }, 629 | ], 630 | output: responseSimpleT(uintT), 631 | mode: 'readonly', 632 | }, 633 | 'get-y-given-x': { 634 | input: [ 635 | { name: 'token-x', type: principalT }, 636 | { name: 'token-y', type: principalT }, 637 | { name: 'factor', type: uintT }, 638 | { name: 'dx', type: uintT }, 639 | ], 640 | output: responseSimpleT(uintT), 641 | mode: 'readonly', 642 | }, 643 | 'get-y-in-given-x-out': { 644 | input: [ 645 | { name: 'token-x', type: principalT }, 646 | { name: 'token-y', type: principalT }, 647 | { name: 'factor', type: uintT }, 648 | { name: 'dx', type: uintT }, 649 | ], 650 | output: responseSimpleT(uintT), 651 | mode: 'readonly', 652 | }, 653 | 'is-blocklisted-or-default': { 654 | input: [{ name: 'sender', type: principalT }], 655 | output: booleanT, 656 | mode: 'readonly', 657 | }, 658 | 'is-dao-or-extension': { 659 | input: [], 660 | output: responseSimpleT(booleanT), 661 | mode: 'readonly', 662 | }, 663 | 'is-paused': { input: [], output: booleanT, mode: 'readonly' }, 664 | paused: { input: noneT, output: booleanT, mode: 'variable' }, 665 | }, 666 | } as const); 667 | -------------------------------------------------------------------------------- /src/generated/smartContract/contract_Alex_sponsor-dex-v01.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineContract, 3 | traitT, 4 | uintT, 5 | principalT, 6 | responseSimpleT, 7 | booleanT, 8 | optionalT, 9 | } from '../smartContractHelpers/codegenImport'; 10 | 11 | export const sponsorDexV01 = defineContract({ 12 | 'sponsor-dex-v01': { 13 | claim: { 14 | input: [ 15 | { name: 'token', type: traitT }, 16 | { name: 'amount', type: uintT }, 17 | { name: 'recipient', type: principalT }, 18 | ], 19 | output: responseSimpleT(booleanT), 20 | mode: 'public', 21 | }, 22 | 'swap-helper': { 23 | input: [ 24 | { name: 'token-x', type: traitT }, 25 | { name: 'token-y', type: traitT }, 26 | { name: 'factor', type: uintT }, 27 | { name: 'dx', type: uintT }, 28 | { name: 'min-dy', type: optionalT(uintT) }, 29 | { name: 'fee', type: uintT }, 30 | ], 31 | output: responseSimpleT(responseSimpleT(uintT)), 32 | mode: 'public', 33 | }, 34 | 'swap-helper-a': { 35 | input: [ 36 | { name: 'token-x', type: traitT }, 37 | { name: 'token-y', type: traitT }, 38 | { name: 'token-z', type: traitT }, 39 | { name: 'factor-x', type: uintT }, 40 | { name: 'factor-y', type: uintT }, 41 | { name: 'dx', type: uintT }, 42 | { name: 'min-dz', type: optionalT(uintT) }, 43 | { name: 'fee', type: uintT }, 44 | ], 45 | output: responseSimpleT(responseSimpleT(uintT)), 46 | mode: 'public', 47 | }, 48 | 'swap-helper-b': { 49 | input: [ 50 | { name: 'token-x', type: traitT }, 51 | { name: 'token-y', type: traitT }, 52 | { name: 'token-z', type: traitT }, 53 | { name: 'token-w', type: traitT }, 54 | { name: 'factor-x', type: uintT }, 55 | { name: 'factor-y', type: uintT }, 56 | { name: 'factor-z', type: uintT }, 57 | { name: 'dx', type: uintT }, 58 | { name: 'min-dw', type: optionalT(uintT) }, 59 | { name: 'fee', type: uintT }, 60 | ], 61 | output: responseSimpleT(responseSimpleT(uintT)), 62 | mode: 'public', 63 | }, 64 | 'swap-helper-c': { 65 | input: [ 66 | { name: 'token-x', type: traitT }, 67 | { name: 'token-y', type: traitT }, 68 | { name: 'token-z', type: traitT }, 69 | { name: 'token-w', type: traitT }, 70 | { name: 'token-v', type: traitT }, 71 | { name: 'factor-x', type: uintT }, 72 | { name: 'factor-y', type: uintT }, 73 | { name: 'factor-z', type: uintT }, 74 | { name: 'factor-w', type: uintT }, 75 | { name: 'dx', type: uintT }, 76 | { name: 'min-dv', type: optionalT(uintT) }, 77 | { name: 'fee', type: uintT }, 78 | ], 79 | output: responseSimpleT(responseSimpleT(uintT)), 80 | mode: 'public', 81 | }, 82 | 'is-dao-or-extension': { 83 | input: [], 84 | output: responseSimpleT(booleanT), 85 | mode: 'readonly', 86 | }, 87 | }, 88 | } as const); 89 | -------------------------------------------------------------------------------- /src/generated/smartContract/contracts_Alex.ts: -------------------------------------------------------------------------------- 1 | import { defineContract } from '../smartContractHelpers/codegenImport'; 2 | import { ammPoolV201 } from './contract_Alex_amm-pool-v2-01'; 3 | import { sponsorDexV01 } from './contract_Alex_sponsor-dex-v01'; 4 | import { alexAmmPoolV201GetHelper } from './contract_Alex_alex-amm-pool-v2-01-get-helper'; 5 | 6 | export const AlexContracts = defineContract({ 7 | ...ammPoolV201, 8 | ...sponsorDexV01, 9 | ...alexAmmPoolV201GetHelper, 10 | }); 11 | -------------------------------------------------------------------------------- /src/generated/smartContractHelpers/codegenImport.ts: -------------------------------------------------------------------------------- 1 | export * from 'clarity-codegen'; 2 | -------------------------------------------------------------------------------- /src/generated/smartContractHelpers/errorCodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "ERR-NOT-AUTHORIZED": [ 3 | { 4 | "code": 1000, 5 | "comment": "token-xbtc.clar:12" 6 | } 7 | ], 8 | "ERR-TRANSFER-FAILED": [ 9 | { 10 | "code": 3000, 11 | "comment": "token-apower.clar:5" 12 | } 13 | ], 14 | "ERR-NO-ARB-EXISTS": [ 15 | { 16 | "code": 9000, 17 | "comment": "flash-loan-user-autoalex-to-alex.clar:5" 18 | } 19 | ], 20 | "ERR-GET-BALANCE-FIXED-FAIL": [ 21 | { 22 | "code": 6001, 23 | "comment": "flash-loan-user-autoalex-to-alex.clar:6" 24 | } 25 | ], 26 | "ERR-EXCEEDS-MAX-USE": [ 27 | { 28 | "code": 9000, 29 | "comment": "faucet.clar:8" 30 | } 31 | ], 32 | "ERR-UNKNOWN-TOKEN": [ 33 | { 34 | "code": 1001, 35 | "comment": "faucet.clar:9" 36 | } 37 | ], 38 | "ERR-EXPIRY-IS-NONE": [ 39 | { 40 | "code": 2027, 41 | "comment": "flash-loan-user-margin-usda-wbtc.clar:5" 42 | } 43 | ], 44 | "ERR-INVALID-TOKEN": [ 45 | { 46 | "code": 2026, 47 | "comment": "flash-loan-user-margin-usda-wbtc.clar:6" 48 | } 49 | ], 50 | "ERR-TOO-MANY-POOLS": [ 51 | { 52 | "code": 2004, 53 | "comment": "staked-fwp-wstx-alex-50-50-v1-01.clar:6" 54 | } 55 | ], 56 | "ERR-INVALID-BALANCE": [ 57 | { 58 | "code": 2008, 59 | "comment": "staked-fwp-wstx-alex-50-50-v1-01.clar:7" 60 | }, 61 | { 62 | "code": 1001, 63 | "comment": "yield-token-equation.clar:13" 64 | } 65 | ], 66 | "ERR-TOKEN-NOT-AUTHORIZED": [ 67 | { 68 | "code": 1001, 69 | "comment": "bridge-endpoint.clar:5" 70 | } 71 | ], 72 | "ERR-RECIPIENT-NOT-AUTHORIZED": [ 73 | { 74 | "code": 1002, 75 | "comment": "bridge-endpoint.clar:6" 76 | } 77 | ], 78 | "ERR-WRAPPER-NOT-AUTHORIZED": [ 79 | { 80 | "code": 1004, 81 | "comment": "bridge-endpoint.clar:7" 82 | } 83 | ], 84 | "ERR-UNKNOWN-USER-ID": [ 85 | { 86 | "code": 1005, 87 | "comment": "bridge-endpoint.clar:8" 88 | } 89 | ], 90 | "ERR-UNKNOWN-VALIDATOR-ID": [ 91 | { 92 | "code": 1006, 93 | "comment": "bridge-endpoint.clar:9" 94 | } 95 | ], 96 | "ERR-USER-ALREADY-REGISTERED": [ 97 | { 98 | "code": 1007, 99 | "comment": "bridge-endpoint.clar:10" 100 | }, 101 | { 102 | "code": 10001, 103 | "comment": "alex-reserve-pool.clar:8" 104 | } 105 | ], 106 | "ERR-VALIDATOR-ALREADY-REGISTERED": [ 107 | { 108 | "code": 1008, 109 | "comment": "bridge-endpoint.clar:11" 110 | } 111 | ], 112 | "ERR-DUPLICATE-SIGNATURE": [ 113 | { 114 | "code": 1009, 115 | "comment": "bridge-endpoint.clar:12" 116 | } 117 | ], 118 | "ERR-ORDER-HASH-MISMATCH": [ 119 | { 120 | "code": 1010, 121 | "comment": "bridge-endpoint.clar:13" 122 | } 123 | ], 124 | "ERR-INVALID-SIGNATURE": [ 125 | { 126 | "code": 1011, 127 | "comment": "bridge-endpoint.clar:14" 128 | } 129 | ], 130 | "ERR-UKNOWN-RELAYER": [ 131 | { 132 | "code": 1012, 133 | "comment": "bridge-endpoint.clar:15" 134 | } 135 | ], 136 | "ERR-REQUIRED-VALIDATORS": [ 137 | { 138 | "code": 1013, 139 | "comment": "bridge-endpoint.clar:16" 140 | } 141 | ], 142 | "ERR-ORDER-ALREADY-SENT": [ 143 | { 144 | "code": 1014, 145 | "comment": "bridge-endpoint.clar:17" 146 | } 147 | ], 148 | "ERR-PAUSED": [ 149 | { 150 | "code": 1015, 151 | "comment": "bridge-endpoint.clar:18" 152 | }, 153 | { 154 | "code": 1001, 155 | "comment": "amm-swap-pool.clar:19" 156 | } 157 | ], 158 | "ERR-USER-NOT-WHITELISTED": [ 159 | { 160 | "code": 1016, 161 | "comment": "bridge-endpoint.clar:19" 162 | } 163 | ], 164 | "ERR-AMOUNT-LESS-THAN-MIN-FEE": [ 165 | { 166 | "code": 1017, 167 | "comment": "bridge-endpoint.clar:20" 168 | } 169 | ], 170 | "ERR-UNKNOWN-CHAIN-ID": [ 171 | { 172 | "code": 1018, 173 | "comment": "bridge-endpoint.clar:21" 174 | } 175 | ], 176 | "ERR-NO-LIQUIDITY": [ 177 | { 178 | "code": 2002, 179 | "comment": "weighted-equation-v1-01.clar:10" 180 | } 181 | ], 182 | "ERR-WEIGHT-SUM": [ 183 | { 184 | "code": 4000, 185 | "comment": "weighted-equation-v1-01.clar:11" 186 | } 187 | ], 188 | "ERR-MAX-IN-RATIO": [ 189 | { 190 | "code": 4001, 191 | "comment": "weighted-equation-v1-01.clar:12" 192 | } 193 | ], 194 | "ERR-MAX-OUT-RATIO": [ 195 | { 196 | "code": 4002, 197 | "comment": "weighted-equation-v1-01.clar:13" 198 | } 199 | ], 200 | "ERR-X-OUT-OF-BOUNDS": [ 201 | { 202 | "code": 5009, 203 | "comment": "weighted-equation-v1-01.clar:485" 204 | } 205 | ], 206 | "ERR-Y-OUT-OF-BOUNDS": [ 207 | { 208 | "code": 5010, 209 | "comment": "weighted-equation-v1-01.clar:486" 210 | } 211 | ], 212 | "ERR-PRODUCT-OUT-OF-BOUNDS": [ 213 | { 214 | "code": 5011, 215 | "comment": "weighted-equation-v1-01.clar:487" 216 | } 217 | ], 218 | "ERR-INVALID-EXPONENT": [ 219 | { 220 | "code": 5012, 221 | "comment": "weighted-equation-v1-01.clar:488" 222 | } 223 | ], 224 | "ERR-OUT-OF-BOUNDS": [ 225 | { 226 | "code": 5013, 227 | "comment": "weighted-equation-v1-01.clar:489" 228 | } 229 | ], 230 | "ERR-NO-FEE-CHANGE": [ 231 | { 232 | "code": 8001, 233 | "comment": "multisig-fwp-alex-autoalex.clar:6" 234 | } 235 | ], 236 | "ERR-BLOCK-HEIGHT-NOT-REACHED": [ 237 | { 238 | "code": 8003, 239 | "comment": "multisig-fwp-alex-autoalex.clar:8" 240 | }, 241 | { 242 | "code": 2042, 243 | "comment": "alex-lottery.clar:5" 244 | } 245 | ], 246 | "ERR-INVALID-LIQUIDITY": [ 247 | { 248 | "code": 2003, 249 | "comment": "auto-fwp-alex-usda.clar:227" 250 | } 251 | ], 252 | "ERR-REWARD-CYCLE-NOT-COMPLETED": [ 253 | { 254 | "code": 10017, 255 | "comment": "auto-fwp-alex-usda.clar:228" 256 | } 257 | ], 258 | "ERR-STAKING-NOT-AVAILABLE": [ 259 | { 260 | "code": 10015, 261 | "comment": "auto-fwp-alex-usda.clar:229" 262 | }, 263 | { 264 | "code": 2027, 265 | "comment": "yield-vault-alex.clar:13" 266 | } 267 | ], 268 | "ERR-NOT-ACTIVATED": [ 269 | { 270 | "code": 2043, 271 | "comment": "auto-fwp-alex-usda.clar:231" 272 | } 273 | ], 274 | "ERR-ACTIVATED": [ 275 | { 276 | "code": 2044, 277 | "comment": "auto-fwp-alex-usda.clar:232" 278 | } 279 | ], 280 | "ERR-USER-ID-NOT-FOUND": [ 281 | { 282 | "code": 10003, 283 | "comment": "auto-fwp-alex-usda.clar:233" 284 | } 285 | ], 286 | "ERR-INSUFFICIENT-BALANCE": [ 287 | { 288 | "code": 2045, 289 | "comment": "auto-fwp-alex-usda.clar:234" 290 | } 291 | ], 292 | "ERR-INVALID-PERCENT": [ 293 | { 294 | "code": 5000, 295 | "comment": "auto-fwp-alex-usda.clar:235" 296 | } 297 | ], 298 | "ERR-AVAILABLE-ALEX": [ 299 | { 300 | "code": 20000, 301 | "comment": "auto-fwp-alex-usda-100x.clar:6" 302 | } 303 | ], 304 | "ERR-BLOCK-HEIGHT": [ 305 | { 306 | "code": 2043, 307 | "comment": "auto-fwp-alex-autoalex-x-v1-01.clar:9" 308 | } 309 | ], 310 | "ERR-NOT-ENOUGH-ALEX": [ 311 | { 312 | "code": 20001, 313 | "comment": "auto-fwp-alex-autoalex-x-v1-01.clar:10" 314 | } 315 | ], 316 | "ERR-X-OUT-OF-BOUNDS-MANTISSA": [ 317 | { 318 | "code": 50091, 319 | "comment": "math-fixed-point-v2.clar:116" 320 | } 321 | ], 322 | "ERR-X-OUT-OF-BOUNDS-EXP": [ 323 | { 324 | "code": 50092, 325 | "comment": "math-fixed-point-v2.clar:117" 326 | } 327 | ], 328 | "ERR-Y-OUT-OF-BOUNDS-MANTISSA": [ 329 | { 330 | "code": 50101, 331 | "comment": "math-fixed-point-v2.clar:119" 332 | } 333 | ], 334 | "ERR-Y-OUT-OF-BOUNDS-EXP": [ 335 | { 336 | "code": 50102, 337 | "comment": "math-fixed-point-v2.clar:120" 338 | } 339 | ], 340 | "ERR-NOT-POSITIVE": [ 341 | { 342 | "code": 5014, 343 | "comment": "math-fixed-point-v2.clar:124" 344 | } 345 | ], 346 | "ERR-MINT-FAILED": [ 347 | { 348 | "code": 6002, 349 | "comment": "token-wxusd.clar:16" 350 | } 351 | ], 352 | "ERR-BURN-FAILED": [ 353 | { 354 | "code": 6003, 355 | "comment": "token-wxusd.clar:17" 356 | } 357 | ], 358 | "ERR-NOT-SUPPORTED": [ 359 | { 360 | "code": 6004, 361 | "comment": "token-wxusd.clar:19" 362 | } 363 | ], 364 | "ERR-CONTRACT-NOT-ACTIVATED": [ 365 | { 366 | "code": 10005, 367 | "comment": "alex-reserve-pool.clar:10" 368 | } 369 | ], 370 | "ERR-CANNOT-STAKE": [ 371 | { 372 | "code": 10016, 373 | "comment": "alex-reserve-pool.clar:12" 374 | } 375 | ], 376 | "ERR-AMOUNT-EXCEED-RESERVE": [ 377 | { 378 | "code": 2024, 379 | "comment": "alex-reserve-pool.clar:14" 380 | } 381 | ], 382 | "ERR-INVALID-POOL": [ 383 | { 384 | "code": 2001, 385 | "comment": "collateral-rebalancing-pool.clar:9" 386 | } 387 | ], 388 | "ERR-POOL-ALREADY-EXISTS": [ 389 | { 390 | "code": 2000, 391 | "comment": "collateral-rebalancing-pool.clar:12" 392 | } 393 | ], 394 | "ERR-GET-WEIGHT-FAIL": [ 395 | { 396 | "code": 2012, 397 | "comment": "collateral-rebalancing-pool.clar:15" 398 | } 399 | ], 400 | "ERR-EXPIRY": [ 401 | { 402 | "code": 2017, 403 | "comment": "collateral-rebalancing-pool.clar:16" 404 | } 405 | ], 406 | "ERR-LTV-TOO-HIGH": [ 407 | { 408 | "code": 2019, 409 | "comment": "collateral-rebalancing-pool.clar:19" 410 | } 411 | ], 412 | "ERR-EXCEEDS-MAX-SLIPPAGE": [ 413 | { 414 | "code": 2020, 415 | "comment": "collateral-rebalancing-pool.clar:20" 416 | } 417 | ], 418 | "ERR-POOL-AT-CAPACITY": [ 419 | { 420 | "code": 2027, 421 | "comment": "collateral-rebalancing-pool.clar:22" 422 | } 423 | ], 424 | "ERR-ROLL-FLASH-LOAN-FEE": [ 425 | { 426 | "code": 2028, 427 | "comment": "collateral-rebalancing-pool.clar:23" 428 | } 429 | ], 430 | "ERR-ORACLE-NOT-ENABLED": [ 431 | { 432 | "code": 7002, 433 | "comment": "collateral-rebalancing-pool.clar:24" 434 | } 435 | ], 436 | "ERR-PERCENT-GREATER-THAN-ONE": [ 437 | { 438 | "code": 5000, 439 | "comment": "fixed-weight-pool-alex.clar:14" 440 | } 441 | ], 442 | "ERR-ORACLE-ALREADY-ENABLED": [ 443 | { 444 | "code": 7003, 445 | "comment": "fixed-weight-pool-alex.clar:17" 446 | } 447 | ], 448 | "ERR-ORACLE-AVERAGE-BIGGER-THAN-ONE": [ 449 | { 450 | "code": 7004, 451 | "comment": "fixed-weight-pool-alex.clar:18" 452 | } 453 | ], 454 | "ERR-UNKNOWN-LOTTERY": [ 455 | { 456 | "code": 2045, 457 | "comment": "alex-lottery.clar:3" 458 | } 459 | ], 460 | "ERR-UNKNOWN-LOTTERY-ROUND": [ 461 | { 462 | "code": 2047, 463 | "comment": "alex-lottery.clar:4" 464 | } 465 | ], 466 | "ERR-INVALID-SEQUENCE": [ 467 | { 468 | "code": 2046, 469 | "comment": "alex-lottery.clar:6" 470 | } 471 | ], 472 | "ERR-INVALID-LOTTERY-TOKEN": [ 473 | { 474 | "code": 2026, 475 | "comment": "alex-lottery.clar:7" 476 | } 477 | ], 478 | "ERR-INVALID-LOTTERY-SETTING": [ 479 | { 480 | "code": 110, 481 | "comment": "alex-lottery.clar:8" 482 | } 483 | ], 484 | "ERR-ALREADY-REGISTERED": [ 485 | { 486 | "code": 10001, 487 | "comment": "alex-lottery.clar:9" 488 | } 489 | ], 490 | "ERR-LOTTERY-ROUND-ALREADY-CLAIMED": [ 491 | { 492 | "code": 2048, 493 | "comment": "alex-lottery.clar:11" 494 | } 495 | ], 496 | "ERR-BONUS-TICKETS-EXCEED-MAX": [ 497 | { 498 | "code": 2049, 499 | "comment": "alex-lottery.clar:12" 500 | } 501 | ], 502 | "ERR-NOT-REGISTERED": [ 503 | { 504 | "code": 2050, 505 | "comment": "alex-lottery.clar:13" 506 | } 507 | ], 508 | "ERR-SWITCH-THRESHOLD-BIGGER-THAN-ONE": [ 509 | { 510 | "code": 7005, 511 | "comment": "amm-swap-pool.clar:20" 512 | } 513 | ], 514 | "ERR-STAKING-IN-PROGRESS": [ 515 | { 516 | "code": 2018, 517 | "comment": "yield-vault-alex.clar:12" 518 | } 519 | ], 520 | "ERR-ALREADY-EXPIRED": [ 521 | { 522 | "code": 2011, 523 | "comment": "liquidity-bootstrapping-pool.clar:16" 524 | } 525 | ], 526 | "ERR-PRICE-LOWER-THAN-MIN": [ 527 | { 528 | "code": 2021, 529 | "comment": "liquidity-bootstrapping-pool.clar:18" 530 | } 531 | ], 532 | "ERR-PRICE-GREATER-THAN-MAX": [ 533 | { 534 | "code": 2022, 535 | "comment": "liquidity-bootstrapping-pool.clar:19" 536 | } 537 | ], 538 | "ERR-NO-FEE": [ 539 | { 540 | "code": 2005, 541 | "comment": "yield-token-pool.clar:14" 542 | } 543 | ], 544 | "ERR-NO-FEE-Y": [ 545 | { 546 | "code": 2006, 547 | "comment": "yield-token-pool.clar:15" 548 | } 549 | ], 550 | "ERR-INVALID-EXPIRY": [ 551 | { 552 | "code": 2009, 553 | "comment": "yield-token-pool.clar:16" 554 | } 555 | ], 556 | "ERR-GET-EXPIRY-FAIL-ERR": [ 557 | { 558 | "code": 2013, 559 | "comment": "yield-token-pool.clar:17" 560 | } 561 | ], 562 | "ERR-DY-BIGGER-THAN-AVAILABLE": [ 563 | { 564 | "code": 2016, 565 | "comment": "yield-token-pool.clar:18" 566 | } 567 | ], 568 | "ERR-NOT-FOUND": [ 569 | { 570 | "code": 1003, 571 | "comment": "dual-farming-pool.clar:8" 572 | } 573 | ], 574 | "ERR-INVALID-TICKET": [ 575 | { 576 | "code": 2028, 577 | "comment": "alex-launchpad.clar:12" 578 | } 579 | ], 580 | "ERR-NO-VRF-SEED-FOUND": [ 581 | { 582 | "code": 2030, 583 | "comment": "alex-launchpad.clar:13" 584 | } 585 | ], 586 | "ERR-CLAIM-NOT-AVAILABLE": [ 587 | { 588 | "code": 2031, 589 | "comment": "alex-launchpad.clar:14" 590 | } 591 | ], 592 | "ERR-REGISTRATION-STARTED": [ 593 | { 594 | "code": 2033, 595 | "comment": "alex-launchpad.clar:15" 596 | } 597 | ], 598 | "ERR-REGISTRATION-NOT-STARTED": [ 599 | { 600 | "code": 2034, 601 | "comment": "alex-launchpad.clar:16" 602 | } 603 | ], 604 | "ERR-LISTING-ACTIVATED": [ 605 | { 606 | "code": 2035, 607 | "comment": "alex-launchpad.clar:17" 608 | } 609 | ], 610 | "ERR-LISTING-NOT-ACTIVATED": [ 611 | { 612 | "code": 2036, 613 | "comment": "alex-launchpad.clar:18" 614 | } 615 | ], 616 | "ERR-INVALID-REGISTRATION-PERIOD": [ 617 | { 618 | "code": 2037, 619 | "comment": "alex-launchpad.clar:19" 620 | } 621 | ], 622 | "ERR-REGISTRATION-ENDED": [ 623 | { 624 | "code": 2038, 625 | "comment": "alex-launchpad.clar:20" 626 | } 627 | ], 628 | "ERR-REGISTRATION-NOT-ENDED": [ 629 | { 630 | "code": 2039, 631 | "comment": "alex-launchpad.clar:21" 632 | } 633 | ], 634 | "ERR-CLAIM-ENDED": [ 635 | { 636 | "code": 2040, 637 | "comment": "alex-launchpad.clar:22" 638 | } 639 | ], 640 | "ERR-CLAIM-NOT-ENDED": [ 641 | { 642 | "code": 2041, 643 | "comment": "alex-launchpad.clar:23" 644 | } 645 | ], 646 | "ERR-INVALID-CLAIM-PERIOD": [ 647 | { 648 | "code": 2042, 649 | "comment": "alex-launchpad.clar:24" 650 | } 651 | ], 652 | "ERR-REFUND-NOT-AVAILABLE": [ 653 | { 654 | "code": 2043, 655 | "comment": "alex-launchpad.clar:25" 656 | } 657 | ], 658 | "ERR-ALREADY-PROCESSED": [ 659 | { 660 | "code": 1409, 661 | "comment": "fwp-wstx-alex-tranched-64.clar:15" 662 | } 663 | ], 664 | "ERR-DISTRIBUTION": [ 665 | { 666 | "code": 1410, 667 | "comment": "fwp-wstx-alex-tranched-64.clar:16" 668 | } 669 | ], 670 | "ERR-CHAIN-NOT-AUTHORIZED": [ 671 | { 672 | "code": 1003, 673 | "comment": "bridge-helper.clar:7" 674 | } 675 | ] 676 | } 677 | -------------------------------------------------------------------------------- /src/helpers/FeeHelper.ts: -------------------------------------------------------------------------------- 1 | import { unwrapResponse } from 'clarity-codegen'; 2 | import { readonlyCall } from '../utils/readonlyCallExecutor'; 3 | import type { Currency } from '../currency'; 4 | import type { PoolData } from '../types'; 5 | import { 6 | type AMMRouteSegment, 7 | resolveAmmRoute, 8 | } from '../utils/ammRouteResolver'; 9 | import { hasLength } from '../utils/arrayHelper'; 10 | 11 | export async function getLiquidityProviderFee( 12 | tokenX: Currency, 13 | tokenY: Currency, 14 | pools: PoolData[], 15 | getContractId: (currency: Currency) => string, 16 | customRoute?: AMMRouteSegment[] 17 | ): Promise { 18 | const ammRoute = customRoute ?? resolveAmmRoute(tokenX, tokenY, pools); 19 | if (ammRoute.length === 0) { 20 | throw new Error('No AMM pools in route'); 21 | } 22 | if (hasLength(ammRoute, 1)) { 23 | const [segment] = ammRoute; 24 | return await readonlyCall('amm-pool-v2-01', 'fee-helper', { 25 | 'token-x': getContractId(tokenX), 26 | 'token-y': getContractId(tokenY), 27 | factor: segment.pool.factor, 28 | }).then(unwrapResponse); 29 | } 30 | if (hasLength(ammRoute, 2)) { 31 | const [segment1, segment2] = ammRoute; 32 | return await readonlyCall('amm-pool-v2-01', 'fee-helper-a', { 33 | 'token-x': getContractId(tokenX), 34 | 'token-y': getContractId(segment1.neighbour), 35 | 'token-z': getContractId(segment2.neighbour), 36 | 'factor-x': segment1.pool.factor, 37 | 'factor-y': segment2.pool.factor, 38 | }).then(unwrapResponse); 39 | } 40 | if (hasLength(ammRoute, 3)) { 41 | const [segment1, segment2, segment3] = ammRoute; 42 | return await readonlyCall('amm-pool-v2-01', 'fee-helper-b', { 43 | 'token-x': getContractId(tokenX), 44 | 'token-y': getContractId(segment1.neighbour), 45 | 'token-z': getContractId(segment2.neighbour), 46 | 'token-w': getContractId(segment3.neighbour), 47 | 'factor-x': segment1.pool.factor, 48 | 'factor-y': segment2.pool.factor, 49 | 'factor-z': segment3.pool.factor, 50 | }).then(unwrapResponse); 51 | } 52 | if (hasLength(ammRoute, 4)) { 53 | const [segment1, segment2, segment3, segment4] = ammRoute; 54 | return await readonlyCall('amm-pool-v2-01', 'fee-helper-c', { 55 | 'token-x': getContractId(tokenX), 56 | 'token-y': getContractId(segment1.neighbour), 57 | 'token-z': getContractId(segment2.neighbour), 58 | 'token-w': getContractId(segment3.neighbour), 59 | 'token-v': getContractId(segment4.neighbour), 60 | 'factor-x': segment1.pool.factor, 61 | 'factor-y': segment2.pool.factor, 62 | 'factor-z': segment3.pool.factor, 63 | 'factor-w': segment4.pool.factor, 64 | }).then(unwrapResponse); 65 | } 66 | throw new Error('Too many AMM pools in route'); 67 | } 68 | -------------------------------------------------------------------------------- /src/helpers/RateHelper.ts: -------------------------------------------------------------------------------- 1 | import { unwrapResponse } from 'clarity-codegen'; 2 | import { readonlyCall } from '../utils/readonlyCallExecutor'; 3 | import type { Currency } from '../currency'; 4 | import type { PoolData } from '../types'; 5 | import { 6 | type AMMRouteSegment, 7 | resolveAmmRoute, 8 | } from '../utils/ammRouteResolver'; 9 | import { hasLength } from '../utils/arrayHelper'; 10 | 11 | export const getYAmountFromXAmount = async ( 12 | tokenX: Currency, 13 | tokenY: Currency, 14 | fromAmount: bigint, 15 | ammPools: PoolData[], 16 | getContractId: (currency: Currency) => string, 17 | customRoute?: AMMRouteSegment[] 18 | ): Promise => { 19 | const ammRoute = customRoute ?? resolveAmmRoute(tokenX, tokenY, ammPools); 20 | if (ammRoute.length === 0) { 21 | throw new Error('No AMM pool found for the given route'); 22 | } 23 | if (hasLength(ammRoute, 1)) { 24 | const [segment] = ammRoute; 25 | return await readonlyCall('alex-amm-pool-v2-01-get-helper', 'get-helper-with-fee', { 26 | 'token-x': getContractId(tokenX), 27 | 'token-y': getContractId(segment.neighbour), 28 | dx: fromAmount, 29 | factor: segment.pool.factor, 30 | }).then(unwrapResponse); 31 | } 32 | if (hasLength(ammRoute, 2)) { 33 | const [segment1, segment2] = ammRoute; 34 | return await readonlyCall('alex-amm-pool-v2-01-get-helper', 'get-helper-with-fee-a', { 35 | 'token-x': getContractId(tokenX), 36 | 'token-y': getContractId(segment1.neighbour), 37 | 'token-z': getContractId(segment2.neighbour), 38 | 'factor-x': segment1.pool.factor, 39 | 'factor-y': segment2.pool.factor, 40 | dx: fromAmount, 41 | }).then(unwrapResponse); 42 | } 43 | if (hasLength(ammRoute, 3)) { 44 | const [segment1, segment2, segment3] = ammRoute; 45 | return await readonlyCall('alex-amm-pool-v2-01-get-helper', 'get-helper-with-fee-b', { 46 | 'token-x': getContractId(tokenX), 47 | 'token-y': getContractId(segment1.neighbour), 48 | 'token-z': getContractId(segment2.neighbour), 49 | 'token-w': getContractId(segment3.neighbour), 50 | 'factor-x': segment1.pool.factor, 51 | 'factor-y': segment2.pool.factor, 52 | 'factor-z': segment3.pool.factor, 53 | dx: fromAmount, 54 | }).then(unwrapResponse); 55 | } 56 | if (hasLength(ammRoute, 4)) { 57 | const [segment1, segment2, segment3, segment4] = ammRoute; 58 | return await readonlyCall('alex-amm-pool-v2-01-get-helper', 'get-helper-with-fee-c', { 59 | 'token-x': getContractId(tokenX), 60 | 'token-y': getContractId(segment1.neighbour), 61 | 'token-z': getContractId(segment2.neighbour), 62 | 'token-w': getContractId(segment3.neighbour), 63 | 'token-v': getContractId(segment4.neighbour), 64 | 'factor-x': segment1.pool.factor, 65 | 'factor-y': segment2.pool.factor, 66 | 'factor-z': segment3.pool.factor, 67 | 'factor-w': segment4.pool.factor, 68 | dx: fromAmount, 69 | }).then(unwrapResponse); 70 | } 71 | throw new Error('Too many AMM pools in route'); 72 | }; 73 | -------------------------------------------------------------------------------- /src/helpers/RouteHelper.ts: -------------------------------------------------------------------------------- 1 | import type { Currency } from '../currency'; 2 | import type { 3 | PoolData, 4 | DetailedAMMRoutes, 5 | StacksAssetContractAddress, 6 | } from '../types'; 7 | import { 8 | type AMMRouteSegment, 9 | resolveAmmRoutes, 10 | } from '../utils/ammRouteResolver'; 11 | import { deserializeAssetIdentifier } from '../types'; 12 | import { announceAtLeastOne, hasLength } from '../utils/arrayHelper'; 13 | import type { TokenInfo } from '../types'; 14 | import { getYAmountFromXAmount } from './RateHelper'; 15 | 16 | export async function getAllPossibleRoute( 17 | from: Currency, 18 | to: Currency, 19 | pools: PoolData[] 20 | ): Promise { 21 | return resolveAmmRoutes(from, to, pools); 22 | } 23 | 24 | export async function getDetailedRoute( 25 | context: { 26 | ammPools: PoolData[]; 27 | tokenInfoMappings: Record; 28 | getContractId: (currency: Currency) => string; 29 | }, 30 | route: AMMRouteSegment[], 31 | fromAmount: bigint 32 | ): Promise { 33 | const detailRoute = await Promise.all( 34 | route.map( 35 | async ( 36 | segment 37 | ): Promise< 38 | | undefined 39 | | (DetailedAMMRoutes['swapPools'][number] & { 40 | fromCurrency: Currency; 41 | fromTokenAddress: StacksAssetContractAddress; 42 | }) 43 | > => { 44 | const fromTokenInfo = context.tokenInfoMappings[segment.from]; 45 | const toTokenInfo = context.tokenInfoMappings[segment.neighbour]; 46 | if (fromTokenInfo == null || toTokenInfo == null) { 47 | return undefined; 48 | } 49 | 50 | const fromTokenAddress = deserializeAssetIdentifier( 51 | fromTokenInfo.wrapToken 52 | ); 53 | const toTokenAddress = deserializeAssetIdentifier( 54 | toTokenInfo.wrapToken 55 | ); 56 | if (fromTokenAddress == null || toTokenAddress == null) { 57 | return undefined; 58 | } 59 | 60 | return { 61 | fromCurrency: segment.from, 62 | fromTokenAddress, 63 | toCurrency: segment.neighbour, 64 | toTokenAddress, 65 | poolId: segment.pool.poolId, 66 | pool: segment.pool, 67 | }; 68 | } 69 | ) 70 | ); 71 | 72 | if (detailRoute.some((x) => x == null)) return undefined; 73 | const _detailRoute = detailRoute as NonNullable< 74 | (typeof detailRoute)[number] 75 | >[]; 76 | 77 | if (hasLength(_detailRoute, 0)) return undefined; 78 | 79 | const firstSegment = _detailRoute[0]; 80 | const lastSegment = _detailRoute[_detailRoute.length - 1]; 81 | 82 | const toAmount = 83 | fromAmount === BigInt(0) 84 | ? BigInt(0) 85 | : await getYAmountFromXAmount( 86 | firstSegment.fromCurrency, 87 | lastSegment.toCurrency, 88 | fromAmount, 89 | context.ammPools, 90 | context.getContractId, 91 | route 92 | ).catch(() => BigInt(0)); 93 | 94 | return { 95 | fromCurrency: firstSegment.fromCurrency, 96 | fromTokenAddress: firstSegment.fromTokenAddress, 97 | swapPools: announceAtLeastOne( 98 | _detailRoute.map((segment) => ({ 99 | toCurrency: segment.toCurrency, 100 | toTokenAddress: segment.toTokenAddress, 101 | poolId: segment.poolId, 102 | pool: segment.pool, 103 | })) 104 | ), 105 | fromAmount, 106 | toAmount, 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /src/helpers/SponsorTxHelper.ts: -------------------------------------------------------------------------------- 1 | import { FungibleConditionCode } from '@stacks/transactions'; 2 | import { configs } from '../config'; 3 | import type { Currency } from '../currency'; 4 | import type { PoolData, TokenInfo } from '../types'; 5 | import { 6 | resolveAmmRoute, 7 | type AMMRoute, 8 | type AMMRouteSegment, 9 | } from '../utils/ammRouteResolver'; 10 | import { hasLength } from '../utils/arrayHelper'; 11 | import { transferFactory } from '../utils/postConditions'; 12 | import { composeTx, type TxToBroadCast } from './SwapHelper'; 13 | 14 | let sponsorData: 15 | | Promise<{ status: 'ok' | string; perRouteFee: bigint }> 16 | | undefined; 17 | 18 | export function getSponsorData(): Promise<{ 19 | status: 'ok' | string; 20 | perRouteFee: bigint; 21 | }> { 22 | if (sponsorData == null) { 23 | sponsorData = fetch(configs.SPONSORED_TX_STATUS, { 24 | method: 'GET', 25 | mode: 'cors', 26 | }) 27 | .then((res) => res.json()) 28 | .then((data) => ({ 29 | status: data.status, 30 | perRouteFee: BigInt(data.per_route_fee), 31 | })); 32 | } 33 | return sponsorData; 34 | } 35 | 36 | export const requiredStxAmountForSponsorTx = async ( 37 | _from: Currency, 38 | _to: Currency, 39 | customRoute: AMMRoute 40 | ): Promise => { 41 | // we need to convert the fee to the same unit as the amount 42 | const feePerSegment = await getSponsorData().then( 43 | (data) => (data.perRouteFee * BigInt(1e8)) / BigInt(1e6) 44 | ); 45 | return BigInt(customRoute.length) * feePerSegment; 46 | }; 47 | 48 | export function runSponsoredSpotTx( 49 | stxAddress: string, 50 | currencyX: Currency, 51 | currencyY: Currency, 52 | totalAmount: bigint, 53 | minDy: bigint, 54 | feeAmount: bigint, 55 | ammPools: PoolData[], 56 | mappings: TokenInfo[], 57 | customRoute?: AMMRouteSegment[] 58 | ): TxToBroadCast { 59 | const fromAmount = totalAmount - feeAmount; 60 | const ammRoute = 61 | customRoute ?? resolveAmmRoute(currencyX, currencyY, ammPools); 62 | const getContractId = (currency: Currency) => { 63 | const mapping = mappings.find((x) => x.id === currency); 64 | if (!mapping) { 65 | throw new Error(`Token mapping not found for currency: ${currency}`); 66 | } 67 | return mapping.wrapToken.split('::')[0] as `${string}.${string}`; 68 | }; 69 | const AlexVault = `${configs.CONTRACT_DEPLOYER}.amm-vault-v2-01`; 70 | if (ammRoute.length === 0) { 71 | throw new Error("Can't find AMM route"); 72 | } 73 | 74 | const transfer = transferFactory(mappings); 75 | 76 | if (hasLength(ammRoute, 1)) { 77 | const [segment] = ammRoute; 78 | return composeTx( 79 | 'sponsor-dex-v01', 80 | 'swap-helper', 81 | { 82 | 'token-x': getContractId(currencyX), 83 | 'token-y': getContractId(segment.neighbour), 84 | factor: segment.pool.factor, 85 | dx: fromAmount, 86 | 'min-dy': minDy, 87 | fee: feeAmount, 88 | }, 89 | [ 90 | transfer( 91 | stxAddress, 92 | currencyX, 93 | totalAmount, 94 | FungibleConditionCode.LessEqual 95 | ), 96 | transfer( 97 | AlexVault, 98 | currencyY, 99 | BigInt(0), 100 | FungibleConditionCode.GreaterEqual 101 | ), 102 | ] 103 | ); 104 | } 105 | 106 | if (hasLength(ammRoute, 2)) { 107 | const [segment1, segment2] = ammRoute; 108 | return composeTx( 109 | 'sponsor-dex-v01', 110 | 'swap-helper-a', 111 | { 112 | 'token-x': getContractId(currencyX), 113 | 'token-y': getContractId(segment1.neighbour), 114 | 'token-z': getContractId(segment2.neighbour), 115 | 'factor-x': segment1.pool.factor, 116 | 'factor-y': segment2.pool.factor, 117 | dx: fromAmount, 118 | 'min-dz': minDy, 119 | fee: feeAmount, 120 | }, 121 | [ 122 | transfer( 123 | stxAddress, 124 | currencyX, 125 | totalAmount, 126 | FungibleConditionCode.LessEqual 127 | ), 128 | transfer( 129 | AlexVault, 130 | segment1.neighbour, 131 | BigInt(0), 132 | FungibleConditionCode.GreaterEqual 133 | ), 134 | transfer( 135 | stxAddress, 136 | segment1.neighbour, 137 | BigInt(0), 138 | FungibleConditionCode.GreaterEqual 139 | ), 140 | transfer( 141 | AlexVault, 142 | currencyY, 143 | BigInt(0), 144 | FungibleConditionCode.GreaterEqual 145 | ), 146 | ] 147 | ); 148 | } 149 | 150 | if (hasLength(ammRoute, 3)) { 151 | const [segment1, segment2, segment3] = ammRoute; 152 | return composeTx( 153 | 'sponsor-dex-v01', 154 | 'swap-helper-b', 155 | { 156 | 'token-x': getContractId(currencyX), 157 | 'token-y': getContractId(segment1.neighbour), 158 | 'token-z': getContractId(segment2.neighbour), 159 | 'token-w': getContractId(segment3.neighbour), 160 | 'factor-x': segment1.pool.factor, 161 | 'factor-y': segment2.pool.factor, 162 | 'factor-z': segment3.pool.factor, 163 | dx: fromAmount, 164 | 'min-dw': minDy, 165 | fee: feeAmount, 166 | }, 167 | [ 168 | transfer(stxAddress, currencyX, totalAmount), 169 | transfer( 170 | AlexVault, 171 | segment1.neighbour, 172 | BigInt(0), 173 | FungibleConditionCode.GreaterEqual 174 | ), 175 | transfer( 176 | stxAddress, 177 | segment1.neighbour, 178 | BigInt(0), 179 | FungibleConditionCode.GreaterEqual 180 | ), 181 | transfer( 182 | AlexVault, 183 | segment2.neighbour, 184 | BigInt(0), 185 | FungibleConditionCode.GreaterEqual 186 | ), 187 | transfer( 188 | stxAddress, 189 | segment2.neighbour, 190 | BigInt(0), 191 | FungibleConditionCode.GreaterEqual 192 | ), 193 | transfer( 194 | AlexVault, 195 | currencyY, 196 | BigInt(0), 197 | FungibleConditionCode.GreaterEqual 198 | ), 199 | ] 200 | ); 201 | } 202 | 203 | if (hasLength(ammRoute, 4)) { 204 | const [segment1, segment2, segment3, segment4] = ammRoute; 205 | return composeTx( 206 | 'sponsor-dex-v01', 207 | 'swap-helper-c', 208 | { 209 | 'token-x': getContractId(currencyX), 210 | 'token-y': getContractId(segment1.neighbour), 211 | 'token-z': getContractId(segment2.neighbour), 212 | 'token-w': getContractId(segment3.neighbour), 213 | 'token-v': getContractId(segment4.neighbour), 214 | 'factor-x': segment1.pool.factor, 215 | 'factor-y': segment2.pool.factor, 216 | 'factor-z': segment3.pool.factor, 217 | 'factor-w': segment4.pool.factor, 218 | dx: fromAmount, 219 | 'min-dv': minDy, 220 | fee: feeAmount, 221 | }, 222 | [ 223 | transfer(stxAddress, currencyX, totalAmount), 224 | transfer( 225 | AlexVault, 226 | segment1.neighbour, 227 | BigInt(0), 228 | FungibleConditionCode.GreaterEqual 229 | ), 230 | transfer( 231 | stxAddress, 232 | segment1.neighbour, 233 | BigInt(0), 234 | FungibleConditionCode.GreaterEqual 235 | ), 236 | transfer( 237 | AlexVault, 238 | segment2.neighbour, 239 | BigInt(0), 240 | FungibleConditionCode.GreaterEqual 241 | ), 242 | transfer( 243 | stxAddress, 244 | segment2.neighbour, 245 | BigInt(0), 246 | FungibleConditionCode.GreaterEqual 247 | ), 248 | transfer( 249 | AlexVault, 250 | segment3.neighbour, 251 | BigInt(0), 252 | FungibleConditionCode.GreaterEqual 253 | ), 254 | transfer( 255 | stxAddress, 256 | segment3.neighbour, 257 | BigInt(0), 258 | FungibleConditionCode.GreaterEqual 259 | ), 260 | transfer( 261 | AlexVault, 262 | currencyY, 263 | BigInt(0), 264 | FungibleConditionCode.GreaterEqual 265 | ), 266 | ] 267 | ); 268 | } 269 | 270 | throw new Error('Too many AMM pools in route'); 271 | } 272 | 273 | export enum SponsoredTxErrorCode { 274 | // The submitted tx payload is invalid 275 | 'invalid_tx' = 'invalid_tx', 276 | // The requested contract / function aren't whitelisted 277 | 'operation_not_supported' = 'operation_not_supported', 278 | // Current user already have a pending sponsored transaction 279 | 'pending_operation_exists' = 'pending_operation_exists', 280 | // The requested tx exceed the capacity of the pool 281 | 'capacity_exceed' = 'capacity_exceed', 282 | // Current user have pending operation, we require the submitted nonce to be immediate nonce 283 | 'invalid_nonce' = 'invalid_nonce', 284 | // Worker failed to broadcast the tx 285 | 'broadcast_error' = 'broadcast_error', 286 | // Insufficient funds to cover sponsor fee 287 | 'insufficient_funds' = 'insufficient_funds', 288 | 'unknown_error' = 'unknown_error', 289 | } 290 | 291 | export class SponsoredTxError extends Error { 292 | constructor(readonly code: SponsoredTxErrorCode, message: string) { 293 | super(message); 294 | } 295 | } 296 | 297 | export async function broadcastSponsoredTx(tx: string): Promise { 298 | const response = await fetch(configs.SPONSORED_TX_EXECUTOR, { 299 | method: 'POST', 300 | mode: 'cors', 301 | headers: { 302 | 'Content-Type': 'application/json', 303 | }, 304 | body: JSON.stringify({ 305 | wrap_http_code: 'true', 306 | tx, 307 | }), 308 | }); 309 | if (!response.ok) { 310 | throw new SponsoredTxError( 311 | SponsoredTxErrorCode.unknown_error, 312 | response.statusText 313 | ); 314 | } 315 | const result = (await response.json()) as { 316 | http_code: number; 317 | code?: SponsoredTxErrorCode; 318 | message?: string; 319 | tx_id?: string; 320 | }; 321 | if (result.http_code !== 200 || result.tx_id == null) { 322 | const message = result.message ?? 'Unknown Error'; 323 | const errorCode = result.code ?? SponsoredTxErrorCode.unknown_error; 324 | throw new SponsoredTxError(errorCode, message); 325 | } 326 | return result.tx_id; 327 | } 328 | -------------------------------------------------------------------------------- /src/helpers/SwapHelper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type ClarityValue, 3 | FungibleConditionCode, 4 | type PostCondition, 5 | } from '@stacks/transactions'; 6 | import type { 7 | OpenCallFunctionDescriptor, 8 | ParameterObjOfDescriptor, 9 | } from 'clarity-codegen'; 10 | import { AlexContracts } from '../generated/smartContract/contracts_Alex'; 11 | import { configs } from '../config'; 12 | import type { Currency } from '../currency'; 13 | import type { PoolData, TokenInfo } from '../types'; 14 | import { 15 | type AMMRouteSegment, 16 | resolveAmmRoute, 17 | } from '../utils/ammRouteResolver'; 18 | import { transferFactory } from '../utils/postConditions'; 19 | import { hasLength } from '../utils/arrayHelper'; 20 | 21 | type Contracts = typeof AlexContracts; 22 | 23 | export type TxToBroadCast = { 24 | contractAddress: string; 25 | contractName: string; 26 | functionName: string; 27 | functionArgs: ClarityValue[]; 28 | postConditions: PostCondition[]; 29 | }; 30 | 31 | export const composeTx = < 32 | T extends keyof Contracts, 33 | F extends keyof Contracts[T], 34 | Descriptor extends Contracts[T][F] 35 | >( 36 | contractName: T, 37 | functionName: F, 38 | args: Descriptor extends OpenCallFunctionDescriptor 39 | ? ParameterObjOfDescriptor 40 | : never, 41 | postConditions: PostCondition[][] 42 | ): TxToBroadCast => { 43 | const functionDescriptor = AlexContracts[contractName][ 44 | functionName 45 | ] as OpenCallFunctionDescriptor; 46 | const clarityArgs = functionDescriptor.input.map((arg) => 47 | arg.type.encode(args[arg.name]) 48 | ); 49 | return { 50 | contractName, 51 | functionName: String(functionName), 52 | functionArgs: clarityArgs, 53 | contractAddress: 54 | contractName === 'sponsor-dex-v01' 55 | ? configs.SPONSOR_TX_DEPLOYER 56 | : configs.CONTRACT_DEPLOYER, 57 | postConditions: postConditions.flat(), 58 | }; 59 | }; 60 | 61 | export function runSpot( 62 | stxAddress: string, 63 | currencyX: Currency, 64 | currencyY: Currency, 65 | fromAmount: bigint, 66 | minDy: bigint, 67 | ammPools: PoolData[], 68 | mappings: TokenInfo[], 69 | customRoute?: AMMRouteSegment[] 70 | ): TxToBroadCast { 71 | const ammRoute = 72 | customRoute ?? resolveAmmRoute(currencyX, currencyY, ammPools); 73 | const getContractId = (currency: Currency) => { 74 | const mapping = mappings.find((x) => x.id === currency); 75 | if (!mapping) { 76 | throw new Error(`Token mapping not found for currency: ${currency}`); 77 | } 78 | return mapping.wrapToken.split('::')[0] as `${string}.${string}`; 79 | }; 80 | const AlexVault = `${configs.CONTRACT_DEPLOYER}.amm-vault-v2-01`; 81 | if (ammRoute.length === 0) { 82 | throw new Error("Can't find AMM route"); 83 | } 84 | 85 | const transfer = transferFactory(mappings); 86 | 87 | if (hasLength(ammRoute, 1)) { 88 | const [segment] = ammRoute; 89 | return composeTx( 90 | 'amm-pool-v2-01', 91 | 'swap-helper', 92 | { 93 | 'token-x-trait': getContractId(currencyX), 94 | 'token-y-trait': getContractId(segment.neighbour), 95 | factor: segment.pool.factor, 96 | dx: fromAmount, 97 | 'min-dy': minDy, 98 | }, 99 | [ 100 | transfer(stxAddress, currencyX, fromAmount), 101 | transfer( 102 | AlexVault, 103 | currencyY, 104 | minDy, 105 | FungibleConditionCode.GreaterEqual 106 | ), 107 | ] 108 | ); 109 | } 110 | 111 | if (hasLength(ammRoute, 2)) { 112 | const [segment1, segment2] = ammRoute; 113 | return composeTx( 114 | 'amm-pool-v2-01', 115 | 'swap-helper-a', 116 | { 117 | 'token-x-trait': getContractId(currencyX), 118 | 'token-y-trait': getContractId(segment1.neighbour), 119 | 'token-z-trait': getContractId(segment2.neighbour), 120 | 'factor-x': segment1.pool.factor, 121 | 'factor-y': segment2.pool.factor, 122 | dx: fromAmount, 123 | 'min-dz': minDy, 124 | }, 125 | [ 126 | transfer(stxAddress, currencyX, fromAmount), 127 | transfer( 128 | AlexVault, 129 | segment1.neighbour, 130 | BigInt(0), 131 | FungibleConditionCode.GreaterEqual 132 | ), 133 | transfer( 134 | stxAddress, 135 | segment1.neighbour, 136 | BigInt(0), 137 | FungibleConditionCode.GreaterEqual 138 | ), 139 | transfer( 140 | AlexVault, 141 | currencyY, 142 | minDy, 143 | FungibleConditionCode.GreaterEqual 144 | ), 145 | ] 146 | ); 147 | } 148 | 149 | if (hasLength(ammRoute, 3)) { 150 | const [segment1, segment2, segment3] = ammRoute; 151 | return composeTx( 152 | 'amm-pool-v2-01', 153 | 'swap-helper-b', 154 | { 155 | 'token-x-trait': getContractId(currencyX), 156 | 'token-y-trait': getContractId(segment1.neighbour), 157 | 'token-z-trait': getContractId(segment2.neighbour), 158 | 'token-w-trait': getContractId(segment3.neighbour), 159 | 'factor-x': segment1.pool.factor, 160 | 'factor-y': segment2.pool.factor, 161 | 'factor-z': segment3.pool.factor, 162 | dx: fromAmount, 163 | 'min-dw': minDy, 164 | }, 165 | [ 166 | transfer(stxAddress, currencyX, fromAmount), 167 | transfer( 168 | AlexVault, 169 | segment1.neighbour, 170 | BigInt(0), 171 | FungibleConditionCode.GreaterEqual 172 | ), 173 | transfer( 174 | stxAddress, 175 | segment1.neighbour, 176 | BigInt(0), 177 | FungibleConditionCode.GreaterEqual 178 | ), 179 | transfer( 180 | AlexVault, 181 | segment2.neighbour, 182 | BigInt(0), 183 | FungibleConditionCode.GreaterEqual 184 | ), 185 | transfer( 186 | stxAddress, 187 | segment2.neighbour, 188 | BigInt(0), 189 | FungibleConditionCode.GreaterEqual 190 | ), 191 | transfer( 192 | AlexVault, 193 | currencyY, 194 | minDy, 195 | FungibleConditionCode.GreaterEqual 196 | ), 197 | ] 198 | ); 199 | } 200 | 201 | if (hasLength(ammRoute, 4)) { 202 | const [segment1, segment2, segment3, segment4] = ammRoute; 203 | return composeTx( 204 | 'amm-pool-v2-01', 205 | 'swap-helper-c', 206 | { 207 | 'token-x-trait': getContractId(currencyX), 208 | 'token-y-trait': getContractId(segment1.neighbour), 209 | 'token-z-trait': getContractId(segment2.neighbour), 210 | 'token-w-trait': getContractId(segment3.neighbour), 211 | 'token-v-trait': getContractId(segment4.neighbour), 212 | 'factor-x': segment1.pool.factor, 213 | 'factor-y': segment2.pool.factor, 214 | 'factor-z': segment3.pool.factor, 215 | 'factor-w': segment4.pool.factor, 216 | dx: fromAmount, 217 | 'min-dv': minDy, 218 | }, 219 | [ 220 | transfer(stxAddress, currencyX, fromAmount), 221 | transfer( 222 | AlexVault, 223 | segment1.neighbour, 224 | BigInt(0), 225 | FungibleConditionCode.GreaterEqual 226 | ), 227 | transfer( 228 | stxAddress, 229 | segment1.neighbour, 230 | BigInt(0), 231 | FungibleConditionCode.GreaterEqual 232 | ), 233 | transfer( 234 | AlexVault, 235 | segment2.neighbour, 236 | BigInt(0), 237 | FungibleConditionCode.GreaterEqual 238 | ), 239 | transfer( 240 | stxAddress, 241 | segment2.neighbour, 242 | BigInt(0), 243 | FungibleConditionCode.GreaterEqual 244 | ), 245 | transfer( 246 | AlexVault, 247 | segment3.neighbour, 248 | BigInt(0), 249 | FungibleConditionCode.GreaterEqual 250 | ), 251 | transfer( 252 | stxAddress, 253 | segment3.neighbour, 254 | BigInt(0), 255 | FungibleConditionCode.GreaterEqual 256 | ), 257 | transfer( 258 | AlexVault, 259 | currencyY, 260 | minDy, 261 | FungibleConditionCode.GreaterEqual 262 | ), 263 | ] 264 | ); 265 | } 266 | 267 | throw new Error('Too many AMM pools in route'); 268 | } 269 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { Currency } from './currency'; 2 | export * from './alexSDK'; 3 | export { TokenInfo } from './types'; 4 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Currency } from './currency'; 2 | import type { AtLeastOne } from './utils/arrayHelper'; 3 | 4 | /** 5 | * TokenInfo represents the details of a token that can be used in the AlexSDK. 6 | */ 7 | export type TokenInfo = { 8 | /** 9 | * The unique identifier of the currency. 10 | */ 11 | id: Currency; 12 | 13 | /** 14 | * The display name of the token that ALEX maintains, usually the token symbol. 15 | */ 16 | name: string; 17 | 18 | /** 19 | * The URL to the icon image of the token, maintained by ALEX. 20 | */ 21 | icon: string; 22 | 23 | /** 24 | * The full asset id of the alex wrapped token in the format of "{deployer}.{contract}::{asset}". 25 | */ 26 | wrapToken: string; 27 | 28 | /** 29 | * The number of decimal places for the wrapped token. 30 | */ 31 | wrapTokenDecimals: number; 32 | 33 | /** 34 | * The full asset id of the underlying token in the format of "{deployer}.{contract}::{asset}". 35 | */ 36 | underlyingToken: string; 37 | 38 | /** 39 | * The number of decimal places for the underlying token. 40 | */ 41 | underlyingTokenDecimals: number; 42 | 43 | /** 44 | * A boolean flag indicating whether the token is a rebase token. 45 | * In a rebase token, getBalance is different from ft-balance 46 | * Also postcondition would need to be adjusted since amount is different from ft-events 47 | */ 48 | isRebaseToken: boolean; 49 | 50 | /** 51 | * A boolean flag indicating whether the token is a vault wrap token. 52 | * In a vault wrap token, the reserve is stored in the wrap token contract and not the vault for security. 53 | */ 54 | isVaultWrapToken: boolean; 55 | }; 56 | 57 | export type PoolData = { 58 | tokenX: Currency; 59 | tokenY: Currency; 60 | factor: bigint; 61 | poolId: bigint; 62 | }; 63 | 64 | export type PriceData = { 65 | token: Currency; 66 | price: number; 67 | }; 68 | 69 | export type AlexSDKResponse = { 70 | tokens: TokenInfo[]; 71 | pools: PoolData[]; 72 | }; 73 | 74 | export type BackendAPIPriceResponse = PriceData[]; 75 | 76 | export interface StacksContractAddress { 77 | deployerAddress: string; 78 | contractName: string; 79 | } 80 | 81 | export interface StacksAssetContractAddress extends StacksContractAddress { 82 | tokenId: string; 83 | } 84 | export function deserializeAssetIdentifier( 85 | a: string 86 | ): undefined | StacksAssetContractAddress { 87 | const step1Res = a.split('.'); 88 | if (step1Res.length !== 2) return undefined; 89 | 90 | const [deployerAddress, contractNameAndRest] = step1Res; 91 | const step2Res = contractNameAndRest.split('::'); 92 | if (!(step2Res.length > 1)) return undefined; 93 | 94 | const [contractName, tokenId] = step2Res; 95 | return { deployerAddress, contractName, tokenId }; 96 | } 97 | 98 | export type DetailedAMMRoutes = { 99 | fromCurrency: Currency; 100 | fromTokenAddress: StacksAssetContractAddress; 101 | swapPools: AtLeastOne<{ 102 | toCurrency: Currency; 103 | toTokenAddress: StacksAssetContractAddress; 104 | poolId: bigint; 105 | pool: PoolData; 106 | }>; 107 | fromAmount: bigint; 108 | toAmount: bigint; 109 | }; 110 | -------------------------------------------------------------------------------- /src/utils/ammRouteResolver.ts: -------------------------------------------------------------------------------- 1 | import { isNotNull } from './utils'; 2 | import type { Currency } from '../currency'; 3 | import type { PoolData } from '../types'; 4 | 5 | export type AMMRouteSegment = { 6 | from: Currency; 7 | neighbour: Currency; 8 | pool: PoolData; 9 | }; 10 | 11 | export type AMMRoute = AMMRouteSegment[]; 12 | 13 | function neighbours(token: Currency, pools: PoolData[]): AMMRouteSegment[] { 14 | return pools 15 | .map((pool) => { 16 | if (pool.tokenX === token) 17 | return { from: pool.tokenX, neighbour: pool.tokenY, pool }; 18 | if (pool.tokenY === token) 19 | return { from: pool.tokenY, neighbour: pool.tokenX, pool }; 20 | return null; 21 | }) 22 | .filter(isNotNull); 23 | } 24 | 25 | export function resolveAmmRoute( 26 | tokenX: Currency, 27 | tokenY: Currency, 28 | pools: PoolData[] 29 | ): AMMRouteSegment[] { 30 | if (pools.length === 0) { 31 | return []; 32 | } 33 | const visited: { [key: string]: AMMRouteSegment[] } = { 34 | [tokenX]: [], 35 | }; 36 | // contract only support up to 4 segments 37 | for (let i = 0; i < 4; i++) { 38 | const visitedNodes = Object.keys(visited).map((a) => a as Currency); 39 | for (const a of visitedNodes) { 40 | for (const b of neighbours(a, pools)) { 41 | if (b.neighbour === tokenY) { 42 | return [...(visited[a] ?? []), b]; 43 | } 44 | if (visited[b.neighbour] == null) { 45 | visited[b.neighbour] = [...(visited[a] ?? []), b]; 46 | } 47 | } 48 | } 49 | } 50 | return []; 51 | } 52 | 53 | export function resolveAmmRoutes( 54 | tokenX: Currency, 55 | tokenY: Currency, 56 | pools: PoolData[] 57 | ): AMMRouteSegment[][] { 58 | if (pools.length === 0) { 59 | return []; 60 | } 61 | 62 | const allRoutes: AMMRouteSegment[][] = []; 63 | 64 | function findRoutes( 65 | currentToken: Currency, 66 | targetToken: Currency, 67 | currentPath: AMMRouteSegment[], 68 | depth: number 69 | ): void { 70 | // Contract only supports up to 4 segments 71 | if (depth > 4) return; 72 | 73 | const neighborSegments = neighbours(currentToken, pools); 74 | for (const segment of neighborSegments) { 75 | // Avoid cycles by checking if we've already visited this token 76 | if ( 77 | currentPath.some( 78 | (route) => 79 | route.from === segment.neighbour || 80 | route.neighbour === segment.neighbour 81 | ) 82 | ) { 83 | continue; 84 | } 85 | 86 | const newPath = [...currentPath, segment]; 87 | if (segment.neighbour === targetToken) { 88 | allRoutes.push(newPath); 89 | } else { 90 | findRoutes(segment.neighbour, targetToken, newPath, depth + 1); 91 | } 92 | } 93 | } 94 | 95 | findRoutes(tokenX, tokenY, [], 0); 96 | return allRoutes.sort((a, b) => a.length - b.length); 97 | } 98 | -------------------------------------------------------------------------------- /src/utils/arrayHelper.ts: -------------------------------------------------------------------------------- 1 | export type AtLeastOne = readonly [T, ...T[]]; 2 | 3 | export function hasLength(x: T[], length: 0): x is []; 4 | export function hasLength(x: T[], length: 1): x is [T]; 5 | export function hasLength(x: T[], length: 2): x is [T, T]; 6 | export function hasLength(x: T[], length: 3): x is [T, T, T]; 7 | export function hasLength(x: T[], length: 4): x is [T, T, T, T]; 8 | export function hasLength(x: T[], length: 5): x is [T, T, T, T, T]; 9 | export function hasLength(x: T[], length: number): boolean { 10 | return x.length === length; 11 | } 12 | 13 | export function zipObj( 14 | keys: K[], 15 | values: V[] 16 | ): Record { 17 | return Object.fromEntries(keys.map((key, i) => [key, values[i]])) as any; 18 | } 19 | 20 | export function announceAtLeastOne(ary: readonly T[]): AtLeastOne { 21 | return ary as any; 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/fetchData.ts: -------------------------------------------------------------------------------- 1 | import { Currency } from '../currency'; 2 | import type { AddressBalanceResponse } from '@stacks/stacks-blockchain-api-types'; 3 | import { configs } from '../config'; 4 | import { fromEntries, isNotNull } from './utils'; 5 | import type { 6 | AlexSDKResponse, 7 | BackendAPIPriceResponse, 8 | PriceData, 9 | TokenInfo, 10 | } from '../types'; 11 | import { fetchCallReadOnlyFunction } from '@stacks/transactions'; 12 | import { STACKS_MAINNET } from '@stacks/network'; 13 | import { 14 | principalCV, 15 | responseSimpleT, 16 | uintT, 17 | unwrapResponse, 18 | } from 'clarity-codegen'; 19 | 20 | export async function getAlexSDKData(): Promise { 21 | return fetch(configs.SDK_API_HOST) 22 | .then((r): Promise => { 23 | if (r.ok) { 24 | return r.json(); 25 | } 26 | throw new Error('Failed to fetch token mappings'); 27 | }) 28 | .then((x) => { 29 | for (const a of x.pools) { 30 | a.poolId = BigInt(a.poolId); 31 | a.factor = BigInt(a.factor); 32 | } 33 | return x; 34 | }); 35 | } 36 | 37 | export async function getPrices( 38 | mappings: TokenInfo[] 39 | ): Promise { 40 | return fetch(`${configs.BACKEND_API_HOST}/v2/public/token-prices`) 41 | .then((r) => { 42 | if (r.ok) { 43 | return r.json(); 44 | } 45 | throw new Error('Failed to fetch token mappings'); 46 | }) 47 | .then((x: any) => 48 | x.data 49 | .map((a: any): PriceData | null => { 50 | if (a.contract_id === 'STX') { 51 | return { 52 | token: Currency.STX, 53 | price: a.last_price_usd, 54 | }; 55 | } 56 | const token = mappings.find( 57 | (b) => b.underlyingToken.split('::')[0] === a.contract_id 58 | )?.id; 59 | if (token == null) { 60 | return null; 61 | } 62 | return { 63 | token, 64 | price: a.last_price_usd, 65 | }; 66 | }) 67 | .filter(isNotNull) 68 | ); 69 | } 70 | 71 | export async function fetchBalanceForAccount( 72 | stxAddress: string, 73 | tokenMappings: TokenInfo[] 74 | ): Promise> { 75 | const response = await fetch( 76 | `${configs.STACKS_API_HOST}/extended/v1/address/${stxAddress}/balances` 77 | ); 78 | if (!response.ok) { 79 | throw new Error('Failed to fetch account balances'); 80 | } 81 | const balanceData: AddressBalanceResponse = await response.json(); 82 | return fromEntries( 83 | await Promise.all( 84 | tokenMappings.map(async (a) => { 85 | if (a.isRebaseToken) { 86 | // call readonly functions to get the correct balance 87 | const [contractAddr, contractName] = a.underlyingToken 88 | .split('::')[0] 89 | .split('.'); 90 | const response = await fetchCallReadOnlyFunction({ 91 | senderAddress: stxAddress, 92 | contractAddress: contractAddr, 93 | contractName: contractName, 94 | functionName: 'get-balance', 95 | functionArgs: [principalCV(stxAddress)], 96 | network: { 97 | ...STACKS_MAINNET, 98 | client: { 99 | ...STACKS_MAINNET.client, 100 | baseUrl: configs.READONLY_CALL_API_HOST, 101 | }, 102 | }, 103 | }); 104 | const amount = unwrapResponse( 105 | responseSimpleT(uintT).decode(response) 106 | ); 107 | return [ 108 | a.id, 109 | (BigInt(amount) * BigInt(1e8)) / 110 | BigInt(10 ** a.underlyingTokenDecimals), 111 | ]; 112 | } 113 | if (a.id === Currency.STX) { 114 | return [a.id, BigInt(balanceData.stx.balance) * BigInt(100)]; 115 | } 116 | const fungibleToken = 117 | balanceData.fungible_tokens[a.underlyingToken]?.balance; 118 | if (fungibleToken == null) { 119 | return [a.id, BigInt(0)]; 120 | } 121 | return [ 122 | a.id, 123 | (BigInt(fungibleToken) * BigInt(1e8)) / 124 | BigInt(10 ** a.underlyingTokenDecimals), 125 | ]; 126 | }) 127 | ) 128 | ); 129 | } 130 | -------------------------------------------------------------------------------- /src/utils/postConditions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addressToString, 3 | FungibleConditionCode, 4 | parsePrincipalString, 5 | type FungibleComparator, 6 | type PostCondition, 7 | } from '@stacks/transactions'; 8 | import { Currency } from '../currency'; 9 | import type { TokenInfo } from '../types'; 10 | import { configs } from '../config'; 11 | 12 | const fungibleConditionCodeToComparator = ( 13 | code: FungibleConditionCode 14 | ): FungibleComparator => { 15 | const mapping: Record = { 16 | [FungibleConditionCode.Equal]: 'eq', 17 | [FungibleConditionCode.Greater]: 'gt', 18 | [FungibleConditionCode.GreaterEqual]: 'gte', 19 | [FungibleConditionCode.Less]: 'lt', 20 | [FungibleConditionCode.LessEqual]: 'lte', 21 | }; 22 | return mapping[code]; 23 | }; 24 | 25 | export const transferFactory = 26 | (tokenMapping: TokenInfo[]) => 27 | ( 28 | senderAddress: string, 29 | currency: Currency, 30 | amount: bigint, 31 | conditionCode: FungibleConditionCode = FungibleConditionCode.Equal 32 | ): PostCondition[] => { 33 | const mapping = tokenMapping.find((m) => m.id === currency); 34 | if (!mapping) { 35 | throw new Error('Token mapping not found'); 36 | } 37 | const scale = BigInt(10 ** mapping.underlyingTokenDecimals); 38 | const nativeAmount = (amount * BigInt(scale)) / BigInt(1e8); 39 | 40 | // validate sender address 41 | addressToString(parsePrincipalString(senderAddress).address); 42 | 43 | if (currency === Currency.STX) { 44 | return [ 45 | { 46 | type: 'stx-postcondition', 47 | address: senderAddress, 48 | condition: fungibleConditionCodeToComparator(conditionCode), 49 | amount: nativeAmount, 50 | }, 51 | ]; 52 | } 53 | 54 | const AlexVault = `${configs.CONTRACT_DEPLOYER}.amm-vault-v2-01`; 55 | if (senderAddress === AlexVault && mapping.isVaultWrapToken) { 56 | return [ 57 | { 58 | type: 'ft-postcondition', 59 | address: AlexVault, 60 | condition: fungibleConditionCodeToComparator(conditionCode), 61 | amount: amount, 62 | asset: mapping.wrapToken as `${string}.${string}::${string}`, 63 | }, 64 | { 65 | type: 'ft-postcondition', 66 | address: mapping.wrapToken.split('::')[0], 67 | condition: fungibleConditionCodeToComparator(conditionCode), 68 | amount: nativeAmount, 69 | asset: mapping.underlyingToken as `${string}.${string}::${string}`, 70 | }, 71 | ]; 72 | } 73 | 74 | // For rebase tokens, use GreaterEqual with amount 0 75 | const finalConditionCode = mapping.isRebaseToken 76 | ? FungibleConditionCode.GreaterEqual 77 | : conditionCode; 78 | const finalAmount = mapping.isRebaseToken ? BigInt(0) : nativeAmount; 79 | 80 | return [ 81 | { 82 | type: 'ft-postcondition', 83 | address: senderAddress, 84 | condition: fungibleConditionCodeToComparator(finalConditionCode), 85 | amount: finalAmount, 86 | asset: mapping.underlyingToken as `${string}.${string}::${string}`, 87 | }, 88 | ]; 89 | }; 90 | -------------------------------------------------------------------------------- /src/utils/promiseHelpers.ts: -------------------------------------------------------------------------------- 1 | import { zipObj } from './arrayHelper'; 2 | 3 | export async function props>( 4 | inputs: I 5 | ): Promise<{ [K in keyof I]: Awaited }> { 6 | const res = await Promise.all(Object.values(inputs)); 7 | return zipObj(Object.keys(inputs), res) as any; 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/readonlyCallExecutor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | fetchCallReadOnlyFunction, 3 | type ClarityValue, 4 | type ReadOnlyFunctionOptions, 5 | } from '@stacks/transactions'; 6 | import { configs } from '../config'; 7 | import { STACKS_MAINNET } from '@stacks/network'; 8 | import type { 9 | ParameterObjOfDescriptor, 10 | ReadonlyFunctionDescriptor, 11 | ReturnTypeOfDescriptor, 12 | } from 'clarity-codegen'; 13 | import { AlexContracts } from '../generated/smartContract/contracts_Alex'; 14 | 15 | type Contracts = typeof AlexContracts; 16 | 17 | type ReadonlyCallExecutor = ( 18 | options: Pick< 19 | ReadOnlyFunctionOptions, 20 | 'functionArgs' | 'functionName' | 'contractName' | 'contractAddress' 21 | > 22 | ) => Promise; 23 | 24 | const defaultReadonlyCallExecutor: ReadonlyCallExecutor = async (options) => { 25 | return fetchCallReadOnlyFunction({ 26 | ...options, 27 | senderAddress: configs.CONTRACT_DEPLOYER, 28 | network: { 29 | ...STACKS_MAINNET, 30 | client: { 31 | ...STACKS_MAINNET.client, 32 | baseUrl: configs.READONLY_CALL_API_HOST, 33 | }, 34 | }, 35 | }); 36 | }; 37 | 38 | export async function readonlyCall< 39 | T extends keyof Contracts, 40 | F extends keyof Contracts[T], 41 | Descriptor extends Contracts[T][F] 42 | >( 43 | contractName: T, 44 | functionName: F, 45 | args: Descriptor extends ReadonlyFunctionDescriptor 46 | ? ParameterObjOfDescriptor 47 | : never 48 | ): Promise< 49 | Descriptor extends ReadonlyFunctionDescriptor 50 | ? ReturnTypeOfDescriptor 51 | : never 52 | > { 53 | const functionDescriptor = AlexContracts[contractName][ 54 | functionName 55 | ] as any as ReadonlyFunctionDescriptor; 56 | const clarityArgs = functionDescriptor.input.map((arg) => 57 | arg.type.encode(args[arg.name]) 58 | ); 59 | const result = await defaultReadonlyCallExecutor({ 60 | contractName, 61 | functionName: String(functionName), 62 | functionArgs: clarityArgs, 63 | contractAddress: contractName === 'alex-amm-pool-v2-01-get-helper' ? configs.QUOTE_CONTRACT_DEPLOYER : configs.CONTRACT_DEPLOYER, 64 | }); 65 | return functionDescriptor.output.decode(result); 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/sampleResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "pools": [ 3 | { 4 | "tokenX": "age000-governance-token", 5 | "tokenY": "auto-alex-v2", 6 | "factor": 100000000 7 | }, 8 | { 9 | "tokenX": "token-wstx", 10 | "tokenY": "age000-governance-token", 11 | "factor": 100000000 12 | }, 13 | { "tokenX": "token-abtc", "tokenY": "token-wbtc", "factor": 5000000 }, 14 | { "tokenX": "token-wstx", "tokenY": "token-wcorgi", "factor": 100000000 }, 15 | { "tokenX": "token-wstx", "tokenY": "token-wbtc", "factor": 100000000 }, 16 | { "tokenX": "token-wstx", "tokenY": "token-wnycc", "factor": 100000000 }, 17 | { "tokenX": "token-susdt", "tokenY": "token-wxusd", "factor": 5000000 }, 18 | { "tokenX": "token-wstx", "tokenY": "token-wxusd", "factor": 100000000 }, 19 | { "tokenX": "token-wstx", "tokenY": "token-wmia", "factor": 100000000 }, 20 | { "tokenX": "token-abtc", "tokenY": "brc20-ordg", "factor": 100000000 }, 21 | { 22 | "tokenX": "age000-governance-token", 23 | "tokenY": "token-wdiko", 24 | "factor": 100000000 25 | }, 26 | { "tokenX": "token-abtc", "tokenY": "brc20-ornj", "factor": 100000000 }, 27 | { "tokenX": "token-susdt", "tokenY": "brc20-chax", "factor": 100000000 }, 28 | { "tokenX": "token-abtc", "tokenY": "brc20-ormm", "factor": 100000000 }, 29 | { "tokenX": "token-abtc", "tokenY": "token-ssko", "factor": 100000000 }, 30 | { "tokenX": "token-abtc", "tokenY": "brc20-tx20", "factor": 100000000 }, 31 | { "tokenX": "token-abtc", "tokenY": "brc20-db20", "factor": 100000000 }, 32 | { "tokenX": "token-wstx", "tokenY": "token-susdt", "factor": 100000000 }, 33 | { 34 | "tokenX": "age000-governance-token", 35 | "tokenY": "token-wusda", 36 | "factor": 100000000 37 | }, 38 | { 39 | "tokenX": "age000-governance-token", 40 | "tokenY": "token-wgus", 41 | "factor": 100000000 42 | }, 43 | { 44 | "tokenX": "age000-governance-token", 45 | "tokenY": "token-wpepe", 46 | "factor": 100000000 47 | }, 48 | { 49 | "tokenX": "age000-governance-token", 50 | "tokenY": "token-wlong", 51 | "factor": 100000000 52 | }, 53 | { 54 | "tokenX": "age000-governance-token", 55 | "tokenY": "token-wnope", 56 | "factor": 100000000 57 | }, 58 | { 59 | "tokenX": "age000-governance-token", 60 | "tokenY": "token-wmax", 61 | "factor": 100000000 62 | }, 63 | { 64 | "tokenX": "age000-governance-token", 65 | "tokenY": "token-wmega-v2", 66 | "factor": 100000000 67 | }, 68 | { "tokenX": "token-wxusd", "tokenY": "token-wusda", "factor": 5000000 }, 69 | { 70 | "tokenX": "age000-governance-token", 71 | "tokenY": "token-wban", 72 | "factor": 100000000 73 | }, 74 | { 75 | "tokenX": "age000-governance-token", 76 | "tokenY": "token-wslm", 77 | "factor": 100000000 78 | }, 79 | { 80 | "tokenX": "age000-governance-token", 81 | "tokenY": "token-wplay", 82 | "factor": 100000000 83 | }, 84 | { 85 | "tokenX": "age000-governance-token", 86 | "tokenY": "token-whashiko", 87 | "factor": 100000000 88 | }, 89 | { 90 | "tokenX": "age000-governance-token", 91 | "tokenY": "token-wmick", 92 | "factor": 100000000 93 | }, 94 | { "tokenX": "token-abtc", "tokenY": "brc20-trio", "factor": 100000000 }, 95 | { "tokenX": "token-wstx", "tokenY": "token-wlqstx", "factor": 5000000 }, 96 | { 97 | "tokenX": "age000-governance-token", 98 | "tokenY": "token-wwif", 99 | "factor": 100000000 100 | }, 101 | { 102 | "tokenX": "age000-governance-token", 103 | "tokenY": "token-wvibes", 104 | "factor": 100000000 105 | }, 106 | { 107 | "tokenX": "age000-governance-token", 108 | "tokenY": "token-wleo", 109 | "factor": 100000000 110 | } 111 | ], 112 | "tokens": [ 113 | { 114 | "id": "token-wstx", 115 | "name": "STX", 116 | "icon": "https://images.ctfassets.net/frwmwlognk87/4gSg3vYkO4Vg5XXGJJc70W/9aa79522b1118fa14506375fe9fdcfaf/STX.svg", 117 | "wrapTokenDecimals": 8, 118 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wstx-v2::wstx", 119 | "underlyingToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.token-wstx::wstx", 120 | "underlyingTokenDecimals": 6 121 | }, 122 | { 123 | "id": "token-wbtc", 124 | "name": "xBTC", 125 | "icon": "https://images.ctfassets.net/frwmwlognk87/1DVlt1M6hFOhHZG3XOjxF6/c428b9ee69d3ce9dc32844606ea9831b/XBTC.svg", 126 | "wrapTokenDecimals": 8, 127 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wxbtc::wxbtc", 128 | "underlyingToken": "SP3DX3H4FEYZJZ586MFBS25ZW3HZDMEW92260R2PR.Wrapped-Bitcoin::wrapped-bitcoin", 129 | "underlyingTokenDecimals": 8 130 | }, 131 | { 132 | "id": "age000-governance-token", 133 | "name": "ALEX", 134 | "icon": "https://images.ctfassets.net/frwmwlognk87/66AVnxb2drR9ofypuV3y2r/1f223e16a7236dfa0ea4b8e0259c35c8/alex.svg", 135 | "wrapTokenDecimals": 8, 136 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex::alex", 137 | "underlyingToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex::alex", 138 | "underlyingTokenDecimals": 8 139 | }, 140 | { 141 | "id": "token-wxusd", 142 | "name": "xUSD", 143 | "icon": "https://images.ctfassets.net/frwmwlognk87/7a2ViAHl6EiTiVep0o7GCy/34d12e049b000ba0ca67760e3e9b169f/type_xUSD.svg", 144 | "wrapTokenDecimals": 8, 145 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wxusd::wxusd", 146 | "underlyingToken": "SP2TZK01NKDC89J6TA56SA47SDF7RTHYEQ79AAB9A.Wrapped-USD::wrapped-usd", 147 | "underlyingTokenDecimals": 8 148 | }, 149 | { 150 | "id": "token-wmia", 151 | "name": "MIA", 152 | "icon": "https://images.ctfassets.net/frwmwlognk87/1aM0cFGHahiadjukDbYcYx/d3b64df0cadc28454fbfecbfeac7d8a2/mia.svg", 153 | "wrapTokenDecimals": 8, 154 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wmia::wmia", 155 | "underlyingToken": "SP1H1733V5MZ3SZ9XRW9FKYGEZT0JDGEB8Y634C7R.miamicoin-token-v2::miamicoin", 156 | "underlyingTokenDecimals": 6 157 | }, 158 | { 159 | "id": "token-wnycc", 160 | "name": "NYC", 161 | "icon": "https://images.ctfassets.net/frwmwlognk87/4UTCc0UvDe5wqtaWxF9fsl/94ca6ae134d9368664aa91a8b4026ee0/nyc-reexport.svg", 162 | "wrapTokenDecimals": 8, 163 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wnyc::wnyc", 164 | "underlyingToken": "SPSCWDV3RKV5ZRN1FQD84YE1NQFEDJ9R1F4DYQ11.newyorkcitycoin-token-v2::newyorkcitycoin", 165 | "underlyingTokenDecimals": 6 166 | }, 167 | { 168 | "id": "token-wcorgi", 169 | "name": "WELSH", 170 | "icon": "https://images.ctfassets.net/frwmwlognk87/0CWd4L2Gx6eqqbfOh98Tr/b3a2949801cf21399bcf84dafeea801f/welsh_tokenlogo.png", 171 | "wrapTokenDecimals": 8, 172 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wcorgi::wcorgi", 173 | "underlyingToken": "SP3NE50GEXFG9SZGTT51P40X2CKYSZ5CC4ZTZ7A2G.welshcorgicoin-token::welshcorgicoin", 174 | "underlyingTokenDecimals": 6 175 | }, 176 | { 177 | "id": "token-susdt", 178 | "name": "sUSDT", 179 | "icon": "https://images.ctfassets.net/frwmwlognk87/2eP1ea7DID9FetHiEOTaO7/8b594dcf7d81bfaba42ad6e6a0194275/token.svg", 180 | "wrapTokenDecimals": 8, 181 | "wrapToken": "SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK.token-susdt::bridged-usdt", 182 | "underlyingToken": "SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK.token-susdt::bridged-usdt", 183 | "underlyingTokenDecimals": 8 184 | }, 185 | { 186 | "id": "token-wdiko", 187 | "name": "DIKO", 188 | "icon": "https://images.ctfassets.net/frwmwlognk87/2PdRx3UFRwpDcUvHM9hOA/693d6354e7752a166d3304f8b856e605/diko.svg", 189 | "wrapTokenDecimals": 8, 190 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wdiko::wdiko", 191 | "underlyingToken": "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-token::diko", 192 | "underlyingTokenDecimals": 6 193 | }, 194 | { 195 | "id": "token-wusda", 196 | "name": "USDA", 197 | "icon": "https://images.ctfassets.net/frwmwlognk87/CD2wPHj7KYZKW1oO8RoX1/0df1f7f69bd5e1daa9760ce598ed8343/USDA.svg", 198 | "wrapTokenDecimals": 8, 199 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wusda::wusda", 200 | "underlyingToken": "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.usda-token::usda", 201 | "underlyingTokenDecimals": 6 202 | }, 203 | { 204 | "id": "auto-alex-v2", 205 | "name": "atALEXv2", 206 | "icon": "https://images.ctfassets.net/frwmwlognk87/3D9PGDItwj9ceb2t1ocpHu/60a99f2dbc07df118f280a75aaf6d843/type_atALEXv2.png", 207 | "wrapTokenDecimals": 8, 208 | "wrapToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.auto-alex-v2::auto-alex-v2", 209 | "underlyingToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.auto-alex-v2::auto-alex-v2", 210 | "underlyingTokenDecimals": 8 211 | }, 212 | { 213 | "id": "brc20-chax", 214 | "name": "CHAX", 215 | "icon": "https://images.ctfassets.net/frwmwlognk87/G9masn8HHScnhhlFp5HaP/ca601a578cb6417960688f53f83cd470/logo.svg", 216 | "wrapTokenDecimals": 8, 217 | "wrapToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-chax::brc20-chax", 218 | "underlyingToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-chax::brc20-chax", 219 | "underlyingTokenDecimals": 8 220 | }, 221 | { 222 | "id": "brc20-ordg", 223 | "name": "ORDG", 224 | "icon": "https://images.ctfassets.net/frwmwlognk87/4IHVeMevpesMr1i38sycfz/53c669810394151c67733e24f2fa5651/ORDG_icon_large.png", 225 | "wrapTokenDecimals": 8, 226 | "wrapToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-ordg::brc20-ordg", 227 | "underlyingToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-ordg::brc20-ordg", 228 | "underlyingTokenDecimals": 8 229 | }, 230 | { 231 | "id": "brc20-ormm", 232 | "name": "ORMM", 233 | "icon": "https://images.ctfassets.net/frwmwlognk87/2RqydiGw6WIAUaDnq2oSbt/82157b3831ef2ddf1e9c4bf060c49947/ORMM-2.png", 234 | "wrapTokenDecimals": 8, 235 | "wrapToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-ormm::brc20-ormm", 236 | "underlyingToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-ormm::brc20-ormm", 237 | "underlyingTokenDecimals": 8 238 | }, 239 | { 240 | "id": "brc20-ornj", 241 | "name": "ORNJ", 242 | "icon": "https://images.ctfassets.net/frwmwlognk87/3UZiZVSZpcFSYqw7csCkPb/9359ee6d914db78aff7cf06cbf5a35e1/Orange_Pill_Avatar_3D_Round.png", 243 | "wrapTokenDecimals": 8, 244 | "wrapToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-ornj::brc20-ornj", 245 | "underlyingToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-ornj::brc20-ornj", 246 | "underlyingTokenDecimals": 8 247 | }, 248 | { 249 | "id": "brc20-db20", 250 | "name": "$B20", 251 | "icon": "https://images.ctfassets.net/frwmwlognk87/6ieKOUbDlX4Be6v0Qnbjwy/f1af270b312827ad378e3e80baea393b/B20_Token_round.svg", 252 | "wrapTokenDecimals": 8, 253 | "wrapToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-db20::brc20-db20", 254 | "underlyingToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-db20::brc20-db20", 255 | "underlyingTokenDecimals": 8 256 | }, 257 | { 258 | "id": "token-ssko", 259 | "name": "SKO", 260 | "icon": "https://images.ctfassets.net/frwmwlognk87/75I14zAh81cFnJmlqBNoeX/68dcb6e031732344bad8b5cba9b7e36d/SKO-Sugar_token.png", 261 | "wrapTokenDecimals": 8, 262 | "wrapToken": "SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK.token-ssko::bridged-sko", 263 | "underlyingToken": "SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK.token-ssko::bridged-sko", 264 | "underlyingTokenDecimals": 8 265 | }, 266 | { 267 | "id": "brc20-tx20", 268 | "name": "TX20", 269 | "icon": "https://images.ctfassets.net/frwmwlognk87/4tnn7N5Hoagib5CKHJ2ax9/3ae01b1d68d4b8e98f899550c161e7c5/Trex20_Token_Ticker.png", 270 | "wrapTokenDecimals": 8, 271 | "wrapToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-tx20::brc20-tx20", 272 | "underlyingToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-tx20::brc20-tx20", 273 | "underlyingTokenDecimals": 8 274 | }, 275 | { 276 | "id": "token-wgus", 277 | "name": "GUS", 278 | "icon": "https://images.ctfassets.net/frwmwlognk87/4BsawO40u92SqXTE4f8y3S/1c96241d3b5590e8e419cb4e931db3d6/GUS_Logo_1.svg", 279 | "wrapTokenDecimals": 8, 280 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wgus::wgus", 281 | "underlyingToken": "SP1JFFSYTSH7VBM54K29ZFS9H4SVB67EA8VT2MYJ9.gus-token::gus", 282 | "underlyingTokenDecimals": 6 283 | }, 284 | { 285 | "id": "token-wpepe", 286 | "name": "PEPE", 287 | "icon": "https://images.ctfassets.net/frwmwlognk87/7JrWwzni1RfxnEKBHcMJYF/ebb762191b18e074850d59487cb8ad0a/photo_2024-03-19_00-02-32_1.png", 288 | "wrapTokenDecimals": 8, 289 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wpepe::wpepe", 290 | "underlyingToken": "SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275.tokensoft-token-v4k68639zxz::tokensoft-token", 291 | "underlyingTokenDecimals": 3 292 | }, 293 | { 294 | "id": "token-wlong", 295 | "name": "LONG", 296 | "icon": "https://images.ctfassets.net/frwmwlognk87/4gKhLHw09nsWjmPlufTjmT/d5432569911d82c3052755b77e0f64dc/LONGcoin-image-2.png", 297 | "wrapTokenDecimals": 8, 298 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wlong::wlong", 299 | "underlyingToken": "SP265WBWD4NH7TVPYQTVD23X3607NNK4484DTXQZ3.longcoin::longcoin", 300 | "underlyingTokenDecimals": 6 301 | }, 302 | { 303 | "id": "token-wnope", 304 | "name": "NOT", 305 | "icon": "https://images.ctfassets.net/frwmwlognk87/43mQaoU8JsfWr5HPWQZN5b/7f56c9d5837e03c3d4535ba073ba1c64/image_1819.png", 306 | "wrapTokenDecimals": 8, 307 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wnot::wnot", 308 | "underlyingToken": "SP32AEEF6WW5Y0NMJ1S8SBSZDAY8R5J32NBZFPKKZ.nope::NOT", 309 | "underlyingTokenDecimals": 0 310 | }, 311 | { 312 | "id": "token-wmax", 313 | "name": "MAX", 314 | "icon": "https://images.ctfassets.net/frwmwlognk87/HWVqVVCcoRu1g2WfgeqHQ/09f55ceb495af673b4b15132289d25c9/MAX_logo.svg", 315 | "wrapTokenDecimals": 8, 316 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wmax::wmax", 317 | "underlyingToken": "SP7V1SE7EA3ZG3QTWSBA2AAG8SRHEYJ06EBBD1J2.max-token::max", 318 | "underlyingTokenDecimals": 6 319 | }, 320 | { 321 | "id": "token-wmega-v2", 322 | "name": "MEGA", 323 | "icon": "https://images.ctfassets.net/frwmwlognk87/2WN3R26iNr3ECw81NzQ30s/edb2a99f50a0752f8e0213da299c90e8/Round.png", 324 | "wrapTokenDecimals": 8, 325 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wmega::wmega", 326 | "underlyingToken": "SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.mega::mega", 327 | "underlyingTokenDecimals": 2 328 | }, 329 | { 330 | "id": "token-abtc", 331 | "name": "aBTC", 332 | "icon": "https://images.ctfassets.net/frwmwlognk87/GYpzIAYIiFLOGQtfAUbGE/37ba1274539f0ee150bc70141e37cc10/aBTC.svg", 333 | "wrapTokenDecimals": 8, 334 | "wrapToken": "SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK.token-abtc::bridged-btc", 335 | "underlyingToken": "SP2XD7417HGPRTREMKF748VNEQPDRR0RMANB7X1NK.token-abtc::bridged-btc", 336 | "underlyingTokenDecimals": 8 337 | }, 338 | { 339 | "id": "token-wban", 340 | "name": "BANANA", 341 | "icon": "https://images.ctfassets.net/frwmwlognk87/1hmx3vgBUjCuf5TdC27sWu/fd10565fa2549c406cdefb84495f912e/BANANA.svg", 342 | "wrapTokenDecimals": 8, 343 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wban::wban", 344 | "underlyingToken": "SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.btc-monkeys-bananas::BANANA", 345 | "underlyingTokenDecimals": 6 346 | }, 347 | { 348 | "id": "token-wslm", 349 | "name": "SLIME", 350 | "icon": "https://images.ctfassets.net/frwmwlognk87/2V50JwTfywuU2liZlWYdqg/4b9bd5306dad78ab5b044f000d863794/Untitled.png", 351 | "wrapTokenDecimals": 8, 352 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wslm::wslm", 353 | "underlyingToken": "SP125J1ADVYWGWB9NQRCVGKYAG73R17ZNMV17XEJ7.slime-token::SLIME", 354 | "underlyingTokenDecimals": 6 355 | }, 356 | { 357 | "id": "token-wplay", 358 | "name": "PLAY", 359 | "icon": "https://images.ctfassets.net/frwmwlognk87/4ihcL3Vkd1jnvQoYc7hBea/1c021b828ab72f7a31a80d16ebdfd4a6/play_logo.png", 360 | "wrapTokenDecimals": 8, 361 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wplay::wplay", 362 | "underlyingToken": "SP1PW804599BZ46B4A0FYH86ED26XPJA7SFYNK1XS.play::play", 363 | "underlyingTokenDecimals": 6 364 | }, 365 | { 366 | "id": "token-whashiko", 367 | "name": "HASHIKO", 368 | "icon": "https://images.ctfassets.net/frwmwlognk87/1yyegtAtYFdTE17bDEdWR6/119b598c4515a6850b5265de460ebd7d/photo_2024-06-11_18.33.13.jpeg", 369 | "wrapTokenDecimals": 8, 370 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-whashiko::token-whashiko", 371 | "underlyingToken": "SP28NB976TJHHGF4218KT194NPWP9N1X3WY516Z1P.Hashiko::hashiko", 372 | "underlyingTokenDecimals": 0 373 | }, 374 | { 375 | "id": "token-wmick", 376 | "name": "MICK", 377 | "icon": "https://images.ctfassets.net/frwmwlognk87/7ta4KUC6H7LP81Tiv0unki/e09daa06a81e52a1bf336745651ea6d6/coin.png", 378 | "wrapTokenDecimals": 8, 379 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wmick::wmick", 380 | "underlyingToken": "SP2Y8T3TR3FKH3Y2FPZVNQAEKNJXKWVS4RVQF48JE.stakemouse::stakemouse", 381 | "underlyingTokenDecimals": 6 382 | }, 383 | { 384 | "id": "brc20-trio", 385 | "name": "TRIO", 386 | "icon": "https://images.ctfassets.net/frwmwlognk87/4RypjV9kQxyOoRAflnWfT/4fcca8ff22753ad380ceef66f6edeadc/Trio-Token-Logo.png", 387 | "wrapTokenDecimals": 8, 388 | "wrapToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-trio::brc20-trio", 389 | "underlyingToken": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-trio::brc20-trio", 390 | "underlyingTokenDecimals": 8 391 | }, 392 | { 393 | "id": "token-wlqstx", 394 | "name": "LiSTX", 395 | "icon": "https://images.ctfassets.net/frwmwlognk87/4iZlQpJZpSJCAEhMqEhOCo/940dcaef06dbb64bd878bb75179d5cd2/token.svg", 396 | "wrapTokenDecimals": 8, 397 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wlqstx-v3::wlqstx", 398 | "underlyingToken": "SM26NBC8SFHNW4P1Y4DFH27974P56WN86C92HPEHH.token-lqstx::lqstx", 399 | "underlyingTokenDecimals": 6 400 | }, 401 | { 402 | "id": "token-wwif", 403 | "name": "WIF", 404 | "icon": "https://images.ctfassets.net/frwmwlognk87/3lxvEmZJz6Lc0uJk9qEuHp/8d4afd30713cbb5811da14d765ffeddc/WIFBTC.svg", 405 | "wrapTokenDecimals": 8, 406 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wwif::wwif", 407 | "underlyingToken": "SP3WPNAEBYMX06RQNNYTH5PTJ1FRGX5A13ZZMZ01D.dogwifhat-token::wif", 408 | "underlyingTokenDecimals": 6 409 | }, 410 | { 411 | "id": "token-wvibes", 412 | "name": "VIBES", 413 | "icon": "https://images.ctfassets.net/frwmwlognk87/1DpuZEVOzSh2eIV84Qo1dk/46787c8c4a92b7f29c73149b18213bcc/image.png", 414 | "wrapTokenDecimals": 8, 415 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wvibes::wvibes", 416 | "underlyingToken": "SP27BB1Y2DGSXZHS7G9YHKTSH6KQ6BD3QG0AN3CR9.vibes-token::vibes-token", 417 | "underlyingTokenDecimals": 8 418 | }, 419 | { 420 | "id": "token-wleo", 421 | "name": "LEO", 422 | "icon": "https://images.ctfassets.net/frwmwlognk87/7FbJB0n9quSMYi8Pa3aOzi/09e5bb3d955bd45e9be9b5154a2967c3/ltc-v1.png", 423 | "wrapTokenDecimals": 8, 424 | "wrapToken": "SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wleo::wleo", 425 | "underlyingToken": "SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token::leo", 426 | "underlyingTokenDecimals": 6 427 | } 428 | ] 429 | } 430 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | export function isNotNull(input: T | undefined | null): input is T { 2 | return input != null; 3 | } 4 | 5 | export function assertNever(x: never): never { 6 | throw new Error('Unexpected object: ' + x); 7 | } 8 | 9 | export function fromEntries( 10 | entries: [K, V][] 11 | ): Record { 12 | return entries.reduce((acc, [key, value]) => { 13 | acc[key] = value; 14 | return acc; 15 | }, {} as Record); 16 | } 17 | -------------------------------------------------------------------------------- /test/alexSDK.mock-exceptions.test.ts: -------------------------------------------------------------------------------- 1 | import { AlexSDK, Currency } from '../src'; 2 | import * as ammRouteResolver from '../src/utils/ammRouteResolver'; 3 | import { assertNever } from '../src/utils/utils'; 4 | import { configs } from '../src/config'; 5 | 6 | const sdk = new AlexSDK(); 7 | 8 | const tokenAlex = 'age000-governance-token' as Currency; 9 | const tokenWUSDA = 'token-wusda' as Currency; 10 | 11 | const dummyRoute = ['TokenA', 'TokenB', 'TokenC', 'TokenD', 'TokenE', 'TokenF']; 12 | jest.mock('../src/utils/ammRouteResolver', () => ({ 13 | resolveAmmRoute: jest.fn(async () => dummyRoute), 14 | })); 15 | 16 | describe('AlexSDK - mock exceptions', () => { 17 | it('Attempt to Get Fee Rate with more than 4 pools in route', async () => { 18 | expect(jest.isMockFunction(ammRouteResolver.resolveAmmRoute)).toBeTruthy(); 19 | await expect(sdk.getFeeRate(tokenAlex, Currency.STX)).rejects.toThrow( 20 | 'Too many AMM pools in route' 21 | ); 22 | }, 10000); 23 | 24 | it('Attempt to getAmountTo with more than 4 pools in route', async () => { 25 | await expect( 26 | sdk.getAmountTo(Currency.STX, BigInt(2) * BigInt(1e8), tokenWUSDA) 27 | ).rejects.toThrow('Too many AMM pools in route'); 28 | }, 10000); 29 | 30 | it('Attempt to run swap with more than 4 pools in route', async () => { 31 | await expect( 32 | sdk.runSwap( 33 | configs.CONTRACT_DEPLOYER, 34 | tokenAlex, 35 | tokenWUSDA, 36 | BigInt(2) * BigInt(1e8), 37 | BigInt(0) 38 | ) 39 | ).rejects.toThrow('Too many AMM pools in route'); 40 | }, 10000); 41 | 42 | it('Attempt assertNever to throw unexpected object', () => { 43 | const unexpectedObject = '' as never; 44 | expect(() => assertNever(unexpectedObject)).toThrowError( 45 | 'Unexpected object: ' + unexpectedObject 46 | ); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/alexSDK.mock-externals.test.ts: -------------------------------------------------------------------------------- 1 | import { AlexSDK, Currency, TokenInfo } from '../src'; 2 | import fetchMock from 'fetch-mock'; 3 | import { configs } from '../src/config'; 4 | import { fetchBalanceForAccount, getPrices } from '../src/utils/fetchData'; 5 | import { transferFactory } from '../src/utils/postConditions'; 6 | 7 | const sdk = new AlexSDK(); 8 | 9 | const tokenAlex = 'age000-governance-token' as Currency; 10 | const tokenWUSDA = 'token-wusda' as Currency; 11 | 12 | const tokenMappings: TokenInfo[] = [ 13 | { 14 | id: 'token-x' as Currency, 15 | name: 'Token x', 16 | icon: 'icon-x', 17 | wrapToken: 'wrap-token-x', 18 | wrapTokenDecimals: 8, 19 | underlyingToken: 'underlying-token-x', 20 | underlyingTokenDecimals: 8, 21 | isRebaseToken: false, 22 | isVaultWrapToken: false, 23 | }, 24 | ]; 25 | 26 | const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; 27 | 28 | describe('AlexSDK - mock externals - SDK_API_HOST - BACKEND_API_HOST - STACKS_API_HOST (Internal Server Error)', () => { 29 | beforeEach(() => { 30 | fetchMock.get(configs.SDK_API_HOST, 500); 31 | fetchMock.get(`${configs.BACKEND_API_HOST}/v2/public/token-prices`, 500); 32 | fetchMock.get( 33 | `${configs.STACKS_API_HOST}/extended/v1/address/${stxAddress}/balances`, 34 | 500 35 | ); 36 | }); 37 | afterEach(() => { 38 | fetchMock.restore(); 39 | }); 40 | 41 | it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { 42 | await expect(sdk.getLatestPrices()).rejects.toThrow( 43 | 'Failed to fetch token mappings' 44 | ); 45 | }); 46 | it('Attempt to Get Fee with incorrect Alex SDK Data', async () => { 47 | await expect(sdk.getFeeRate(tokenAlex, Currency.STX)).rejects.toThrow( 48 | 'Failed to fetch token mappings' 49 | ); 50 | }); 51 | 52 | it('Attempt to Get Router with incorrect Alex SDK Data', async () => { 53 | await expect(sdk.getRouter(tokenAlex, Currency.STX)).rejects.toThrow( 54 | 'Failed to fetch token mappings' 55 | ); 56 | }); 57 | 58 | it('Attempt to Get Amount with incorrect Alex SDK Data', async () => { 59 | await expect( 60 | sdk.getAmountTo(Currency.STX, BigInt(2) * BigInt(1e8), tokenWUSDA) 61 | ).rejects.toThrow('Failed to fetch token mappings'); 62 | }); 63 | 64 | it('Attempt to Run Swap with incorrect Alex SDK Data', async () => { 65 | await expect( 66 | sdk.runSwap( 67 | configs.CONTRACT_DEPLOYER, 68 | tokenAlex, 69 | tokenWUSDA, 70 | BigInt(2) * BigInt(1e8), 71 | BigInt(0) 72 | ) 73 | ).rejects.toThrow('Failed to fetch token mappings'); 74 | }); 75 | 76 | it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { 77 | await expect(sdk.getLatestPrices()).rejects.toThrow( 78 | 'Failed to fetch token mappings' 79 | ); 80 | }); 81 | 82 | it('Attempt to Get Balances with incorrect Alex SDK Data', async () => { 83 | const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; 84 | await expect(sdk.getBalances(stxAddress)).rejects.toThrow( 85 | 'Failed to fetch token mappings' 86 | ); 87 | }); 88 | 89 | it('Attempt to Fetch Swappable Currency with incorrect Alex SDK Data', async () => { 90 | await expect(sdk.fetchSwappableCurrency()).rejects.toThrow( 91 | 'Failed to fetch token mappings' 92 | ); 93 | }); 94 | 95 | it('Attempt to get token prices with incorrect data', async () => { 96 | await expect(getPrices(tokenMappings)).rejects.toThrow( 97 | 'Failed to fetch token mappings' 98 | ); 99 | expect( 100 | fetchMock.calls(`${configs.BACKEND_API_HOST}/v2/public/token-prices`) 101 | .length 102 | ).toBe(1); 103 | }); 104 | 105 | it('Attempt to Get Balances with incorrect data', async () => { 106 | await expect( 107 | fetchBalanceForAccount(stxAddress, tokenMappings) 108 | ).rejects.toThrow('Failed to fetch account balances'); 109 | }); 110 | }); 111 | describe('AlexSDK - mock externals - SDK_API_HOST - BACKEND_API_HOST - STACKS_API_HOST (Gateway Timeout)', () => { 112 | beforeEach(() => { 113 | fetchMock.get(configs.SDK_API_HOST, 504); 114 | fetchMock.get(`${configs.BACKEND_API_HOST}/v2/public/token-prices`, 504); 115 | fetchMock.get( 116 | `${configs.STACKS_API_HOST}/extended/v1/address/${stxAddress}/balances`, 117 | 504 118 | ); 119 | }); 120 | afterEach(() => { 121 | fetchMock.restore(); 122 | }); 123 | 124 | it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { 125 | await expect(sdk.getLatestPrices()).rejects.toThrow( 126 | 'Failed to fetch token mappings' 127 | ); 128 | }); 129 | it('Attempt to Get Fee with incorrect Alex SDK Data', async () => { 130 | await expect(sdk.getFeeRate(tokenAlex, Currency.STX)).rejects.toThrow( 131 | 'Failed to fetch token mappings' 132 | ); 133 | }); 134 | 135 | it('Attempt to Get Router with incorrect Alex SDK Data', async () => { 136 | await expect(sdk.getRouter(tokenAlex, Currency.STX)).rejects.toThrow( 137 | 'Failed to fetch token mappings' 138 | ); 139 | }); 140 | 141 | it('Attempt to Get Amount with incorrect Alex SDK Data', async () => { 142 | await expect( 143 | sdk.getAmountTo(Currency.STX, BigInt(2) * BigInt(1e8), tokenWUSDA) 144 | ).rejects.toThrow('Failed to fetch token mappings'); 145 | }); 146 | 147 | it('Attempt to Run Swap with incorrect Alex SDK Data', async () => { 148 | await expect( 149 | sdk.runSwap( 150 | configs.CONTRACT_DEPLOYER, 151 | tokenAlex, 152 | tokenWUSDA, 153 | BigInt(2) * BigInt(1e8), 154 | BigInt(0) 155 | ) 156 | ).rejects.toThrow('Failed to fetch token mappings'); 157 | }); 158 | 159 | it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { 160 | await expect(sdk.getLatestPrices()).rejects.toThrow( 161 | 'Failed to fetch token mappings' 162 | ); 163 | }); 164 | 165 | it('Attempt to Get Balances with incorrect Alex SDK Data', async () => { 166 | const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; 167 | await expect(sdk.getBalances(stxAddress)).rejects.toThrow( 168 | 'Failed to fetch token mappings' 169 | ); 170 | }); 171 | 172 | it('Attempt to Fetch Swappable Currency with incorrect Alex SDK Data', async () => { 173 | await expect(sdk.fetchSwappableCurrency()).rejects.toThrow( 174 | 'Failed to fetch token mappings' 175 | ); 176 | }); 177 | 178 | it('Attempt to get token prices with incorrect data', async () => { 179 | await expect(getPrices(tokenMappings)).rejects.toThrow( 180 | 'Failed to fetch token mappings' 181 | ); 182 | expect( 183 | fetchMock.calls(`${configs.BACKEND_API_HOST}/v2/public/token-prices`) 184 | .length 185 | ).toBe(1); 186 | }); 187 | 188 | it('Attempt to Get Balances with incorrect data', async () => { 189 | await expect( 190 | fetchBalanceForAccount(stxAddress, tokenMappings) 191 | ).rejects.toThrow('Failed to fetch account balances'); 192 | }); 193 | }); 194 | describe('AlexSDK - mock externals - SDK_API_HOST - BACKEND_API_HOST - STACKS_API_HOST (Not Found)', () => { 195 | beforeEach(() => { 196 | fetchMock.get(configs.SDK_API_HOST, 404); 197 | fetchMock.get(`${configs.BACKEND_API_HOST}/v2/public/token-prices`, 404); 198 | fetchMock.get( 199 | `${configs.STACKS_API_HOST}/extended/v1/address/${stxAddress}/balances`, 200 | 404 201 | ); 202 | }); 203 | afterEach(() => { 204 | fetchMock.restore(); 205 | }); 206 | 207 | it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { 208 | await expect(sdk.getLatestPrices()).rejects.toThrow( 209 | 'Failed to fetch token mappings' 210 | ); 211 | }); 212 | it('Attempt to Get Fee with incorrect Alex SDK Data', async () => { 213 | await expect(sdk.getFeeRate(tokenAlex, Currency.STX)).rejects.toThrow( 214 | 'Failed to fetch token mappings' 215 | ); 216 | }); 217 | 218 | it('Attempt to Get Router with incorrect Alex SDK Data', async () => { 219 | await expect(sdk.getRouter(tokenAlex, Currency.STX)).rejects.toThrow( 220 | 'Failed to fetch token mappings' 221 | ); 222 | }); 223 | 224 | it('Attempt to Get Amount with incorrect Alex SDK Data', async () => { 225 | await expect( 226 | sdk.getAmountTo(Currency.STX, BigInt(2) * BigInt(1e8), tokenWUSDA) 227 | ).rejects.toThrow('Failed to fetch token mappings'); 228 | }); 229 | 230 | it('Attempt to Run Swap with incorrect Alex SDK Data', async () => { 231 | await expect( 232 | sdk.runSwap( 233 | configs.CONTRACT_DEPLOYER, 234 | tokenAlex, 235 | tokenWUSDA, 236 | BigInt(2) * BigInt(1e8), 237 | BigInt(0) 238 | ) 239 | ).rejects.toThrow('Failed to fetch token mappings'); 240 | }); 241 | 242 | it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { 243 | await expect(sdk.getLatestPrices()).rejects.toThrow( 244 | 'Failed to fetch token mappings' 245 | ); 246 | }); 247 | 248 | it('Attempt to Get Balances with incorrect Alex SDK Data', async () => { 249 | const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; 250 | await expect(sdk.getBalances(stxAddress)).rejects.toThrow( 251 | 'Failed to fetch token mappings' 252 | ); 253 | }); 254 | 255 | it('Attempt to Fetch Swappable Currency with incorrect Alex SDK Data', async () => { 256 | await expect(sdk.fetchSwappableCurrency()).rejects.toThrow( 257 | 'Failed to fetch token mappings' 258 | ); 259 | }); 260 | 261 | it('Attempt to get token prices with incorrect data', async () => { 262 | await expect(getPrices(tokenMappings)).rejects.toThrow( 263 | 'Failed to fetch token mappings' 264 | ); 265 | expect( 266 | fetchMock.calls(`${configs.BACKEND_API_HOST}/v2/public/token-prices`) 267 | .length 268 | ).toBe(1); 269 | }); 270 | 271 | it('Attempt to Get Balances with incorrect data', async () => { 272 | await expect( 273 | fetchBalanceForAccount(stxAddress, tokenMappings) 274 | ).rejects.toThrow('Failed to fetch account balances'); 275 | }); 276 | }); 277 | describe('Transfer Factory', () => { 278 | it('Throws error in Transfer Factory', () => { 279 | const transfer = transferFactory(tokenMappings); 280 | expect(() => transfer(stxAddress, tokenAlex, BigInt(1000))).toThrow( 281 | 'Token mapping not found' 282 | ); 283 | }); 284 | }); 285 | -------------------------------------------------------------------------------- /test/alexSDK.mock-helpers.test.ts: -------------------------------------------------------------------------------- 1 | import { AlexSDK, Currency } from '../src'; 2 | import * as FeeHelper from '../src/helpers/FeeHelper'; 3 | import * as RouteHelper from '../src/helpers/RouteHelper'; 4 | import * as RateHelper from '../src/helpers/RateHelper'; 5 | import * as SwapHelper from '../src/helpers/SwapHelper'; 6 | import * as fetchData from '../src/utils/fetchData'; 7 | import * as ammRouteResolver from '../src/utils/ammRouteResolver'; 8 | import { configs } from '../src/config'; 9 | // @ts-ignore 10 | import { 11 | dummyAlexSDKData, 12 | dummyAmmRoute, 13 | dummyBalances, 14 | dummyCurrencies, 15 | dummyFee, 16 | dummyRate, 17 | dummyTx, 18 | parsedDummyPrices, 19 | dummyTokenA, 20 | dummyTokenB, 21 | dummyFactorA, 22 | dummyFactorB, 23 | dummyTokenC, 24 | DUMMY_DEPLOYER, 25 | } from './mock-data/alexSDKMockResponses'; 26 | import { cvToValue } from '@stacks/transactions'; 27 | 28 | const sdk = new AlexSDK(); 29 | 30 | const tokenAlex = 'age000-governance-token' as Currency; 31 | const tokenWUSDA = 'token-wusda' as Currency; 32 | 33 | // Mocked helpers and utilities for testing SDK functions 34 | jest.mock('../src/helpers/FeeHelper', () => ({ 35 | getLiquidityProviderFee: jest.fn(async () => dummyFee), 36 | })); 37 | jest.mock('../src/helpers/RouteHelper', () => ({ 38 | getAllPossibleRoute: jest.fn(async () => [dummyAmmRoute, dummyAmmRoute]), 39 | })); 40 | jest.mock('../src/helpers/RateHelper', () => ({ 41 | getYAmountFromXAmount: jest.fn(async () => dummyRate), 42 | })); 43 | jest.mock('../src/helpers/SwapHelper', () => { 44 | const originalModule = jest.requireActual('../src/helpers/SwapHelper'); 45 | return { 46 | runSpot: jest.fn(async (deployer, ...args) => { 47 | if (deployer !== configs.CONTRACT_DEPLOYER) { 48 | return dummyTx; 49 | } 50 | return await originalModule.runSpot(deployer, ...args); 51 | }), 52 | }; 53 | }); 54 | jest.mock('../src/utils/fetchData', () => { 55 | const originalModule = jest.requireActual('../src/utils/fetchData'); 56 | const { dummyPrices, dummyCurrencies } = jest.requireActual( 57 | './mock-data/alexSDKMockResponses' 58 | ); 59 | return { 60 | __esModule: true, 61 | ...originalModule, 62 | getPrices: jest 63 | .fn() 64 | .mockReturnValueOnce(dummyPrices) 65 | .mockReturnValueOnce(originalModule.getPrices(dummyCurrencies)), 66 | fetchBalanceForAccount: jest.fn(async () => dummyBalances), 67 | getAlexSDKData: jest.fn(async () => dummyAlexSDKData), 68 | }; 69 | }); 70 | jest.mock('../src/utils/ammRouteResolver', () => { 71 | const originalModule = jest.requireActual('../src/utils/ammRouteResolver'); 72 | return { 73 | resolveAmmRoute: jest.fn((tokenX, ...args) => { 74 | if (tokenX === dummyTokenA) { 75 | return dummyAmmRoute; 76 | } 77 | return originalModule.resolveAmmRoute(tokenX, ...args); 78 | }), 79 | }; 80 | }); 81 | 82 | describe('AlexSDK - mock helpers', () => { 83 | it('Verify response value of getFeeRate function', async () => { 84 | expect(jest.isMockFunction(FeeHelper.getLiquidityProviderFee)).toBeTruthy(); 85 | const result = await sdk.getFeeRate(tokenAlex, Currency.STX); 86 | expect(result).toStrictEqual(dummyFee); 87 | }); 88 | 89 | it('Verify response value of getAllPossibleRoute function', async () => { 90 | expect(jest.isMockFunction(RouteHelper.getAllPossibleRoute)).toBeTruthy(); 91 | const result = await sdk.getRoute(Currency.STX, tokenWUSDA); 92 | expect(result).toStrictEqual(dummyAmmRoute); 93 | }); 94 | 95 | it('Verify response value of getRouter[deprecated] function', async () => { 96 | expect(jest.isMockFunction(RouteHelper.getAllPossibleRoute)).toBeTruthy(); 97 | const result = await sdk.getRouter(Currency.STX, dummyTokenB); 98 | expect(result).toStrictEqual([Currency.STX, dummyTokenC, dummyTokenB]); 99 | }); 100 | 101 | it('Verify response value of getAmountTo function', async () => { 102 | expect(jest.isMockFunction(RateHelper.getYAmountFromXAmount)).toBeTruthy(); 103 | const result = await sdk.getAmountTo( 104 | Currency.STX, 105 | BigInt(2) * BigInt(1e8), 106 | tokenWUSDA 107 | ); 108 | expect(result).toStrictEqual(dummyRate); 109 | }); 110 | 111 | it('Verify response value of runSwap function', async () => { 112 | expect(jest.isMockFunction(SwapHelper.runSpot)).toBeTruthy(); 113 | const result = await sdk.runSwap( 114 | DUMMY_DEPLOYER, 115 | tokenAlex, 116 | tokenWUSDA, 117 | BigInt(1), 118 | BigInt(0) 119 | ); 120 | expect(result).toStrictEqual(dummyTx); 121 | }); 122 | 123 | it('Verify response value of runSwap function (tx construct + rebase)', async () => { 124 | expect(jest.isMockFunction(SwapHelper.runSpot)).toBeTruthy(); 125 | expect(jest.isMockFunction(ammRouteResolver.resolveAmmRoute)).toBeTruthy(); 126 | const amount = BigInt(2) * BigInt(1e8); 127 | const result = await sdk.runSwap( 128 | configs.CONTRACT_DEPLOYER, 129 | dummyTokenA, 130 | dummyTokenB, 131 | amount, 132 | BigInt(0) 133 | ); 134 | expect(cvToValue(result.functionArgs[3])).toStrictEqual(dummyFactorA); 135 | expect(cvToValue(result.functionArgs[4])).toStrictEqual(dummyFactorB); 136 | expect(cvToValue(result.functionArgs[5])).toStrictEqual(amount); 137 | expect(result.postConditions[0].condition).toStrictEqual('gte'); 138 | }); 139 | 140 | it('Verify response value of runSwap function (empty pools)', async () => { 141 | expect(jest.isMockFunction(SwapHelper.runSpot)).toBeTruthy(); 142 | expect(jest.isMockFunction(ammRouteResolver.resolveAmmRoute)).toBeTruthy(); 143 | const amount = BigInt(2) * BigInt(1e8); 144 | await expect( 145 | sdk.runSwap( 146 | configs.CONTRACT_DEPLOYER, 147 | dummyTokenB, 148 | dummyTokenC, 149 | amount, 150 | BigInt(0) 151 | ) 152 | ).rejects.toThrow("Can't find AMM route"); 153 | }); 154 | 155 | it('Verify response value of getLatestPrices function', async () => { 156 | expect(jest.isMockFunction(fetchData.getPrices)).toBeTruthy(); 157 | const result = await sdk.getLatestPrices(); 158 | expect(result).toStrictEqual(parsedDummyPrices); 159 | }); 160 | 161 | it('Verify response value of getLatestPrices function (null token cases)', async () => { 162 | expect(jest.isMockFunction(fetchData.getPrices)).toBeTruthy(); 163 | const result = await sdk.getLatestPrices(); 164 | expect(result).toBeDefined(); 165 | }); 166 | 167 | it('Verify response value of getBalances function', async () => { 168 | expect(jest.isMockFunction(fetchData.fetchBalanceForAccount)).toBeTruthy(); 169 | const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; 170 | const result = await sdk.getBalances(stxAddress); 171 | expect(result).toStrictEqual(dummyBalances); 172 | }); 173 | 174 | it('Verify response value of fetchSwappableCurrency function', async () => { 175 | expect(jest.isMockFunction(fetchData.getAlexSDKData)).toBeTruthy(); 176 | const result = await sdk.fetchSwappableCurrency(); 177 | expect(result).toStrictEqual(dummyCurrencies); 178 | }); 179 | 180 | it('Verify response value of getWayPoints function', async () => { 181 | expect(jest.isMockFunction(fetchData.getAlexSDKData)).toBeTruthy(); 182 | expect(jest.isMockFunction(RouteHelper.getAllPossibleRoute)).toBeTruthy(); 183 | const mockedRoute = await sdk.getRoute(dummyTokenA, dummyTokenB); 184 | const result = await sdk.getWayPoints(mockedRoute); 185 | expect(result[0].id).toBe(dummyTokenA); 186 | expect(result[1].id).toBe(dummyTokenC); 187 | expect(result[2].id).toBe(dummyTokenB); 188 | expect(result[0].isRebaseToken).toBe(true); 189 | expect(result[1].isRebaseToken).toBe(false); 190 | expect(result[2].isRebaseToken).toBe(false); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /test/alexSDK.test.ts: -------------------------------------------------------------------------------- 1 | import { configs } from '../src/config'; 2 | import { AlexSDK, Currency } from '../src'; 3 | import Ajv from 'ajv'; 4 | import { createGenerator } from 'ts-json-schema-generator'; 5 | import path from 'node:path'; 6 | import { getAlexSDKData, getPrices } from '../src/utils/fetchData'; 7 | import { TxToBroadCast } from '../src/helpers/SwapHelper'; 8 | 9 | const runtimeTypescriptMatcher = (received: any, typeName: string) => { 10 | const validator = new Ajv().compile( 11 | createGenerator({ 12 | type: typeName, 13 | path: path.resolve(__dirname, '../src/types.ts'), 14 | tsconfig: path.resolve(__dirname, '../tsconfig.json'), 15 | }).createSchema(typeName) 16 | ); 17 | const pass = validator(received); 18 | return { 19 | pass, 20 | message: () => 21 | `expected ${received} does not match type ${typeName}: \n${JSON.stringify( 22 | validator.errors, 23 | null, 24 | 2 25 | )}`, 26 | }; 27 | }; 28 | 29 | declare global { 30 | namespace jest { 31 | interface Matchers { 32 | toMatchType(typeName: string): R; 33 | } 34 | } 35 | } 36 | 37 | const checkSwapResult = (result: TxToBroadCast) => { 38 | expect(typeof result).toBe('object'); 39 | expect(result).toHaveProperty('contractAddress'); 40 | expect(result).toHaveProperty('contractName'); 41 | expect(result).toHaveProperty('functionName'); 42 | expect(result).toHaveProperty('functionArgs'); 43 | expect(result).toHaveProperty('postConditions'); 44 | expect(result.contractAddress).toBe(configs.CONTRACT_DEPLOYER); 45 | expect(result.contractName).toBe('amm-pool-v2-01'); 46 | expect([ 47 | 'swap-helper', 48 | 'swap-helper-a', 49 | 'swap-helper-b', 50 | 'swap-helper-c', 51 | ]).toContain(result.functionName); 52 | expect(Array.isArray(result.functionArgs)).toBeTruthy(); 53 | expect(Array.isArray(result.postConditions)).toBeTruthy(); 54 | }; 55 | 56 | expect.extend({ toMatchType: runtimeTypescriptMatcher }); 57 | 58 | const tokenAlex = 'age000-governance-token' as Currency; 59 | const tokenDiko = 'token-wdiko' as Currency; 60 | const tokenWmick = 'token-wmick' as Currency; 61 | const tokenSSL = 'token-ssl-all-AESDE' as Currency; 62 | const tokenBRC20ORMM = 'brc20-ormm' as Currency; 63 | const wrongTokenAlex = '' as Currency; 64 | 65 | const routeLength1 = { from: tokenAlex, to: Currency.STX }; 66 | const routeLength2 = { from: tokenWmick, to: tokenDiko }; 67 | const routeLength3 = { from: tokenSSL, to: tokenDiko }; 68 | const routeLength4 = { from: tokenWmick, to: tokenBRC20ORMM }; 69 | const alternativeRoutes = [routeLength2, routeLength3, routeLength4]; 70 | 71 | const sdk = new AlexSDK(); 72 | const CLARITY_MAX_UNSIGNED_INT = BigInt( 73 | '340282366920938463463374607431768211455' 74 | ); 75 | 76 | describe('AlexSDK', () => { 77 | it('Verify response of getFeeRate function (custom route)', async () => { 78 | const customRoute = await sdk.getRoute(routeLength1.from, routeLength1.to); 79 | const result = await sdk.getFeeRate( 80 | routeLength1.from, 81 | routeLength1.to, 82 | customRoute 83 | ); 84 | expect(typeof result).toBe('bigint'); 85 | expect(result >= BigInt(0)).toBeTruthy(); 86 | }, 10000); 87 | 88 | it('Verify response of getFeeRate function (alternative routes)', async () => { 89 | for (const route of alternativeRoutes) { 90 | const result = await sdk.getFeeRate(route.from, route.to); 91 | expect(typeof result).toBe('bigint'); 92 | expect(result >= BigInt(0)).toBeTruthy(); 93 | } 94 | }, 40000); 95 | 96 | it('Attempt to Get Fee Rate with wrong tokens', async () => { 97 | await expect( 98 | sdk.getFeeRate(wrongTokenAlex, wrongTokenAlex) 99 | ).rejects.toThrow('No AMM pools in route'); 100 | }, 10000); 101 | 102 | it('Verify response of getRoute function', async () => { 103 | const result = await sdk.getRoute(Currency.STX, tokenDiko); 104 | expect(Array.isArray(result)).toBeTruthy(); 105 | expect(result.length).toBeGreaterThan(0); 106 | expect(result[0].pool.tokenX).toBe(Currency.STX); 107 | expect(result[result.length - 1].pool.tokenY).toBe(tokenDiko); 108 | expect(result.length).toBeLessThanOrEqual(5); 109 | expect(typeof result[0].pool.tokenX).toBe('string'); 110 | result.forEach((routeSegment) => { 111 | expect(typeof routeSegment.pool.tokenY).toBe('string'); 112 | }); 113 | }, 10000); 114 | 115 | it('Attempt to Get Route with wrong tokens', async () => { 116 | await expect(sdk.getRoute(wrongTokenAlex, wrongTokenAlex)).rejects.toThrow( 117 | "Can't find route" 118 | ); 119 | }, 10000); 120 | 121 | it('Verify response of Get Rate function (custom route)', async () => { 122 | const customRoute = await sdk.getRoute(routeLength1.from, routeLength1.to); 123 | const result = await sdk.getAmountTo( 124 | routeLength1.from, 125 | BigInt(10000000) * BigInt(1e8), 126 | routeLength1.to, 127 | customRoute 128 | ); 129 | expect(typeof result).toBe('bigint'); 130 | expect(result > BigInt(0)).toBeTruthy(); 131 | }, 10000); 132 | 133 | it('Attempt to Get Rate with a wrong From token', async () => { 134 | await expect( 135 | sdk.getAmountTo(wrongTokenAlex, BigInt(2) * BigInt(1e8), tokenDiko) 136 | ).rejects.toThrow('No AMM pool found for the given route'); 137 | }, 10000); 138 | 139 | it('Attempt to Get Rate with negative From amount', async () => { 140 | await expect( 141 | sdk.getAmountTo(Currency.STX, BigInt(-111), tokenDiko) 142 | ).rejects.toThrow( 143 | 'Cannot construct unsigned clarity integer from negative value' 144 | ); 145 | }, 10000); 146 | 147 | it('Attempt to Get Rate with an overflowing From amount (parseReadOnlyResponse)', async () => { 148 | await expect( 149 | sdk.getAmountTo( 150 | Currency.STX, 151 | BigInt('99999999999999992233720368547758007'), 152 | tokenDiko 153 | ) 154 | ).rejects.toThrow('ArithmeticOverflow'); 155 | }, 10000); 156 | 157 | it('Attempt to Get Rate with an overflowing From amount (decoders)', async () => { 158 | await expect( 159 | sdk.getAmountTo(Currency.STX, BigInt('9999223372036854775807'), tokenDiko) 160 | ).rejects.toThrow('ClarityError: 2011'); 161 | }, 10000); 162 | 163 | it('Verify response of runSwap function (custom route)', async () => { 164 | const customRoute = await sdk.getRoute(routeLength1.from, routeLength1.to); 165 | const result = await sdk.runSwap( 166 | configs.CONTRACT_DEPLOYER, 167 | routeLength1.from, 168 | routeLength1.to, 169 | BigInt(2) * BigInt(1e8), 170 | BigInt(0), 171 | customRoute 172 | ); 173 | checkSwapResult(result); 174 | }, 10000); 175 | 176 | it('Verify response of runSwap function (alternative routes)', async () => { 177 | for (const route of alternativeRoutes) { 178 | const result = await sdk.runSwap( 179 | configs.CONTRACT_DEPLOYER, 180 | route.from, 181 | route.to, 182 | BigInt(2) * BigInt(1e8), 183 | BigInt(0) 184 | ); 185 | checkSwapResult(result); 186 | } 187 | }, 40000); 188 | 189 | it('Attempt to Get Tx with an invalid stx address (checksum mismatch)', async () => { 190 | await expect( 191 | sdk.runSwap( 192 | 'SP25DP4A9EXT42KC40DMYQPMQCT1P0R5234GWEGS', 193 | Currency.STX, 194 | tokenDiko, 195 | BigInt(100), 196 | BigInt(0) 197 | ) 198 | ).rejects.toThrow('Invalid c32check string: checksum mismatch'); 199 | }, 10000); 200 | 201 | it('Attempt to run swap with wrong token', async () => { 202 | await expect( 203 | sdk.runSwap( 204 | 'SP25DP4A9EXT42KC40QDMYQPMQCT1P0R5234GWEGS', 205 | Currency.STX, 206 | wrongTokenAlex, 207 | BigInt(100), 208 | BigInt(0) 209 | ) 210 | ).rejects.toThrow("Can't find AMM route"); 211 | }, 10000); 212 | 213 | it('Attempt to runSwap with an invalid minDy value', async () => { 214 | const wrongValue = CLARITY_MAX_UNSIGNED_INT + BigInt(1); 215 | await expect( 216 | sdk.runSwap( 217 | configs.CONTRACT_DEPLOYER, 218 | Currency.STX, 219 | tokenDiko, 220 | BigInt(0), 221 | wrongValue 222 | ) 223 | ).rejects.toThrow( 224 | `Cannot construct unsigned clarity integer greater than ${CLARITY_MAX_UNSIGNED_INT}` 225 | ); 226 | }, 10000); 227 | 228 | it('Verify response of getLatestPrices function', async () => { 229 | const result = await sdk.getLatestPrices(); 230 | expect(result).toBeDefined(); 231 | expect(typeof result).toBe('object'); 232 | for (const value of Object.values(result)) { 233 | expect(typeof value).toBe('number'); 234 | expect(Number.isNaN(Number(value))).toBe(false); 235 | } 236 | }, 10000); 237 | 238 | it('Verify response of getBalances function', async () => { 239 | const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; 240 | const balances = await sdk.getBalances(stxAddress); 241 | expect(balances).toBeDefined(); 242 | expect(typeof balances).toBe('object'); 243 | for (const currency of Object.keys(balances)) { 244 | if (Object.values(Currency).includes(currency as Currency)) { 245 | expect(typeof balances[currency as Currency]).toBe('bigint'); 246 | } 247 | } 248 | }, 10000); 249 | 250 | it('Verify response of getBalances function (with fungible token balance)', async () => { 251 | const stxAddress = 'SP3ANPTPEQE72PNE31WE8BEV4VCKB2C38P48TPH0Q'; 252 | const balances = await sdk.getBalances(stxAddress); 253 | expect(balances).toBeDefined(); 254 | }, 10000); 255 | 256 | it('Verify response of getLatestPrices function', async () => { 257 | const result = await sdk.getLatestPrices(); 258 | expect(result).toBeDefined(); 259 | expect(typeof result).toBe('object'); 260 | for (const value of Object.values(result)) { 261 | expect(typeof value).toBe('number'); 262 | expect(Number.isNaN(Number(value))).toBe(false); 263 | } 264 | }); 265 | 266 | it('Verify response of getBalances function', async () => { 267 | const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; 268 | const balances = await sdk.getBalances(stxAddress); 269 | expect(balances).toBeDefined(); 270 | expect(typeof balances).toBe('object'); 271 | for (const currency of Object.keys(balances)) { 272 | expect(typeof balances[currency as Currency]).toBe('bigint'); 273 | } 274 | }); 275 | 276 | it('Attempt to get balances with invalid address', async () => { 277 | // TODO: Implement principal address verification in the SDK methods. 278 | const wrongAddress = 'ABC'; 279 | await expect(sdk.getBalances(wrongAddress)).rejects.toThrow( 280 | 'Failed to fetch account balances' 281 | ); 282 | }, 10000); 283 | 284 | it('getPrices response', async () => { 285 | const sdk = new AlexSDK(); 286 | const tokens = await sdk.fetchSwappableCurrency(); 287 | expect(await getPrices(tokens)).toMatchType('BackendAPIPriceResponse'); 288 | }); 289 | 290 | it('Verify response of getWayPoints function', async () => { 291 | const route = await sdk.getRoute(tokenAlex, Currency.STX); 292 | const result = await sdk.getWayPoints(route); 293 | expect(result[0].id).toBe(tokenAlex); 294 | expect(result[1].id).toBe(Currency.STX); 295 | result.forEach((token) => { 296 | expect(typeof token.name).toBe('string'); 297 | expect(typeof token.icon).toBe('string'); 298 | expect(typeof token.wrapToken).toBe('string'); 299 | expect(typeof token.underlyingToken).toBe('string'); 300 | expect(typeof token.underlyingTokenDecimals).toBe('number'); 301 | expect(token.wrapTokenDecimals).toBe(8); 302 | expect(token.isRebaseToken).toBe(false); 303 | }); 304 | }, 10000); 305 | }); 306 | -------------------------------------------------------------------------------- /test/ammRouteResolver.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AMMRouteSegment, 3 | resolveAmmRoute, 4 | } from '../src/utils/ammRouteResolver'; 5 | import { PoolData } from '../src/types'; 6 | import { Currency } from '../src'; 7 | 8 | namespace TestCurrency { 9 | export const W_USDA = 'token-wusda' as Currency; 10 | export const W_XUSD = 'token-wxusd' as Currency; 11 | export const W_STX = 'token-wstx' as Currency; 12 | export const W_BANANA = 'token-wban' as Currency; 13 | export const W_XBTC = 'token-wbtc' as Currency; 14 | export const BRC20_DB20 = 'brc20-db20' as Currency; 15 | } 16 | 17 | const TestPool = { 18 | AMM_SWAP_POOL_WXUSD_WUSDA: toPoolData('token-wxusd,token-wusda,0.0001e8'), 19 | AMM_SWAP_POOL_ALEX_WBAN: toPoolData('age000-governance-token,token-wban,1e8'), 20 | AMM_SWAP_POOL_WSTX_ALEX: toPoolData('token-wstx,age000-governance-token,1e8'), 21 | AMM_SWAP_POOL_WSTX_XBTC: toPoolData('token-wstx,token-wbtc,1e8'), 22 | AMM_SWAP_POOL_ALEX_WUSDA: toPoolData( 23 | 'age000-governance-token,token-wusda,1e8' 24 | ), 25 | AMM_SWAP_POOL_V1_1_WSTX_XBTC: toPoolData('token-wstx,token-wbtc,1e8'), 26 | AMM_SWAP_POOL_V1_1_WSTX_ABTC: toPoolData('token-wstx,token-abtc,1e8'), 27 | AMM_SWAP_POOL_V1_1_ABTC_WBTC: toPoolData('token-abtc,token-wbtc,0.05e8'), 28 | AMM_SWAP_POOL_V1_1_ABTC_BRC20DB20: toPoolData('token-abtc,brc20-db20,1e8'), 29 | }; 30 | 31 | describe('resolveAmmRoute', function () { 32 | it('should match if there is an exact match', function () { 33 | expect( 34 | resolveAmmRoute(TestCurrency.W_USDA, TestCurrency.W_XUSD, [ 35 | TestPool.AMM_SWAP_POOL_WXUSD_WUSDA, 36 | ]) 37 | ).toEqual([ 38 | { 39 | pool: TestPool.AMM_SWAP_POOL_WXUSD_WUSDA, 40 | from: TestCurrency.W_USDA, 41 | neighbour: TestCurrency.W_XUSD, 42 | }, 43 | ]); 44 | expect( 45 | resolveAmmRoute(TestCurrency.W_XUSD, TestCurrency.W_USDA, [ 46 | TestPool.AMM_SWAP_POOL_WXUSD_WUSDA, 47 | ]).map((a) => a.pool) 48 | ).toEqual([TestPool.AMM_SWAP_POOL_WXUSD_WUSDA]); 49 | }); 50 | 51 | it('should match with l2', function () { 52 | expect( 53 | resolveAmmRoute(TestCurrency.W_STX, TestCurrency.W_BANANA, [ 54 | TestPool.AMM_SWAP_POOL_ALEX_WBAN, 55 | TestPool.AMM_SWAP_POOL_WXUSD_WUSDA, 56 | TestPool.AMM_SWAP_POOL_WSTX_ALEX, 57 | ]).map((a) => a.pool) 58 | ).toEqual([ 59 | TestPool.AMM_SWAP_POOL_WSTX_ALEX, 60 | TestPool.AMM_SWAP_POOL_ALEX_WBAN, 61 | ]); 62 | expect( 63 | resolveAmmRoute(TestCurrency.W_BANANA, TestCurrency.W_STX, [ 64 | TestPool.AMM_SWAP_POOL_ALEX_WBAN, 65 | TestPool.AMM_SWAP_POOL_WXUSD_WUSDA, 66 | TestPool.AMM_SWAP_POOL_WSTX_ALEX, 67 | ]).map((a) => a.pool) 68 | ).toEqual([ 69 | TestPool.AMM_SWAP_POOL_ALEX_WBAN, 70 | TestPool.AMM_SWAP_POOL_WSTX_ALEX, 71 | ]); 72 | }); 73 | 74 | it('should match with l3', function () { 75 | expect( 76 | resolveAmmRoute(TestCurrency.W_XBTC, TestCurrency.W_BANANA, [ 77 | TestPool.AMM_SWAP_POOL_WXUSD_WUSDA, 78 | TestPool.AMM_SWAP_POOL_WSTX_ALEX, 79 | TestPool.AMM_SWAP_POOL_WSTX_XBTC, 80 | TestPool.AMM_SWAP_POOL_ALEX_WBAN, 81 | TestPool.AMM_SWAP_POOL_ALEX_WUSDA, 82 | ]).map((a) => a.pool) 83 | ).toEqual([ 84 | TestPool.AMM_SWAP_POOL_WSTX_XBTC, 85 | TestPool.AMM_SWAP_POOL_WSTX_ALEX, 86 | TestPool.AMM_SWAP_POOL_ALEX_WBAN, 87 | ]); 88 | }); 89 | 90 | it('should find the shortest path', function () { 91 | expect( 92 | resolveAmmRoute(TestCurrency.W_STX, TestCurrency.BRC20_DB20, [ 93 | TestPool.AMM_SWAP_POOL_V1_1_WSTX_XBTC, 94 | TestPool.AMM_SWAP_POOL_V1_1_WSTX_ABTC, 95 | TestPool.AMM_SWAP_POOL_V1_1_ABTC_WBTC, 96 | TestPool.AMM_SWAP_POOL_V1_1_ABTC_BRC20DB20, 97 | ]).map((a) => a.pool) 98 | ).toEqual([ 99 | TestPool.AMM_SWAP_POOL_V1_1_WSTX_ABTC, 100 | TestPool.AMM_SWAP_POOL_V1_1_ABTC_BRC20DB20, 101 | ]); 102 | }); 103 | }); 104 | 105 | function toPoolData(pool: string): PoolData { 106 | const [tokenX, tokenY, factor] = pool.split(','); 107 | return { 108 | tokenX: tokenX as Currency, 109 | tokenY: tokenY as Currency, 110 | factor: BigInt(Number(factor) * 1e8), 111 | poolId: BigInt(0), 112 | }; 113 | } 114 | -------------------------------------------------------------------------------- /test/mock-data/alexSDKMockResponses.ts: -------------------------------------------------------------------------------- 1 | import { AlexSDKResponse, PriceData, TokenInfo } from '../../src/types'; 2 | import { TxToBroadCast } from '../../src/helpers/SwapHelper'; 3 | import { Currency } from '../../src'; 4 | import { AMMRouteSegment } from '../../src/utils/ammRouteResolver'; 5 | import { configs } from '../../src/config'; 6 | 7 | const validDeployer = configs.CONTRACT_DEPLOYER; 8 | export const DUMMY_DEPLOYER = 'SP111111111111111111111111111111111111111'; 9 | 10 | export const dummyFee = BigInt(777); 11 | 12 | export const dummyTokenA = 'TokenA' as Currency; 13 | export const dummyTokenB = 'TokenB' as Currency; 14 | export const dummyTokenC = 'TokenC' as Currency; 15 | 16 | export const dummyFactorA = BigInt(670000000); 17 | export const dummyFactorB = BigInt(680000000); 18 | export const dummyAmmRoute: AMMRouteSegment[] = [ 19 | { 20 | from: dummyTokenA, 21 | neighbour: dummyTokenC, 22 | pool: { 23 | tokenX: dummyTokenA, 24 | tokenY: dummyTokenC, 25 | factor: dummyFactorA, 26 | poolId: BigInt(1), 27 | }, 28 | }, 29 | { 30 | from: dummyTokenC, 31 | neighbour: dummyTokenB, 32 | pool: { 33 | tokenX: dummyTokenC, 34 | tokenY: dummyTokenB, 35 | factor: dummyFactorB, 36 | poolId: BigInt(2), 37 | }, 38 | }, 39 | ]; 40 | 41 | export const dummyRate = BigInt(1001); 42 | 43 | export const dummyTx: TxToBroadCast = { 44 | contractName: 'amm-pool-v2-01', 45 | functionName: 'swap-helper', 46 | functionArgs: [], 47 | contractAddress: validDeployer, 48 | postConditions: [], 49 | }; 50 | 51 | export const dummyPrices: PriceData[] = [ 52 | { 53 | token: dummyTokenA, 54 | price: 1.1, 55 | }, 56 | { 57 | token: dummyTokenB, 58 | price: 2.2, 59 | }, 60 | ]; 61 | export const parsedDummyPrices = { 62 | TokenA: 1.1, 63 | TokenB: 2.2, 64 | }; 65 | 66 | export const dummyBalances = { 67 | TokenA: BigInt(86794603901), 68 | TokenB: BigInt(86794603902), 69 | }; 70 | 71 | export const dummyCurrencies: TokenInfo[] = [ 72 | { 73 | id: dummyTokenA, 74 | name: 'TKA', 75 | icon: '', 76 | wrapTokenDecimals: 8, 77 | wrapToken: `${validDeployer}.token-a::tka`, 78 | underlyingToken: `${validDeployer}.token-a::tka`, 79 | underlyingTokenDecimals: 6, 80 | isRebaseToken: true, 81 | isVaultWrapToken: false, 82 | }, 83 | { 84 | id: dummyTokenB, 85 | name: 'TKB', 86 | icon: '', 87 | wrapTokenDecimals: 8, 88 | wrapToken: `${validDeployer}.token-b::tkb`, 89 | underlyingToken: `${validDeployer}.token-b::tkb`, 90 | underlyingTokenDecimals: 6, 91 | isRebaseToken: false, 92 | isVaultWrapToken: false, 93 | }, 94 | { 95 | id: dummyTokenC, 96 | name: 'TKC', 97 | icon: '', 98 | wrapTokenDecimals: 8, 99 | wrapToken: `${validDeployer}.token-c::tkc`, 100 | underlyingToken: `${validDeployer}.token-c::tkc`, 101 | underlyingTokenDecimals: 6, 102 | isRebaseToken: false, 103 | isVaultWrapToken: false, 104 | }, 105 | ]; 106 | 107 | export const dummyAlexSDKData: AlexSDKResponse = { 108 | tokens: dummyCurrencies, 109 | pools: [], 110 | }; 111 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": false, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | "resolveJsonModule": true 35 | } 36 | } 37 | --------------------------------------------------------------------------------