├── .github
├── ISSUE_TEMPLATE
│ ├── bug.yml
│ ├── config.yml
│ └── enhancement.yml
└── workflows
│ └── tests.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── babel.config.cjs
├── docs
├── dex-transaction.md
├── models.md
├── providers
│ ├── data.md
│ └── wallet.md
└── requests
│ ├── cancel-swap-request.md
│ ├── fetch-request.md
│ ├── split-cancel-swap-request.md
│ ├── split-swap-request.md
│ └── swap-request.md
├── jest.config.cjs
├── package-lock.json
├── package.json
├── src
├── constants.ts
├── definition-builder.ts
├── dex
│ ├── api
│ │ ├── base-api.ts
│ │ ├── minswap-api.ts
│ │ ├── muesliswap-api.ts
│ │ ├── splash-api.ts
│ │ ├── sundaeswap-v1-api.ts
│ │ ├── sundaeswap-v3-api.ts
│ │ ├── vyfinance-api.ts
│ │ └── wingriders-api.ts
│ ├── base-dex.ts
│ ├── definitions
│ │ ├── minswap-v2
│ │ │ ├── order.ts
│ │ │ └── pool.ts
│ │ ├── minswap
│ │ │ ├── order.ts
│ │ │ └── pool.ts
│ │ ├── muesliswap
│ │ │ ├── order.ts
│ │ │ └── pool.ts
│ │ ├── splash
│ │ │ ├── order.ts
│ │ │ └── pool.ts
│ │ ├── sundaeswap-v1
│ │ │ ├── order.ts
│ │ │ └── pool.ts
│ │ ├── sundaeswap-v3
│ │ │ ├── order.ts
│ │ │ └── pool.ts
│ │ ├── vyfinance
│ │ │ ├── order.ts
│ │ │ └── pool.ts
│ │ ├── wingriders-v2
│ │ │ ├── order.ts
│ │ │ └── pool.ts
│ │ └── wingriders
│ │ │ ├── order.ts
│ │ │ └── pool.ts
│ ├── logo
│ │ ├── minswap.png
│ │ ├── minswapv2.png
│ │ ├── muesliswap.png
│ │ ├── splash.png
│ │ ├── sundaeswap.png
│ │ ├── vyfinance.png
│ │ ├── wingriders.png
│ │ └── wingridersv2.png
│ ├── minswap-v2.ts
│ ├── minswap.ts
│ ├── models
│ │ ├── asset.ts
│ │ ├── dex-transaction.ts
│ │ └── liquidity-pool.ts
│ ├── muesliswap.ts
│ ├── splash.ts
│ ├── sundaeswap-v1.ts
│ ├── sundaeswap-v3.ts
│ ├── vyfinance.ts
│ ├── wingriders-v2.ts
│ └── wingriders.ts
├── dexter.ts
├── index.ts
├── providers
│ ├── asset-metadata
│ │ ├── base-metadata-provider.ts
│ │ └── token-registry-provider.ts
│ ├── data
│ │ ├── base-data-provider.ts
│ │ ├── blockfrost-provider.ts
│ │ ├── kupo-provider.ts
│ │ └── mock-data-provider.ts
│ └── wallet
│ │ ├── base-wallet-provider.ts
│ │ ├── lucid-provider.ts
│ │ └── mock-wallet-provider.ts
├── requests
│ ├── cancel-swap-request.ts
│ ├── fetch-request.ts
│ ├── split-cancel-swap-request.ts
│ ├── split-swap-request.ts
│ └── swap-request.ts
├── types.ts
└── utils.ts
├── tests
├── dex-transaction.test.ts
├── minswap.test.ts
├── muesliswap.test.ts
├── split-swap-request.test.ts
├── sundaeswap-v1.test.ts
├── swap-request.test.ts
├── vyfinance.test.ts
└── wingriders.test.ts
└── tsconfig.json
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: File a bug report
3 | title: "[Bug]: "
4 | labels: ["bug"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to fill out this bug report!
10 | - type: dropdown
11 | id: data-provider
12 | attributes:
13 | label: Data Provider
14 | description: Which Data Provider you were using
15 | options:
16 | - Blockfrost
17 | - Kupo
18 | - Custom
19 | validations:
20 | required: true
21 | - type: dropdown
22 | id: dex
23 | attributes:
24 | label: DEX
25 | description: Which DEX this bug occurred for
26 | options:
27 | - All
28 | - Minswap
29 | - SundaeSwap
30 | - MuesliSwap
31 | - WingRiders
32 | - VyFinance
33 | validations:
34 | required: true
35 | - type: textarea
36 | id: description
37 | attributes:
38 | label: Description
39 | description: Give context for the bug
40 | placeholder: What happened?
41 | validations:
42 | required: true
43 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/enhancement.yml:
--------------------------------------------------------------------------------
1 | name: Enhancement
2 | description: Suggest an Enhancement
3 | title: "[Enhancement]: "
4 | labels: ["enhancement"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to help us improve Dexter!
10 | - type: textarea
11 | id: description
12 | attributes:
13 | label: Description
14 | description: Give context for your idea
15 | validations:
16 | required: true
17 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v3
10 | - name: Test using Node.js
11 | uses: actions/setup-node@v3
12 | with:
13 | node-version: '17'
14 | - run: npm ci
15 | - run: npm run test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build/
3 | .idea/
4 | .DS_Store
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | tests
3 | tsconfig.json
4 | babel.config.cjs
5 | jest.config.cjs
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to Dexter will be documented in this file.
4 |
5 | ## [v5.4.9]
6 | - Splash integration
7 |
8 | ## [v5.4.0]
9 | - SundaeSwap v3 integration
10 |
11 | ## [v5.3.0]
12 | - Minswap v2 integration
13 |
14 | ## [v5.2.0]
15 | - Add `withMinimumReceive(minReceive: bigint)` to SwapRequest
16 |
17 | ## [v5.1.0]
18 | - Fix cancelling orders for each DEX
19 | - Add new split cancel order request
20 |
21 | ## [v5.0.0]
22 | - TeddySwap integration
23 | - Spectrum integration
24 |
25 | ## [v4.2.0]
26 | - Fix WR price impact formula for 0 decimals
27 | - Rename Asset identifier function
28 | - Include '/' helper function for proxy URLs
29 | - Add export for SplitSwapRequest
30 | - Add tests for DexTransaction events
31 | - Fix `withSwapOutAmountMappings` for split swap requests
32 | - Add fetching for total LP tokens for liquidity pools
33 |
34 | ## [v4.1.0]
35 | - Support for multi-dex swap requests.
36 |
37 | ## [v4.0.2]
38 | - Fix pool identifiers & LP token for Minswap.
39 |
40 | ## [v4.0.1]
41 | - Remove total LP tokens from fetched data. This data is not needed for swapping, and wastes a lot of network requests.
42 | - Add `setProviderForDex()` to use different data providers for each DEX.
43 |
44 | ## [v3.0.3]
45 | - Fix for Minswap calculations with pool fee percents to round before casting.
46 |
47 | ## [v3.0.2]
48 | - Update DEX template definitions to use a copy of the template, rather than altering the original.
49 | - Fix for WingRiders API.
50 |
51 | ## [v3.0.1]
52 |
53 | - Fix for WingRiders price impact calculation when using a non ADA swap in token.
54 | - Expose address payments in `DexTransaction` instance.
55 | - Update DEX `name` variable to `identifier` to resolve browser related issue with reserved words.
56 |
57 | ## [v2.0.0]
58 |
59 | - Adjust Kupo & Blockfrost data providers to accept an optional `RequestConfig`.
60 | - Cleanup around asset filtering when using `FetchRequest.getLiquidityPools()`.
61 | - Add `FetchRequest.getLiquidityPoolState()` helper to get the latest state for a liquidity pool.
62 | - Liquidity pool fee fix for SundaeSwap when constructing pools from on-chain data.
63 | - Add ability to retry API requests in the `RequestConfig` options.
64 | - Add handling for Blockfrost API limit cooldown.
65 | - Add `SwapRequest.withSwapOutAmount(bigint)` to calculate the estimated swap in amount.
66 | - Add `SwapRequest.withSwapOutToken(Token)` to allow crafting a SwapRequest given the swap out token.
67 | - Update `FetchRequest.forDexs()` to `FetchRequest.onDexs()`.
68 | - Update `FetchRequest.forAllDexs()` to `FetchRequest.onAllDexs()`.
69 | - Add `FetchRequest.forTokens()` & `FetchRequest.forTokenPairs()` for filtering pools containing tokens/token pairs.
70 | - Fix for encrypted Minswap API responses (API still has hard call limits).
71 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Indigo Protocol
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.
--------------------------------------------------------------------------------
/babel.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "node": "current"
8 | }
9 | }
10 | ],
11 | "@babel/preset-typescript",
12 | ]
13 | };
--------------------------------------------------------------------------------
/docs/dex-transaction.md:
--------------------------------------------------------------------------------
1 |
2 |
DEX Transactions
3 |
4 |
5 | You should never have to create DEX transactions yourself. However, the documentation below gives a rundown
6 | on how you can obtain information for your swap transactions, as well as listen for specific events through the Tx lifecycle.
7 |
8 | ### Getters
9 |
10 |
11 | hash(): string
Get the Tx hash if available
12 |
13 | ##### Using
14 |
15 | ```js
16 | console.log(transaction.hash);
17 | ```
18 |
19 |
20 |
21 |
22 |
23 | isSigned(): boolean
Get whether the Tx has been signed
24 |
25 | ##### Using
26 |
27 | ```js
28 | console.log(transaction.isSigned);
29 | ```
30 |
31 |
32 |
33 |
34 |
35 | payments(): PayToAddress[]
Get the address payments made for the Tx
36 |
37 | ##### Using
38 |
39 | ```js
40 | console.log(transaction.payments);
41 | ```
42 |
43 |
44 |
45 |
46 |
47 | status(): TransactionStatus
Get the current status of the transaction
48 |
49 | ##### Using
50 |
51 | ```js
52 | console.log(transaction.status);
53 | ```
54 |
55 |
56 | ### Listen for Events
57 |
58 |
59 | onBuilding(TransactionCallback): DexTransaction
Tx is in building status
60 |
61 | ##### Using
62 |
63 | ```js
64 | transaction.onBuilding(() => {
65 | console.log('Tx building');
66 | });
67 | ```
68 |
69 |
70 |
71 |
72 |
73 | onSigning(TransactionCallback): DexTransaction
Tx is in signing status
74 |
75 | ##### Using
76 |
77 | ```js
78 | transaction.onSigning(() => {
79 | console.log('Tx signing');
80 | });
81 | ```
82 |
83 |
84 |
85 |
86 |
87 | onSubmitting(TransactionCallback): DexTransaction
Tx is in submitting status
88 |
89 | ##### Using
90 |
91 | ```js
92 | transaction.onSubmitting(() => {
93 | console.log('Tx submitting to chain');
94 | });
95 | ```
96 |
97 |
98 |
99 |
100 |
101 | onSubmitted(TransactionCallback): DexTransaction
Tx has been submitted on-chain
102 |
103 | ##### Using
104 |
105 | ```js
106 | transaction.onSubmitted(() => {
107 | console.log('Tx submitted');
108 | });
109 | ```
110 |
111 |
112 |
113 |
114 |
115 | onError(TransactionCallback): DexTransaction
Error has occurred with the transaction
116 |
117 | ##### Using
118 |
119 | ```js
120 | transaction.onError(() => {
121 | console.log('Something went wrong');
122 | });
123 | ```
124 |
125 |
126 |
127 |
128 |
129 | onFinally(TransactionCallback): DexTransaction
Everything went OK or error has occurred
130 |
131 | ##### Using
132 |
133 | ```js
134 | transaction.onFinally(() => {
135 | console.log('All complete or has errored');
136 | });
137 | ```
138 |
--------------------------------------------------------------------------------
/docs/models.md:
--------------------------------------------------------------------------------
1 |
2 |
Return Models
3 |
4 |
5 | Below are commonly returned models & types for your requests, along with example data.
6 |
7 | ### Asset
8 | ```js
9 | {
10 | policyId: string // '533bb94a8850ee3c...'
11 | nameHex: Token // '494e4459'
12 | decimals: Token // 6
13 | }
14 | ```
15 |
16 | ### LiquidityPool
17 | ```js
18 | {
19 | dex: string // Minswap
20 | assetA: Token // Asset || 'lovelace'
21 | assetB: Token // Asset || 'lovelace'
22 | reserveA: bigint // 1234_000000n
23 | reserveB: bigint // 5678_000000n
24 | address: string // 'addr1LiquidityPoolAddress...'
25 | marketOrderAddress: string // 'addr1...'
26 | limitOrderAddress: string // 'addr1...'
27 | }
28 |
29 | ```
30 | ### SwapFee
31 | ```js
32 | {
33 | id: string // 'batcherFee'
34 | title: string // 'Batcher Fee'
35 | description: string // 'Fee paid for the service of off-chain Laminar batcher to process transactions.'
36 | value: bigint // 2_000000n
37 | isReturned: boolean // false
38 | }
39 | ```
40 |
41 | ### DexTransaction
42 | See [doc](dex-transaction.md) for more information
43 |
--------------------------------------------------------------------------------
/docs/providers/data.md:
--------------------------------------------------------------------------------
1 |
2 |
Data Providers
3 |
4 |
5 | ### Mock
6 |
7 | ```js
8 | const mockProvider: BaseDataProvider = new MockDataProvider();
9 | ```
10 |
11 | ### Blockfrost
12 | Dexter requires a lot of requests when pulling liquidity pools, so it is advised to either [host your own Blockfrost backend](https://github.com/blockfrost/blockfrost-backend-ryo),
13 | or use an API plan sufficient to handle enough requests.
14 | ```js
15 | const blockfrostProvider: BaseDataProvider = new BlockfrostProvider(
16 | {
17 | url: 'https://cardano-mainnet.blockfrost.io/api/v0',
18 | projectId: '',
19 | }
20 | );
21 | ```
22 |
23 | ### Kupo
24 |
25 | ```js
26 | const kupoProvider: BaseDataProvider = new KupoProvider(
27 | {
28 | url: 'http://localhost:1442',
29 | }
30 | );
31 | ```
32 |
33 | For the best results, the matching patterns below give the minimum required for DEX addresses
34 | to resolve within Dexter :
35 | ```
36 | --match "de9b756719341e79785aa13c164e7fe68c189ed04d61c9876b2fe53f.4d7565736c69537761705f414d4d"
37 | --match "026a18d04a0c642759bb3d83b12e3344894e5c1c7b2aeb1a2113a570.4c"
38 | --match "13aa2accf2e1561723aa26871e071fdf32c867cff7e7d50ad470d62f.4d494e53574150"
39 | --match "addr1w9qzpelu9hn45pefc0xr4ac4kdxeswq7pndul2vuj59u8tqaxdznu"
40 | --match "{ Your Address }"
41 | ```
42 |
43 | An example of a full Kupo run command given these patterns :
44 | ```
45 | kupo \
46 | --host 0.0.0.0 \
47 | --port 1442 \
48 | --node-socket path/to/node/socket \
49 | --node-config path/to/node/config \
50 | --since origin \
51 | --defer-db-indexes \
52 | --workdir path/to/db/directory \
53 | --prune-utxo \
54 | --match "de9b756719341e79785aa13c164e7fe68c189ed04d61c9876b2fe53f.4d7565736c69537761705f414d4d" \
55 | --match "026a18d04a0c642759bb3d83b12e3344894e5c1c7b2aeb1a2113a570.4c" \
56 | --match "13aa2accf2e1561723aa26871e071fdf32c867cff7e7d50ad470d62f.4d494e53574150" \
57 | --match "addr1w9qzpelu9hn45pefc0xr4ac4kdxeswq7pndul2vuj59u8tqaxdznu"
58 | --match "ffcdbb9155da0602280c04d8b36efde35e3416567f9241aff0955269.4d7565736c69537761705f414d4d" \
59 | --match "{ Your Address }"
60 | ```
61 |
--------------------------------------------------------------------------------
/docs/providers/wallet.md:
--------------------------------------------------------------------------------
1 |
2 |
Wallet Providers
3 |
4 |
5 | ### Mock
6 | ```js
7 | const mockProvider: BaseWalletProvider = new MockWalletProvider();
8 | ```
9 |
10 | ### Lucid
11 | ```js
12 | const lucidProvider: BaseWalletProvider = new LucidProvider();
13 | ```
14 |
15 | ##### Lucid Blockfrost
16 | ```js
17 | lucidProvider.loadWallet(walletApi, {
18 | url: 'https://cardano-mainnet.blockfrost.io/api/v0',
19 | projectId: '',
20 | })
21 | ```
22 | or
23 | ```js
24 | lucidProvider.loadWalletFromSeedPhrase(['...'], {}, {
25 | url: 'https://cardano-mainnet.blockfrost.io/api/v0',
26 | projectId: '',
27 | })
28 | ```
29 |
30 | ##### Lucid Kupmios
31 | ```js
32 | lucidProvider.loadWallet(walletApi, {}, {
33 | kupoUrl: 'http://localhost:1442',
34 | ogmiosUrl: 'ws://localhost:1337',
35 | })
36 | ```
37 | or
38 | ```js
39 | lucidProvider.loadWalletFromSeedPhrase(['...'], {}, {
40 | kupoUrl: 'http://localhost:1442',
41 | ogmiosUrl: 'ws://localhost:1337',
42 | })
43 | ```
--------------------------------------------------------------------------------
/docs/requests/cancel-swap-request.md:
--------------------------------------------------------------------------------
1 |
2 |
Cancel Swap Request
3 |
4 |
5 | ### Obtaining
6 | ```js
7 | dexter.newCancelSwapRequest()
8 | ...
9 | ```
10 |
11 | ### CancelSwapRequest API
12 |
13 |
14 | forTransaction(string): CancelSwapRequest
Set which transaction to cancel from, given the Tx hash.
15 |
16 | ##### Using
17 |
18 | ```js
19 | dexter.newCancelSwapRequest()
20 | .forTransaction('abc...')
21 | ...
22 | ```
23 |
24 |
25 |
26 |
27 |
28 | forDex(string): CancelSwapRequest
Set which DEX the transaction came from.
29 |
30 | ##### Using
31 |
32 | ```js
33 | dexter.newCancelSwapRequest()
34 | .forDex(VyFinance.identifier)
35 | ...
36 | ```
37 |
38 |
39 |
40 |
41 |
42 | cancel(): DexTransaction
Submit the transaction to cancel your order.
43 |
44 | Transaction & DEX must be set beforehand.
45 |
46 | ##### Using
47 |
48 | ```js
49 | dexter.newCancelSwapRequest()
50 | ...
51 | .cancel()
52 | ```
53 |
--------------------------------------------------------------------------------
/docs/requests/fetch-request.md:
--------------------------------------------------------------------------------
1 |
2 |
Fetch Request
3 |
4 |
5 | ### Obtaining
6 | ```js
7 | dexter.newFetchRequest()
8 | ...
9 | ```
10 |
11 | ### FetchRequest API
12 | Omitting the `forTokens()` & `forTokenPairs()` when constructing your request will result in all possible pools.
13 |
14 | Omitting the `forDexs()` & `forAllDexs()` when constructing your request will result in all available DEXs to be used
15 | when fetching pools.
16 |
17 |
18 | onDexs(string | string[]): FetchRequest
Set which DEXs to grab information for.
19 |
20 | ##### Using
21 |
22 | ```js
23 | dexter.newFetchRequest()
24 | .onDexs(WingRiders.identifier)
25 | ...
26 | ```
27 | or
28 | ```js
29 | dexter.newFetchRequest()
30 | .onDexs([WingRiders.identifier, SundaeSwap.identifier])
31 | ...
32 | ```
33 |
34 |
35 |
36 |
37 |
38 | onAllDexs(): FetchRequest
Grab information from all available DEXs.
39 |
40 | ##### Using
41 |
42 | ```js
43 | dexter.newFetchRequest()
44 | .onAllDexs()
45 | ...
46 | ```
47 |
48 |
49 |
50 |
51 |
52 | setDataProviderForDex(string, BaseDataProvider): FetchRequest
Force a data provider for a DEX.
53 |
54 | ##### Using
55 |
56 | ```js
57 | dexter.newFetchRequest()
58 | .onAllDexs()
59 | ...
60 | ```
61 |
62 |
63 |
64 |
65 |
66 | forTokens(Token[]): FetchRequest
Set filtering tokens when fetching liquidity pools
67 |
68 | ##### Using
69 |
70 | ```js
71 | const indyAsset: Asset = new Asset('533bb94a8850ee3ccbe483106489399112b74c905342cb1792a797a0', '494e4459', 6);
72 |
73 | // Will only fetch pools containing the INDY token
74 | dexter.newFetchRequest()
75 | .forTokens([indyAsset])
76 | ...
77 | ```
78 |
79 |
80 |
81 |
82 |
83 | forTokenPairs(Token[][]): FetchRequest
Set filtering token pairs when fetching liquidity pools
84 |
85 | ##### Using
86 |
87 | ```js
88 | const indyAsset: Asset = new Asset('533bb94a8850ee3ccbe483106489399112b74c905342cb1792a797a0', '494e4459', 6);
89 |
90 | // Will only fetch pools containing ADA & INDY assets
91 | dexter.newFetchRequest()
92 | .forTokenPairs([
93 | ['lovelace', indyAsset],
94 | ])
95 | ...
96 | ```
97 |
98 |
99 |
100 |
101 |
102 | getLiquidityPools(): Promise<LiquidityPool[]>
Fetch liquidity pools from your set DEXs
103 |
104 | Providing the first or first & second parameters will filter the returned pools by the assets you provide.
105 |
106 | ##### Using
107 |
108 | ```js
109 | dexter.newFetchRequest()
110 | .onAllDexs()
111 | .getLiquidityPools()
112 | .then((pools: LiquidityPool[]) => {
113 | console.log(pools);
114 | });
115 | ```
116 |
117 |
118 |
119 |
120 |
121 | getLiquidityPoolState(LiquidityPool): Promise<LiquidityPool>
Fetch latest state for a liquidity pool
122 |
123 | ##### Using
124 |
125 | ```js
126 | dexter.newFetchRequest()
127 | .getLiquidityPoolState(liquidityPool)
128 | .then((pool: LiquidityPool) => {
129 | console.log(pool);
130 | });
131 | ```
132 |
133 |
--------------------------------------------------------------------------------
/docs/requests/split-cancel-swap-request.md:
--------------------------------------------------------------------------------
1 |
2 |
Split Cancel Swap Request
3 |
4 |
5 | Request a split cancel order request in order to split your cancel order into chunks on different DEXs.
6 |
7 | ### Obtaining
8 | ```js
9 | dexter.newSplitCancelSwapRequest()
10 | ...
11 | ```
12 |
13 | ### SwapRequest API
14 |
15 |
16 | forTransactions(SplitCancelSwapMapping[]): SplitSwapRequest
Set which transactions to cancel.
17 |
18 | ##### Using
19 |
20 | ```js
21 | dexter.newSplitCancelSwapRequest()
22 | .forTransactions([{ txHash: '{Tx Hash}', dex: 'Minswap' }])
23 | ...
24 | ```
25 |
26 |
27 |
28 |
29 |
30 | submit(): DexTransaction
Finally submit your split cancel orders on-chain.
31 |
32 | ##### Using
33 |
34 | ```js
35 | dexter.newSplitCancelSwapRequest()
36 | ...
37 | .submit()
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/docs/requests/split-swap-request.md:
--------------------------------------------------------------------------------
1 |
2 |
Split Swap Request
3 |
4 |
5 | Request a split order request in order to split your swap order into chunks on different DEXs.
6 |
7 | ### Obtaining
8 | ```js
9 | dexter.newSplitSwapRequest()
10 | ...
11 | ```
12 |
13 | ### SwapRequest API
14 |
15 |
16 | withSwapInToken(Token): SplitSwapRequest
Set which Token in the pool you are swapping in.
17 |
18 | ##### Using
19 |
20 | ```js
21 | dexter.newSplitSwapRequest()
22 | .withSwapInToken('lovelace')
23 | ...
24 | ```
25 |
26 |
27 |
28 |
29 |
30 | withSwapOutToken(Token): SplitSwapRequest
Set which Token in the pool you are swapping out.
31 |
32 | ##### Using
33 |
34 | ```js
35 | dexter.newSplitSwapRequest()
36 | .withSwapOutToken('lovelace')
37 | ...
38 | ```
39 |
40 |
41 |
42 |
43 |
44 | withSwapInAmountMappings(SwapInAmountMapping[]): SplitSwapRequest
Set how much you are swapping in for each DEX.
45 |
46 | ##### Using
47 |
48 | ```js
49 | dexter.newSplitSwapRequest()
50 | .withSwapInAmountMappings([
51 | {
52 | swapInAmount: 2_000000n,
53 | liquidityPool: new LiquidityPool(Minswap.identifier, ...)
54 | },
55 | {
56 | swapInAmount: 5_000000n,
57 | liquidityPool: new LiquidityPool(WingRiders.identifier, ...)
58 | },
59 | ])
60 | ...
61 | ```
62 |
63 |
64 |
65 |
66 |
67 | withSwapOutAmountMappings(SwapOutAmountMapping[]): SplitSwapRequest
Set how much you are swapping out for each DEX.
68 |
69 | ##### Using
70 |
71 | ```js
72 | dexter.newSplitSwapRequest()
73 | .withSwapOutAmountMappings([
74 | {
75 | swapInAmount: 2_000000n,
76 | liquidityPool: new LiquidityPool(Minswap.identifier, ...)
77 | },
78 | {
79 | swapInAmount: 5_000000n,
80 | liquidityPool: new LiquidityPool(WingRiders.identifier, ...)
81 | },
82 | ])
83 | ```
84 |
85 |
86 |
87 |
88 |
89 | flip(): SplitSwapRequest
Flip your swap in and swap out token.
90 |
91 | ##### Using
92 |
93 | ```js
94 | dexter.newSplitSwapRequest()
95 | .flip()
96 | ...
97 | ```
98 |
99 |
100 |
101 |
102 |
103 | withSlippagePercent(number): SplitSwapRequest
Set how much slippage you will tolerate. (Default: 1.0%)
104 |
105 | ##### Using
106 |
107 | ```js
108 | dexter.newSplitSwapRequest()
109 | .withSlippagePercent(0.5)
110 | ...
111 | ```
112 |
113 |
114 |
115 |
116 |
117 | getEstimatedReceive(): bigint
Get the total estimated receive for your swap.
118 |
119 | Will return a sum of the estimated receive for each DEX mapping.
120 |
121 | ##### Using
122 |
123 | ```js
124 | dexter.newSplitSwapRequest()
125 | .getEstimatedReceive()
126 | ```
127 |
128 |
129 |
130 |
131 |
132 | getMinimumReceive(): bigint
Get the total minimum receive for your swap.
133 |
134 | Will return a sum of the minimum receive for each DEX mapping.
135 |
136 | ##### Using
137 |
138 | ```js
139 | dexter.newSplitSwapRequest()
140 | .getMinimumReceive()
141 | ```
142 |
143 |
144 |
145 |
146 |
147 | getAvgPriceImpactPercent(): number
Get the average price impact percentage for your swap.
148 |
149 | Will return the average price impact for each swap on each DEX.
150 |
151 | ##### Using
152 |
153 | ```js
154 | dexter.newSplitSwapRequest()
155 | .getAvgPriceImpactPercent()
156 | ```
157 |
158 |
159 |
160 |
161 |
162 | getSwapFees(): SwapFee[]
Get the DEX specific fees for your swap.
163 |
164 | Will return all swap fees associated with each DEX in the swap.
165 |
166 | ##### Using
167 |
168 | ```js
169 | dexter.newSplitSwapRequest()
170 | .getSwapFees()
171 | ```
172 |
173 |
174 |
175 |
176 |
177 | submit(): DexTransaction
Finally submit your split swap on-chain.
178 |
179 | ##### Using
180 |
181 | ```js
182 | dexter.newSplitSwapRequest()
183 | ...
184 | .submit()
185 | ```
186 |
187 |
--------------------------------------------------------------------------------
/docs/requests/swap-request.md:
--------------------------------------------------------------------------------
1 |
2 |
Swap Request
3 |
4 |
5 | All calculations for your swap parameters are DEX specific. For example, the estimated receive might be different on one DEX
6 | vs another given the same values due to DEXs using different formulas for their calculations.
7 |
8 | ### Obtaining
9 | ```js
10 | dexter.newSwapRequest()
11 | ...
12 | ```
13 |
14 | ### SwapRequest API
15 |
16 |
17 | forLiquidityPool(LiquidityPool): SwapRequest
Set which Liquidity Pool to swap from.
18 |
19 | ##### Using
20 |
21 | ```js
22 | dexter.newSwapRequest()
23 | .forLiquidityPool(new LiquidityPool(...))
24 | ...
25 | ```
26 |
27 |
28 |
29 |
30 |
31 | withSwapInToken(Token): SwapRequest
Set which Token in the pool you are swapping in.
32 |
33 | ##### Using
34 |
35 | ```js
36 | dexter.newSwapRequest()
37 | .withSwapInToken('lovelace')
38 | ...
39 | ```
40 |
41 |
42 |
43 |
44 |
45 | withSwapOutToken(Token): SwapRequest
Set which Token in the pool you are swapping out.
46 |
47 | ##### Using
48 |
49 | ```js
50 | dexter.newSwapRequest()
51 | .withSwapOutToken('lovelace')
52 | ...
53 | ```
54 |
55 |
56 |
57 |
58 |
59 | withSwapInAmount(bigint): SwapRequest
Set how much you are swapping in.
60 |
61 | ##### Using
62 |
63 | ```js
64 | dexter.newSwapRequest()
65 | .withSwapInAmount(10_000000n)
66 | ...
67 | ```
68 |
69 |
70 |
71 |
72 |
73 | withSwapOutAmount(bigint): SwapRequest
Set how much you are swapping out.
74 |
75 | ##### Using
76 |
77 | ```js
78 | dexter.newSwapRequest()
79 | .withSwapOutAmount(10_000000n)
80 | ...
81 | ```
82 |
83 |
84 |
85 |
86 |
87 | withMinimumReceive(bigint): SwapRequest
Set the minimum you want to receive.
88 |
89 | ##### Using
90 |
91 | ```js
92 | dexter.newSwapRequest()
93 | .withMinimumReceive(10_000000n)
94 | ...
95 | ```
96 |
97 |
98 |
99 |
100 |
101 | withSlippagePercent(number): SwapRequest
Set how much slippage you will tolerate. (Default: 1.0%)
102 |
103 | ##### Using
104 |
105 | ```js
106 | dexter.newSwapRequest()
107 | .withSlippagePercent(0.5)
108 | ...
109 | ```
110 |
111 |
112 |
113 |
114 |
115 | flip(): SwapRequest
Flip your swap in and swap out token.
116 |
117 | Flipping will only affect the swap in & swap out token if the swap in token was set beforehand.
118 |
119 | ##### Using
120 |
121 | ```js
122 | dexter.newSwapRequest()
123 | .flip()
124 | ...
125 | ```
126 |
127 |
128 |
129 |
130 |
131 | getEstimatedReceive(LiquidityPool?): bigint
Get the estimated receive for your swap.
132 |
133 | Supplying a liquidity pool will run against the provided pool. This is useful when getting the estimated receive for pools with the
134 | same tokens, but on different DEXs.
135 |
136 | ##### Using
137 |
138 | ```js
139 | dexter.newSwapRequest()
140 | .getEstimatedReceive()
141 | ```
142 |
143 |
144 |
145 |
146 |
147 | getMinimumReceive(LiquidityPool?): bigint
Get the minimum receive for your swap.
148 |
149 | Supplying a liquidity pool will run against the provided pool. This is useful when getting the minimum receive for pools with the
150 | same tokens, but on different DEXs.
151 |
152 | ##### Using
153 |
154 | ```js
155 | dexter.newSwapRequest()
156 | .getMinimumReceive()
157 | ```
158 |
159 |
160 |
161 |
162 |
163 | getPriceImpactPercent(): number
Get the price impact percentage for your swap.
164 |
165 | Supplying a liquidity pool will run against the provided pool. This is useful when getting the minimum receive for pools with the
166 | same tokens, but on different DEXs.
167 |
168 | ##### Using
169 |
170 | ```js
171 | dexter.newSwapRequest()
172 | .getPriceImpactPercent()
173 | ```
174 |
175 |
176 |
177 |
178 |
179 | getSwapFees(): SwapFee[]
Get the DEX specific fees for your swap.
180 |
181 | ##### Using
182 |
183 | ```js
184 | dexter.newSwapRequest()
185 | .getSwapFees()
186 | ```
187 |
188 |
189 |
190 |
191 |
192 | submit(): DexTransaction
Finally submit your swap on-chain.
193 |
194 | ##### Using
195 |
196 | ```js
197 | dexter.newSwapRequest()
198 | ...
199 | .submit()
200 | ```
201 |
202 |
--------------------------------------------------------------------------------
/jest.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest/presets/default-esm',
3 | testEnvironment: 'node',
4 | modulePathIgnorePatterns: [
5 | '/build/',
6 | '/node_modules/',
7 | ],
8 | transform: {
9 | "^.+\\.ts?$": "babel-jest",
10 | },
11 | roots: [''],
12 | modulePaths: [''],
13 | moduleDirectories: ['node_modules'],
14 | moduleNameMapper: {
15 | '@app/(.*)': '/src/$1',
16 | '@dex/(.*)': '/src/dex/$1',
17 | '@providers/(.*)': '/src/providers/$1',
18 | '@requests/(.*)': '/src/requests/$1',
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@indigo-labs/dexter",
3 | "version": "5.4.9",
4 | "license": "MIT",
5 | "author": "Zachary Sluder",
6 | "keywords": [
7 | "dexter",
8 | "Cardano",
9 | "DEX"
10 | ],
11 | "description": "Customizable Typescript SDK for interacting with Cardano DEXs",
12 | "type": "module",
13 | "main": "./build/index.js",
14 | "types": "./build/index.d.ts",
15 | "files": [
16 | "build/**/*"
17 | ],
18 | "scripts": {
19 | "build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json",
20 | "test": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js",
21 | "prepare": "npm run build",
22 | "prepublishOnly": "npm run test"
23 | },
24 | "dependencies": {
25 | "@types/blake2b": "^2.1.3",
26 | "@types/crypto-js": "^4.1.1",
27 | "axios": "^0.26.1",
28 | "axios-retry": "^3.5.1",
29 | "blake2b": "^2.1.4",
30 | "bottleneck": "^2.19.5",
31 | "crypto-js": "^4.1.1",
32 | "int64-buffer": "^1.0.1",
33 | "js-encoding-utils": "^0.7.3",
34 | "lodash": "^4.17.21",
35 | "lucid-cardano": "^0.10.9"
36 | },
37 | "devDependencies": {
38 | "@babel/core": "^7.21.4",
39 | "@babel/preset-env": "^7.21.4",
40 | "@babel/preset-typescript": "^7.21.4",
41 | "@types/jest": "^29.5.0",
42 | "@types/lodash": "^4.14.202",
43 | "babel-jest": "^29.5.0",
44 | "jest": "^29.5.0",
45 | "prettier": "^2.8.8",
46 | "ts-jest": "^29.1.0",
47 | "ts-node": "^10.9.1",
48 | "tsc-alias": "^1.8.6",
49 | "typescript": "^5.0.3"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | export enum MetadataKey {
2 | Message = 674,
3 | }
4 |
5 | export enum DatumParameterKey {
6 | /**
7 | * Generics.
8 | */
9 | Action = 'Action',
10 | TokenPolicyId = 'TokenPolicyId',
11 | TokenAssetName = 'TokenAssetName',
12 | ReserveA = 'ReserveA',
13 | ReserveB = 'ReserveB',
14 | CancelDatum = 'CancelDatum',
15 | AScale = 'AScale',
16 | BScale = 'BScale',
17 |
18 | /**
19 | * Swap/wallet info.
20 | */
21 | Address = 'Address',
22 | SenderPubKeyHash = 'SenderPubKeyHash',
23 | SenderStakingKeyHash = 'SenderStakingKeyHash',
24 | SenderKeyHashes = 'SenderKeyHashes',
25 | ReceiverPubKeyHash = 'ReceiverPubKeyHash',
26 | ReceiverStakingKeyHash = 'ReceiverStakingKeyHash',
27 | SwapInAmount = 'SwapInAmount',
28 | SwapInTokenPolicyId = 'SwapInTokenPolicyId',
29 | SwapInTokenAssetName = 'SwapInTokenAssetName',
30 | SwapOutTokenPolicyId = 'SwapOutTokenPolicyId',
31 | SwapOutTokenAssetName = 'SwapOutTokenAssetName',
32 | MinReceive = 'MinReceive',
33 | Expiration = 'Expiration',
34 | AllowPartialFill = 'AllowPartialFill',
35 | Direction = 'Direction',
36 | FeePaymentKeyHash = 'FeePaymentKeyHash',
37 | Beacon = 'Beacon',
38 | Batcher = 'Batcher',
39 | InToken = 'InToken',
40 |
41 | /**
42 | * Trading fees.
43 | */
44 | TotalFees = 'TotalFees',
45 | BatcherFee = 'BatcherFee',
46 | DepositFee = 'DepositFee',
47 | ScooperFee = 'ScooperFee',
48 | BaseFee = 'BaseFee',
49 | ExecutionFee = 'ExecutionFee',
50 | FeeSharingNumerator = 'FeeSharingNumerator',
51 | OpeningFee = 'OpeningFee',
52 | FinalFee = 'FinalFee',
53 | FeesFinalized = 'FeesFinalized',
54 | MarketOpen = 'MarketOpen',
55 | ProtocolFee = 'ProtocolFee',
56 | SwapFee = 'SwapFee',
57 | ProjectFeeInBasis = 'ProjectFeeInBasis',
58 | ReserveFeeInBasis = 'ReserveFeeInBasis',
59 | FeeBasis = 'FeeBasis',
60 | AgentFee = 'AgentFee',
61 |
62 | /**
63 | * LP info.
64 | */
65 | PoolIdentifier = 'PoolIdentifier',
66 | TotalLpTokens = 'TotalLpTokens',
67 | LpTokenPolicyId = 'LpTokenPolicyId',
68 | LpTokenAssetName = 'LpTokenAssetName',
69 | LpFee = 'LpFee',
70 | LpFeeNumerator = 'LpFeeNumerator',
71 | LpFeeDenominator = 'LpFeeDenominator',
72 | PoolAssetAPolicyId = 'PoolAssetAPolicyId',
73 | PoolAssetAAssetName = 'PoolAssetAAssetName',
74 | PoolAssetATreasury = 'PoolAssetATreasury',
75 | PoolAssetABarFee = 'PoolAssetABarFee',
76 | PoolAssetBPolicyId = 'PoolAssetBPolicyId',
77 | PoolAssetBAssetName = 'PoolAssetBAssetName',
78 | PoolAssetBTreasury = 'PoolAssetBTreasury',
79 | PoolAssetBBarFee = 'PoolAssetBBarFee',
80 | RootKLast = 'RootKLast',
81 | LastInteraction = 'LastInteraction',
82 | RequestScriptHash = 'RequestScriptHash',
83 | StakeAdminPolicy = 'StakeAdminPolicy',
84 | LqBound = 'LqBound',
85 |
86 | Unknown = 'Unknown',
87 | }
88 |
89 | export enum TransactionStatus {
90 | Building,
91 | Signing,
92 | Submitting,
93 | Submitted,
94 | Errored,
95 | }
96 |
97 | export enum AddressType {
98 | Contract,
99 | Base,
100 | Enterprise,
101 | }
102 |
--------------------------------------------------------------------------------
/src/definition-builder.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameters, DefinitionConstr, DefinitionField } from './types';
2 | import { DatumParameterKey } from './constants';
3 | import _ from 'lodash';
4 | import { datumJsonToCbor } from '@app/utils';
5 |
6 | export class DefinitionBuilder {
7 | private _definition: DefinitionConstr;
8 |
9 | /**
10 | * Load a DEX definition file as a template for this builder.
11 | */
12 | public async loadDefinition(definition: DefinitionConstr): Promise {
13 | this._definition = _.cloneDeepWith(definition, (value: any) => {
14 | if (value instanceof Function) {
15 | return value;
16 | }
17 | });
18 |
19 | return this;
20 | }
21 |
22 | /**
23 | * Push specified parameters to the definition template.
24 | */
25 | public pushParameters(parameters: DatumParameters): DefinitionBuilder {
26 | if (!this._definition) {
27 | throw new Error(`Definition file must be loaded before applying parameters`);
28 | }
29 |
30 | this._definition = this.applyParameters(this._definition, parameters);
31 |
32 | return this;
33 | }
34 |
35 | /**
36 | * Pull parameters of a datum using a definition template.
37 | */
38 | public pullParameters(definedDefinition: DefinitionConstr): DatumParameters {
39 | if (!this._definition) {
40 | throw new Error(`Definition file must be loaded before pulling parameters`);
41 | }
42 |
43 | return this.extractParameters(definedDefinition, this._definition);
44 | }
45 |
46 | /**
47 | * Retrieve the CBOR for the builder.
48 | */
49 | public getCbor(): string {
50 | return datumJsonToCbor(JSON.parse(JSON.stringify(this._definition)));
51 | }
52 |
53 | /**
54 | * Recursively set specified parameters.
55 | */
56 | private applyParameters(field: DefinitionField, mappedParameters: DatumParameters): DefinitionConstr {
57 | if (field instanceof Function) {
58 | return field(field, mappedParameters, false);
59 | }
60 |
61 | if ('fields' in field) {
62 | if (typeof field.constructor === 'string') {
63 | const parameterValue: any = mappedParameters[field.constructor as keyof typeof DatumParameterKey];
64 |
65 | if (typeof parameterValue !== 'number') {
66 | throw new Error(`Invalid parameter value '${parameterValue}' for constructor value`);
67 | }
68 |
69 | field.constructor = parameterValue;
70 | }
71 |
72 | field.fields = field.fields.map((fieldParameter: DefinitionField) => {
73 | return this.applyParameters(fieldParameter, mappedParameters);
74 | });
75 | }
76 |
77 | if ('list' in field) {
78 | field.list = (field.list as DefinitionField[])?.map((fieldParameter: DefinitionField) => {
79 | return this.applyParameters(fieldParameter, mappedParameters);
80 | });
81 | }
82 |
83 | if ('int' in field) {
84 | let parameterValue: any = mappedParameters[field.int as keyof typeof DatumParameterKey];
85 |
86 | if (typeof parameterValue === 'bigint') {
87 | parameterValue = Number(parameterValue);
88 | }
89 | if (typeof parameterValue !== 'number') {
90 | throw new Error(`Invalid parameter value '${parameterValue}' for type 'int'`);
91 | }
92 |
93 | field.int = parameterValue;
94 | }
95 |
96 | if ('bytes' in field) {
97 | const parameterValue: any = mappedParameters[field.bytes as keyof typeof DatumParameterKey] ?? '';
98 |
99 | if (typeof parameterValue !== 'string') {
100 | throw new Error(`Invalid parameter value '${parameterValue}' for type 'bytes'`);
101 | }
102 |
103 | field.bytes = parameterValue;
104 | }
105 |
106 | if (Array.isArray(field) && field.every((item) => typeof item === 'object' && Object.keys(item).length === 1)) {
107 | field.forEach((value) => {
108 | return this.applyParameters(value, mappedParameters);
109 | });
110 | }
111 |
112 | return field as DefinitionConstr;
113 | }
114 |
115 | /**
116 | * Recursively pull parameters from datum using definition template.
117 | */
118 | private extractParameters(definedDefinition: DefinitionField, templateDefinition: DefinitionField, foundParameters: DatumParameters = {}): DatumParameters {
119 | if (! templateDefinition) return foundParameters;
120 |
121 | if (templateDefinition instanceof Function) {
122 | templateDefinition(definedDefinition, foundParameters);
123 |
124 | return foundParameters;
125 | }
126 |
127 | if (templateDefinition instanceof Array) {
128 | templateDefinition
129 | .map((fieldParameter: DefinitionField, index: number) => {
130 | return this.extractParameters(fieldParameter, templateDefinition[index], foundParameters);
131 | })
132 | .forEach((parameters: DatumParameters) => {
133 | foundParameters = { ...foundParameters, ...parameters };
134 | });
135 | }
136 |
137 | if ('fields' in definedDefinition) {
138 | if (!('fields' in templateDefinition)) {
139 | throw new Error("Template definition does not match with 'fields'");
140 | }
141 |
142 | if (templateDefinition.constructor && typeof templateDefinition.constructor !== 'number') {
143 | foundParameters[templateDefinition.constructor] = definedDefinition.constructor;
144 | } else if (templateDefinition.constructor !== definedDefinition.constructor) {
145 | throw new Error('Template definition does not match with constructor value');
146 | }
147 |
148 | definedDefinition.fields
149 | .map((fieldParameter: DefinitionField, index: number) => {
150 | return this.extractParameters(fieldParameter, templateDefinition.fields[index], foundParameters);
151 | })
152 | .forEach((parameters: DatumParameters) => {
153 | foundParameters = { ...foundParameters, ...parameters };
154 | });
155 | }
156 |
157 | if ('list' in definedDefinition) {
158 | if (!('list' in templateDefinition) || !Array.isArray(definedDefinition.list)) {
159 | throw new Error("Template definition does not match or 'list' is not an array");
160 | }
161 |
162 | (definedDefinition.list as DefinitionField[])
163 | .map((fieldParameter: DefinitionField, index: number) => {
164 | // Ensure the template list at index exists before attempting to access it
165 | if (templateDefinition.list as DefinitionField[]) {
166 | return this.extractParameters(fieldParameter, (templateDefinition.list as DefinitionField[])[index], foundParameters);
167 | } else {
168 | throw new Error(`Template definition at index ${index} is undefined`);
169 | }
170 | })
171 | .forEach((parameters: any) => {
172 | foundParameters = { ...foundParameters, ...parameters };
173 | });
174 | }
175 |
176 | if ('int' in definedDefinition) {
177 | if (!('int' in templateDefinition)) {
178 | throw new Error("Template definition does not match with 'int'");
179 | }
180 |
181 | if (typeof templateDefinition.int !== 'number') {
182 | foundParameters[templateDefinition.int] = definedDefinition.int;
183 | }
184 | }
185 |
186 | if ('bytes' in definedDefinition) {
187 | if (!('bytes' in templateDefinition)) {
188 | throw new Error("Template definition does not match with 'bytes'");
189 | }
190 |
191 | const datumKeys: string[] = Object.values(DatumParameterKey);
192 |
193 | if (datumKeys.includes(templateDefinition.bytes)) {
194 | foundParameters[templateDefinition.bytes] = definedDefinition.bytes;
195 | }
196 | }
197 |
198 | return foundParameters;
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/dex/api/base-api.ts:
--------------------------------------------------------------------------------
1 | import { Token } from '../models/asset';
2 | import { LiquidityPool } from '../models/liquidity-pool';
3 | import { BaseDex } from '../base-dex';
4 | import { AxiosInstance } from 'axios';
5 |
6 | export abstract class BaseApi {
7 |
8 | protected abstract readonly api: AxiosInstance;
9 | protected abstract readonly dex: BaseDex;
10 |
11 | /**
12 | * Fetch all liquidity pools matching assetA & assetB.
13 | */
14 | abstract liquidityPools(assetA?: Token, assetB?: Token): Promise;
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/dex/api/minswap-api.ts:
--------------------------------------------------------------------------------
1 | import { BaseApi } from './base-api';
2 | import { Asset, Token } from '../models/asset';
3 | import { LiquidityPool } from '../models/liquidity-pool';
4 | import axios, { AxiosInstance } from 'axios';
5 | import { Minswap } from '../minswap';
6 | import { RequestConfig } from '@app/types';
7 | import AES from 'crypto-js/aes';
8 | import Utf8 from 'crypto-js/enc-utf8';
9 | import { appendSlash } from '@app/utils';
10 |
11 | const AES_KEY: string = '22eaca439bfd89cf125827a7a33fe3970d735dbfd5d84f19dd95820781fc47be';
12 |
13 | export class MinswapApi extends BaseApi {
14 |
15 | protected readonly api: AxiosInstance;
16 | protected readonly dex: Minswap;
17 |
18 | constructor(dex: Minswap, requestConfig: RequestConfig) {
19 | super();
20 |
21 | this.dex = dex;
22 |
23 | this.api = axios.create({
24 | timeout: requestConfig.timeout,
25 | baseURL: `${appendSlash(requestConfig.proxyUrl)}https://monorepo-mainnet-prod.minswap.org/graphql`,
26 | withCredentials: false,
27 | });
28 | }
29 |
30 | liquidityPools(assetA: Token, assetB?: Token): Promise {
31 | // Small optimization for providing both tokens
32 | if (assetA && assetB) {
33 | return this.poolsByPair(assetA, assetB)
34 | .then((pool: LiquidityPool) => [pool]);
35 | }
36 |
37 | const maxPerPage: number = 20;
38 |
39 | const getPaginatedResponse = (page: number): Promise => {
40 | return this.api.post('', {
41 | operationName: 'PoolsByAsset',
42 | query: `
43 | query PoolsByAsset($asset: InputAsset!, $limit: Int, $offset: Int) {
44 | poolsByAsset(
45 | asset: $asset
46 | limit: $limit
47 | offset: $offset
48 | ) {
49 | assetA {
50 | currencySymbol
51 | tokenName
52 | ...allMetadata
53 | }
54 | assetB {
55 | currencySymbol
56 | tokenName
57 | ...allMetadata
58 | }
59 | reserveA
60 | reserveB
61 | lpAsset {
62 | currencySymbol
63 | tokenName
64 | }
65 | totalLiquidity
66 | }
67 | }
68 | fragment allMetadata on Asset {
69 | metadata {
70 | name
71 | decimals
72 | }
73 | }
74 | `,
75 | variables: {
76 | asset: {
77 | currencySymbol: assetA === 'lovelace' ? '' : assetA.policyId,
78 | tokenName: assetA === 'lovelace' ? '' : assetA.nameHex,
79 | },
80 | limit: maxPerPage,
81 | offset: page * maxPerPage,
82 | },
83 | }).then((response: any) => {
84 | response = JSON.parse(this.decryptResponse(response.data.data.encryptedData));
85 |
86 | const pools = response.poolsByAsset;
87 |
88 | const liquidityPools = pools.map((pool: any) => this.liquidityPoolFromResponse(pool));
89 |
90 | if (pools.length < maxPerPage) {
91 | return liquidityPools;
92 | }
93 |
94 | return getPaginatedResponse(page + 1).then((nextPagePools: LiquidityPool[]) => {
95 | return liquidityPools.concat(nextPagePools);
96 | });
97 | });
98 | };
99 |
100 | return getPaginatedResponse(0);
101 | }
102 |
103 | private poolsByPair(assetA: Token, assetB: Token): Promise {
104 | return this.api.post('', {
105 | operationName: 'PoolByPair',
106 | query: `
107 | query PoolByPair($pair: InputPoolByPair!) {
108 | poolByPair(pair: $pair) {
109 | assetA {
110 | currencySymbol
111 | tokenName
112 | isVerified
113 | ...allMetadata
114 | }
115 | assetB {
116 | currencySymbol
117 | tokenName
118 | isVerified
119 | ...allMetadata
120 | }
121 | reserveA
122 | reserveB
123 | lpAsset {
124 | currencySymbol
125 | tokenName
126 | }
127 | totalLiquidity
128 | profitSharing {
129 | feeTo
130 | }
131 | }
132 | }
133 | fragment allMetadata on Asset {
134 | metadata {
135 | name
136 | ticker
137 | url
138 | decimals
139 | description
140 | }
141 | }
142 | `,
143 | variables: {
144 | pair: {
145 | assetA: {
146 | currencySymbol: assetA === 'lovelace' ? '' : assetA.policyId,
147 | tokenName: assetA === 'lovelace' ? '' : assetA.nameHex,
148 | },
149 | assetB: {
150 | currencySymbol: assetB === 'lovelace' ? '' : assetB.policyId,
151 | tokenName: assetB === 'lovelace' ? '' : assetB.nameHex,
152 | },
153 | },
154 | },
155 | }).then((response: any) => {
156 | response = JSON.parse(this.decryptResponse(response.data.data.encryptedData));
157 |
158 | return this.liquidityPoolFromResponse(response.poolByPair)
159 | });
160 | }
161 |
162 | private liquidityPoolFromResponse(poolData: any): LiquidityPool {
163 | const liquidityPool: LiquidityPool = new LiquidityPool(
164 | Minswap.identifier,
165 | poolData.assetA.currencySymbol !== ''
166 | ? new Asset(poolData.assetA.currencySymbol, poolData.assetA.tokenName, poolData.assetA.metadata?.decimals ?? 0)
167 | : 'lovelace',
168 | poolData.assetB.currencySymbol !== ''
169 | ? new Asset(poolData.assetB.currencySymbol, poolData.assetB.tokenName, poolData.assetB.metadata?.decimals ?? 0)
170 | : 'lovelace',
171 | BigInt(poolData.reserveA),
172 | BigInt(poolData.reserveB),
173 | '', // Not provided
174 | this.dex.marketOrderAddress,
175 | this.dex.limitOrderAddress,
176 | );
177 |
178 | liquidityPool.lpToken = new Asset(poolData.lpAsset.currencySymbol, poolData.lpAsset.tokenName);
179 | liquidityPool.totalLpTokens = BigInt(poolData.totalLiquidity);
180 | liquidityPool.poolFeePercent = 0.3;
181 | liquidityPool.identifier = liquidityPool.lpToken.identifier();
182 |
183 | return liquidityPool;
184 | }
185 |
186 | private decryptResponse(encryptedResponse: string): any {
187 | return AES.decrypt(encryptedResponse, AES_KEY).toString(Utf8);
188 | }
189 |
190 | }
191 |
--------------------------------------------------------------------------------
/src/dex/api/muesliswap-api.ts:
--------------------------------------------------------------------------------
1 | import { BaseApi } from './base-api';
2 | import { Asset, Token } from '../models/asset';
3 | import { LiquidityPool } from '../models/liquidity-pool';
4 | import axios, { AxiosInstance } from 'axios';
5 | import { MuesliSwap } from '../muesliswap';
6 | import { RequestConfig } from '@app/types';
7 | import { appendSlash } from '@app/utils';
8 |
9 | export class MuesliSwapApi extends BaseApi {
10 |
11 | protected readonly api: AxiosInstance;
12 | protected readonly dex: MuesliSwap;
13 |
14 | constructor(dex: MuesliSwap, requestConfig: RequestConfig) {
15 | super();
16 |
17 | this.dex = dex;
18 | this.api = axios.create({
19 | timeout: requestConfig.timeout,
20 | baseURL: `${appendSlash(requestConfig.proxyUrl)}https://api.muesliswap.com/`,
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | }
24 | });
25 | }
26 |
27 | liquidityPools(assetA: Token, assetB?: Token): Promise {
28 | const providers: string[] = ['muesliswap', 'muesliswap_v2', 'muesliswap_clp'];
29 | const tokenA: string = (assetA === 'lovelace')
30 | ? '.'
31 | : assetA.identifier('.');
32 | const tokenB: string = (assetB && assetB !== 'lovelace')
33 | ? assetB.identifier('.')
34 | : '';
35 |
36 | return this.api.get(`/liquidity/pools?providers=${providers.join(',')}&token-a=${tokenA}&token-b=${tokenB}`)
37 | .then((response: any) => {
38 | return response.data.map((pool: any) => {
39 | let liquidityPool: LiquidityPool = new LiquidityPool(
40 | MuesliSwap.identifier,
41 | pool.tokenA.symbol !== 'ADA'
42 | ? new Asset(pool.tokenA.address.policyId, pool.tokenA.address.name, pool.tokenA.decimalPlaces)
43 | : 'lovelace',
44 | pool.tokenB.symbol !== 'ADA'
45 | ? new Asset(pool.tokenB.address.policyId, pool.tokenB.address.name, pool.tokenB.decimalPlaces)
46 | : 'lovelace',
47 | BigInt(pool.tokenA.amount),
48 | BigInt(pool.tokenB.amount),
49 | pool.batcherAddress,
50 | this.dex.orderAddress,
51 | this.dex.orderAddress,
52 | );
53 |
54 | liquidityPool.identifier = pool.poolId;
55 | liquidityPool.lpToken = new Asset(pool.lpToken.address.policyId, pool.lpToken.address.name);
56 | liquidityPool.poolFeePercent = Number(pool.poolFee);
57 | liquidityPool.totalLpTokens = BigInt(pool.lpToken.amount);
58 |
59 | return liquidityPool;
60 | });
61 | });
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/dex/api/splash-api.ts:
--------------------------------------------------------------------------------
1 | import { BaseApi } from './base-api';
2 | import { Asset, Token } from '../models/asset';
3 | import { LiquidityPool } from '../models/liquidity-pool';
4 | import axios, { AxiosInstance } from 'axios';
5 | import { RequestConfig } from '@app/types';
6 | import { appendSlash } from '@app/utils';
7 | import { Splash } from '@dex/splash';
8 |
9 | const MAX_INT: bigint = 9_223_372_036_854_775_807n;
10 |
11 | export class SplashApi extends BaseApi {
12 |
13 | protected readonly api: AxiosInstance;
14 | protected readonly dex: Splash;
15 |
16 | constructor(dex: Splash, requestConfig: RequestConfig) {
17 | super();
18 |
19 | this.dex = dex;
20 |
21 | this.api = axios.create({
22 | timeout: requestConfig.timeout,
23 | baseURL: `${appendSlash(requestConfig.proxyUrl)}https://api5.splash.trade/platform-api/v1/`,
24 | withCredentials: false,
25 | });
26 | }
27 |
28 | async liquidityPools(assetA: Token, assetB?: Token): Promise {
29 | const assets: any = (await this.assets()).data['tokens'];
30 |
31 | return this.api.get('/pools/overview?verified=false&duplicated=false').then((response: any) => {
32 | return response.data.map((pool: any) => this.liquidityPoolFromResponse(pool, assets)) as LiquidityPool[];
33 | });
34 | }
35 |
36 | private liquidityPoolFromResponse(poolData: any, assets: any): LiquidityPool {
37 | poolData = poolData.pool;
38 |
39 | const tokenA: Token = poolData.x.asset === '.'
40 | ? 'lovelace'
41 | : new Asset(poolData.x.asset.split('.')[0], poolData.x.asset.split('.')[1]);
42 | const tokenB = poolData.y.asset === '.'
43 | ? 'lovelace'
44 | : new Asset(poolData.y.asset.split('.')[0], poolData.y.asset.split('.')[1]);
45 |
46 | if (tokenA !== 'lovelace' && tokenA.identifier('.') in assets) {
47 | tokenA.decimals = assets[tokenA.identifier('.')].decimals;
48 | }
49 | if (tokenB !== 'lovelace' && tokenB.identifier('.') in assets) {
50 | tokenB.decimals = assets[tokenB.identifier('.')].decimals;
51 | }
52 |
53 | const liquidityPool: LiquidityPool = new LiquidityPool(
54 | Splash.identifier,
55 | tokenA,
56 | tokenB,
57 | BigInt(poolData['x']['amount']) - BigInt(poolData['treasuryX']),
58 | BigInt(poolData['y']['amount']) - BigInt(poolData['treasuryY']),
59 | '',
60 | '',
61 | '',
62 | );
63 |
64 | const [lpTokenPolicyId, lpTokenAssetName] = poolData['lq']['asset'].split('.');
65 |
66 | liquidityPool.lpToken = new Asset(lpTokenPolicyId, lpTokenAssetName);
67 | liquidityPool.totalLpTokens = MAX_INT - BigInt(poolData['lq']['amount']);
68 | liquidityPool.identifier = poolData['id'];
69 |
70 | return liquidityPool;
71 | }
72 |
73 | private assets(): Promise {
74 | return axios.get('https://spectrum.fi/cardano-token-list-v2.json');
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/dex/api/sundaeswap-v1-api.ts:
--------------------------------------------------------------------------------
1 | import { BaseApi } from './base-api';
2 | import { Asset, Token } from '../models/asset';
3 | import { LiquidityPool } from '../models/liquidity-pool';
4 | import axios, { AxiosInstance } from 'axios';
5 | import { SundaeSwapV1 } from '../sundaeswap-v1';
6 | import { RequestConfig } from '@app/types';
7 | import { appendSlash } from '@app/utils';
8 |
9 | export class SundaeSwapV1Api extends BaseApi {
10 |
11 | protected readonly api: AxiosInstance;
12 | protected readonly dex: SundaeSwapV1;
13 |
14 | constructor(dex: SundaeSwapV1, requestConfig: RequestConfig) {
15 | super();
16 |
17 | this.dex = dex;
18 | this.api = axios.create({
19 | timeout: requestConfig.timeout,
20 | baseURL: `${appendSlash(requestConfig.proxyUrl)}https://stats.sundaeswap.finance/graphql`,
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | }
24 | });
25 | }
26 |
27 | liquidityPools(assetA: Token, assetB?: Token): Promise {
28 | const maxPerPage: number = 100;
29 |
30 | const assetAId: string = (assetA === 'lovelace')
31 | ? ''
32 | : assetA.identifier('.');
33 | let assetBId: string = (assetB && assetB !== 'lovelace')
34 | ? assetB.identifier('.')
35 | : '';
36 |
37 | const getPaginatedResponse = (page: number): Promise => {
38 | return this.api.post('', {
39 | operationName: 'getPoolsByAssetIds',
40 | query: `
41 | query getPoolsByAssetIds($assetIds: [String!]!, $pageSize: Int, $page: Int) {
42 | pools(assetIds: $assetIds, pageSize: $pageSize, page: $page) {
43 | ...PoolFragment
44 | }
45 | }
46 | fragment PoolFragment on Pool {
47 | assetA {
48 | ...AssetFragment
49 | }
50 | assetB {
51 | ...AssetFragment
52 | }
53 | assetLP {
54 | ...AssetFragment
55 | }
56 | name
57 | fee
58 | quantityA
59 | quantityB
60 | quantityLP
61 | ident
62 | assetID
63 | }
64 | fragment AssetFragment on Asset {
65 | assetId
66 | decimals
67 | }
68 | `,
69 | variables: {
70 | page: page,
71 | pageSize: maxPerPage,
72 | assetIds: [assetBId !== '' ? assetBId : assetAId],
73 | },
74 | }).then((response: any) => {
75 | const pools = response.data.data.pools;
76 | const liquidityPools = pools.map((pool: any) => {
77 | let liquidityPool: LiquidityPool = new LiquidityPool(
78 | SundaeSwapV1.identifier,
79 | pool.assetA.assetId
80 | ? Asset.fromIdentifier(pool.assetA.assetId, pool.assetA.decimals)
81 | : 'lovelace',
82 | pool.assetB.assetId
83 | ? Asset.fromIdentifier(pool.assetB.assetId, pool.assetB.decimals)
84 | : 'lovelace',
85 | BigInt(pool.quantityA),
86 | BigInt(pool.quantityB),
87 | this.dex.poolAddress,
88 | this.dex.orderAddress,
89 | this.dex.orderAddress,
90 | );
91 |
92 | liquidityPool.identifier = pool.ident;
93 | liquidityPool.lpToken = Asset.fromIdentifier(pool.assetLP.assetId);
94 | liquidityPool.poolFeePercent = Number(pool.fee);
95 | liquidityPool.totalLpTokens = BigInt(pool.quantityLP);
96 |
97 | return liquidityPool;
98 | });
99 |
100 | if (pools.length < maxPerPage) {
101 | return liquidityPools;
102 | }
103 |
104 | return getPaginatedResponse(page + 1).then((nextPagePools: LiquidityPool[]) => {
105 | return liquidityPools.concat(nextPagePools);
106 | });
107 | });
108 | };
109 |
110 | return getPaginatedResponse(0);
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/dex/api/sundaeswap-v3-api.ts:
--------------------------------------------------------------------------------
1 | import { BaseApi } from './base-api';
2 | import { Asset, Token } from '../models/asset';
3 | import { LiquidityPool } from '../models/liquidity-pool';
4 | import axios, { AxiosInstance } from 'axios';
5 | import { RequestConfig } from '@app/types';
6 | import { appendSlash } from '@app/utils';
7 | import { SundaeSwapV3 } from '@dex/sundaeswap-v3';
8 |
9 | export class SundaeSwapV3Api extends BaseApi {
10 |
11 | protected readonly api: AxiosInstance;
12 | protected readonly dex: SundaeSwapV3;
13 |
14 | constructor(dex: SundaeSwapV3, requestConfig: RequestConfig) {
15 | super();
16 |
17 | this.dex = dex;
18 | this.api = axios.create({
19 | timeout: requestConfig.timeout,
20 | baseURL: `${appendSlash(requestConfig.proxyUrl)}https://api.sundae.fi/graphql`,
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | }
24 | });
25 | }
26 |
27 | async liquidityPools(assetA: Token, assetB?: Token): Promise {
28 | const assetAId: string = (assetA === 'lovelace')
29 | ? 'ada.lovelace'
30 | : assetA.identifier('.');
31 | const assetBId: string = (assetB && assetB !== 'lovelace')
32 | ? assetB.identifier('.')
33 | : 'ada.lovelace';
34 | const assets: string[] = [assetAId, assetBId];
35 |
36 | return await this.api.post('', {
37 | operationName: 'fetchPoolsByPair',
38 | query: `query fetchPoolsByPair($assetA: ID!, $assetB: ID!) {\n pools {\n byPair(assetA: $assetA, assetB: $assetB) {\n ...PoolBrambleFragment\n }\n }\n}\n\nfragment PoolBrambleFragment on Pool {\n id\n assetA {\n ...AssetBrambleFragment\n }\n assetB {\n ...AssetBrambleFragment\n }\n assetLP {\n ...AssetBrambleFragment\n }\n feesFinalized {\n slot\n }\n marketOpen {\n slot\n }\n askFee\n bidFee\n feeManagerId\n current {\n quantityA {\n quantity\n }\n quantityB {\n quantity\n }\n quantityLP {\n quantity\n }\n tvl {\n quantity\n }\n }\n version\n}\n\nfragment AssetBrambleFragment on Asset {\n id\n policyId\n description\n dateListed {\n format\n }\n decimals\n ticker\n name\n logo\n assetName\n metadata {\n ... on OnChainLabel20 {\n __typename\n }\n ... on OnChainLabel721 {\n __typename\n }\n ... on CardanoTokenRegistry {\n __typename\n }\n }\n}`,
39 | variables: {
40 | assetA: assets[0],
41 | assetB: assets[1],
42 | },
43 | }).then((response: any) => {
44 | const pools = response.data.data.pools.byPair;
45 |
46 | return pools
47 | .filter((pool: any) => pool.version === 'V3')
48 | .map((pool: any) => {
49 | let liquidityPool: LiquidityPool = new LiquidityPool(
50 | SundaeSwapV3.identifier,
51 | pool.assetA.id === 'ada.lovelace'
52 | ? 'lovelace'
53 | : Asset.fromIdentifier(pool.assetA.id, pool.assetA.decimals),
54 | pool.assetB.id === 'ada.lovelace'
55 | ? 'lovelace'
56 | : Asset.fromIdentifier(pool.assetB.id, pool.assetB.decimals),
57 | BigInt(pool.current.quantityA.quantity),
58 | BigInt(pool.current.quantityB.quantity),
59 | this.dex.poolAddress,
60 | '',
61 | '',
62 | );
63 |
64 | liquidityPool.identifier = pool.id;
65 | liquidityPool.lpToken = Asset.fromIdentifier(pool.assetLP.id);
66 | liquidityPool.poolFeePercent = Number((pool.bidFee[0] / pool.bidFee[1]) * 100);
67 | liquidityPool.totalLpTokens = BigInt(pool.current.quantityLP.quantity);
68 |
69 | return liquidityPool;
70 | });
71 | });
72 |
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/dex/api/vyfinance-api.ts:
--------------------------------------------------------------------------------
1 | import { BaseApi } from './base-api';
2 | import { Asset, Token } from '../models/asset';
3 | import { LiquidityPool } from '../models/liquidity-pool';
4 | import axios, { AxiosInstance } from 'axios';
5 | import { VyFinance } from '../vyfinance';
6 | import { RequestConfig } from '@app/types';
7 | import { appendSlash } from '@app/utils';
8 |
9 | export class VyfinanceApi extends BaseApi {
10 |
11 | protected readonly api: AxiosInstance;
12 | protected readonly dex: VyFinance;
13 |
14 | constructor(dex: VyFinance, requestConfig: RequestConfig) {
15 | super();
16 |
17 | this.dex = dex;
18 | this.api = axios.create({
19 | timeout: requestConfig.timeout,
20 | baseURL: `${appendSlash(requestConfig.proxyUrl)}https://api.vyfi.io`,
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | }
24 | });
25 | }
26 |
27 | liquidityPools(assetA?: Token, assetB?: Token): Promise {
28 | const assetAId: string = (assetA && assetA !== 'lovelace')
29 | ? assetA.identifier()
30 | : 'lovelace';
31 | let assetBId: string = (assetB && assetB !== 'lovelace')
32 | ? assetB.identifier()
33 | : 'lovelace';
34 |
35 | const url: string = assetA && assetB
36 | ? `/lp?networkId=1&v2=true&tokenAUnit=${assetAId}&tokenBUnit=${assetBId}`
37 | : '/lp?networkId=1&v2=true';
38 |
39 | return this.api.get(url)
40 | .then((poolResponse: any) => {
41 | return poolResponse.data.map((pool: any) => {
42 | const poolDetails: any = JSON.parse(pool.json);
43 |
44 | const tokenA: Token = poolDetails['aAsset']['tokenName']
45 | ? new Asset(poolDetails['aAsset']['currencySymbol'], Buffer.from(poolDetails['aAsset']['tokenName']).toString('hex'))
46 | : 'lovelace';
47 | const tokenB: Token = poolDetails['bAsset']['tokenName']
48 | ? new Asset(poolDetails['bAsset']['currencySymbol'], Buffer.from(poolDetails['bAsset']['tokenName']).toString('hex'))
49 | : 'lovelace';
50 |
51 |
52 | let liquidityPool: LiquidityPool = new LiquidityPool(
53 | VyFinance.identifier,
54 | tokenA,
55 | tokenB,
56 | BigInt(pool['tokenAQuantity'] ?? 0),
57 | BigInt(pool['tokenBQuantity'] ?? 0),
58 | pool['poolValidatorUtxoAddress'],
59 | pool['orderValidatorUtxoAddress'],
60 | pool['orderValidatorUtxoAddress'],
61 | );
62 |
63 | const lpTokenDetails: string[] = pool['lpPolicyId-assetId'].split('-');
64 | liquidityPool.lpToken = new Asset(lpTokenDetails[0], lpTokenDetails[1]);
65 | liquidityPool.poolFeePercent = (poolDetails['feesSettings']['barFee'] + poolDetails['feesSettings']['liqFee']) / 100;
66 | liquidityPool.identifier = liquidityPool.lpToken.identifier();
67 | liquidityPool.extra.nft = new Asset(poolDetails['mainNFT']['currencySymbol'], poolDetails['mainNFT']['tokenName']);
68 |
69 | return liquidityPool;
70 | }).filter((pool: LiquidityPool | undefined) => pool !== undefined) as LiquidityPool[];
71 | }).catch((e) => {
72 | console.error(e)
73 | return [];
74 | });
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/dex/api/wingriders-api.ts:
--------------------------------------------------------------------------------
1 | import { BaseApi } from './base-api';
2 | import { Asset, Token } from '../models/asset';
3 | import { LiquidityPool } from '../models/liquidity-pool';
4 | import axios, { AxiosInstance } from 'axios';
5 | import { RequestConfig } from '@app/types';
6 | import { WingRiders } from '@dex/wingriders';
7 | import { appendSlash } from '@app/utils';
8 |
9 | export class WingRidersApi extends BaseApi {
10 |
11 | protected readonly api: AxiosInstance;
12 | protected readonly dex: WingRiders;
13 |
14 | constructor(dex: WingRiders, requestConfig: RequestConfig) {
15 | super();
16 |
17 | this.dex = dex;
18 | this.api = axios.create({
19 | timeout: requestConfig.timeout,
20 | baseURL: `${appendSlash(requestConfig.proxyUrl)}https://api.mainnet.wingriders.com/graphql`,
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | }
24 | });
25 | }
26 |
27 | liquidityPools(assetA: Token, assetB?: Token): Promise {
28 | return this.api.post('', {
29 | operationName: 'LiquidityPoolsWithMarketData',
30 | query: `
31 | query LiquidityPoolsWithMarketData($input: PoolsWithMarketdataInput) {
32 | poolsWithMarketdata(input: $input) {
33 | ...LiquidityPoolFragment
34 | }
35 | }
36 | fragment LiquidityPoolFragment on PoolWithMarketdata {
37 | issuedShareToken {
38 | policyId
39 | assetName
40 | quantity
41 | }
42 | tokenA {
43 | policyId
44 | assetName
45 | quantity
46 | }
47 | tokenB {
48 | policyId
49 | assetName
50 | quantity
51 | }
52 | treasuryA
53 | treasuryB
54 | _utxo {
55 | address
56 | }
57 | }
58 | `,
59 | variables: {
60 | input: {
61 | sort: true
62 | },
63 | },
64 | }).then((response: any) => {
65 | return response.data.data.poolsWithMarketdata.map((pool: any) => {
66 | const tokenA: Token = pool.tokenA.policyId !== ''
67 | ? new Asset(pool.tokenA.policyId, pool.tokenA.assetName)
68 | : 'lovelace';
69 | const tokenB: Token = pool.tokenB.policyId !== ''
70 | ? new Asset(pool.tokenB.policyId, pool.tokenB.assetName)
71 | : 'lovelace';
72 |
73 | let liquidityPool: LiquidityPool = new LiquidityPool(
74 | WingRiders.identifier,
75 | tokenA,
76 | tokenB,
77 | BigInt(pool.tokenA.quantity) - BigInt(pool.treasuryA),
78 | BigInt(pool.tokenB.quantity) - BigInt(pool.treasuryB),
79 | pool._utxo.address,
80 | this.dex.orderAddress,
81 | this.dex.orderAddress,
82 | );
83 |
84 | liquidityPool.lpToken = new Asset(pool.issuedShareToken.policyId, pool.issuedShareToken.assetName);
85 | liquidityPool.poolFeePercent = 0.35;
86 | liquidityPool.identifier = liquidityPool.lpToken.identifier();
87 | liquidityPool.totalLpTokens = BigInt(pool.issuedShareToken.quantity);
88 |
89 | return liquidityPool;
90 | }).filter((pool: LiquidityPool | undefined) => pool !== undefined);
91 | });
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/dex/base-dex.ts:
--------------------------------------------------------------------------------
1 | import { LiquidityPool } from './models/liquidity-pool';
2 | import { BaseDataProvider } from '@providers/data/base-data-provider';
3 | import { Asset, Token } from './models/asset';
4 | import { AssetBalance, DatumParameters, PayToAddress, SpendUTxO, SwapFee, UTxO } from '@app/types';
5 | import { DatumParameterKey } from '@app/constants';
6 | import { tokensMatch } from '@app/utils';
7 | import { BaseApi } from '@dex/api/base-api';
8 | import { BaseWalletProvider } from '@providers/wallet/base-wallet-provider';
9 |
10 | export abstract class BaseDex {
11 |
12 | /**
13 | * API connection for the DEX.
14 | */
15 | public abstract readonly api: BaseApi;
16 |
17 | /**
18 | * Fetch addresses mapped to a liquidity pool.
19 | */
20 | abstract liquidityPoolAddresses(provider?: BaseDataProvider): Promise;
21 |
22 | /**
23 | * Fetch all liquidity pools.
24 | */
25 | abstract liquidityPools(provider: BaseDataProvider, wallet?: BaseWalletProvider): Promise;
26 |
27 | /**
28 | * Craft liquidity pool state from a valid UTxO.
29 | */
30 | abstract liquidityPoolFromUtxo(provider: BaseDataProvider, utxo: UTxO, wallet?: BaseWalletProvider): Promise;
31 |
32 | /**
33 | * Estimated swap in amount given for a swap out token & amount on a liquidity pool.
34 | */
35 | abstract estimatedGive(liquidityPool: LiquidityPool, swapOutToken: Token, swapOutAmount: bigint): bigint;
36 |
37 | /**
38 | * Estimated swap out amount received for a swap in token & amount on a liquidity pool.
39 | */
40 | abstract estimatedReceive(liquidityPool: LiquidityPool, swapInToken: Token, swapInAmount: bigint): bigint;
41 |
42 | /**
43 | * Calculated price impact after for swap order.
44 | */
45 | abstract priceImpactPercent(liquidityPool: LiquidityPool, swapInToken: Token, swapInAmount: bigint): number;
46 |
47 | /**
48 | * Craft a swap order for this DEX.
49 | */
50 | abstract buildSwapOrder(liquidityPool: LiquidityPool, swapParameters: DatumParameters, spendUtxos?: SpendUTxO[], dataProvider?: BaseDataProvider): Promise;
51 |
52 | /**
53 | * Craft a swap order cancellation for this DEX.
54 | */
55 | abstract buildCancelSwapOrder(txOutputs: UTxO[], returnAddress: string, wallet?: BaseWalletProvider): Promise;
56 |
57 | /**
58 | * Fees associated with submitting a swap order.
59 | */
60 | abstract swapOrderFees(liquidityPool?: LiquidityPool, swapInToken?: Token, swapInAmount?: bigint): SwapFee[];
61 |
62 | /**
63 | * Adjust the payment for the DEX order address to include the swap in amount.
64 | */
65 | protected buildSwapOrderPayment(swapParameters: DatumParameters, orderPayment: PayToAddress): PayToAddress {
66 | const swapInAmount: bigint = swapParameters[DatumParameterKey.SwapInAmount] as bigint;
67 | const swapInToken: Token = swapParameters[DatumParameterKey.SwapInTokenPolicyId]
68 | ? new Asset(
69 | swapParameters[DatumParameterKey.SwapInTokenPolicyId] as string,
70 | swapParameters[DatumParameterKey.SwapInTokenAssetName] as string,
71 | )
72 | : 'lovelace';
73 |
74 | let assetBalance: AssetBalance | undefined = orderPayment.assetBalances.find((payment: AssetBalance) => {
75 | return tokensMatch(payment.asset, swapInToken);
76 | });
77 |
78 | if (! assetBalance) {
79 | orderPayment.assetBalances.push({
80 | asset: swapInToken,
81 | quantity: swapInAmount,
82 | });
83 | } else {
84 | assetBalance.quantity += swapInAmount;
85 | }
86 |
87 | return orderPayment;
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/dex/definitions/minswap-v2/order.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | /**
4 | * https://github.com/minswap/minswap-dex-v2/blob/main/src/types/order.ts
5 | */
6 | export default {
7 | constructor: 0,
8 | fields: [
9 | {
10 | constructor: 0,
11 | fields: [
12 | {
13 | bytes: DatumParameterKey.SenderPubKeyHash,
14 | }
15 | ]
16 | },
17 | {
18 | constructor: 0,
19 | fields: [
20 | {
21 | constructor: 0,
22 | fields: [
23 | {
24 | bytes: DatumParameterKey.SenderPubKeyHash,
25 | }
26 | ]
27 | },
28 | {
29 | constructor: 0,
30 | fields: [
31 | {
32 | constructor: 0,
33 | fields: [
34 | {
35 | constructor: 0,
36 | fields: [
37 | {
38 | bytes: DatumParameterKey.SenderStakingKeyHash,
39 | }
40 | ]
41 | }
42 | ]
43 | }
44 | ]
45 | }
46 | ]
47 | },
48 | {
49 | constructor: 0,
50 | fields: []
51 | },
52 | {
53 | constructor: 0,
54 | fields: [
55 | {
56 | constructor: 0,
57 | fields: [
58 | {
59 | bytes: DatumParameterKey.SenderPubKeyHash,
60 | }
61 | ]
62 | },
63 | {
64 | constructor: 0,
65 | fields: [
66 | {
67 | constructor: 0,
68 | fields: [
69 | {
70 | constructor: 0,
71 | fields: [
72 | {
73 | bytes: DatumParameterKey.SenderStakingKeyHash,
74 | }
75 | ]
76 | }
77 | ]
78 | }
79 | ]
80 | }
81 | ]
82 | },
83 | {
84 | constructor: 0,
85 | fields: []
86 | },
87 | {
88 | constructor: 0,
89 | fields: [
90 | {
91 | bytes: DatumParameterKey.LpTokenPolicyId
92 | },
93 | {
94 | bytes: DatumParameterKey.LpTokenAssetName
95 | }
96 | ]
97 | },
98 | {
99 | constructor: 0,
100 | fields: [
101 | {
102 | constructor: DatumParameterKey.Direction,
103 | fields: []
104 | },
105 | {
106 | constructor: 0,
107 | fields: [
108 | {
109 | int: DatumParameterKey.SwapInAmount
110 | }
111 | ]
112 | },
113 | {
114 | int: DatumParameterKey.MinReceive
115 | },
116 | {
117 | constructor: 0,
118 | fields: []
119 | }
120 | ]
121 | },
122 | {
123 | int: DatumParameterKey.BatcherFee
124 | },
125 | {
126 | constructor: 1,
127 | fields: []
128 | }
129 | ]
130 | }
--------------------------------------------------------------------------------
/src/dex/definitions/minswap-v2/pool.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 | import { DatumParameters, DefinitionField } from '@app/types';
3 |
4 | /**
5 | * https://github.com/minswap/minswap-dex-v2/blob/main/src/types/pool.ts
6 | */
7 | export default {
8 | constructor: 0,
9 | fields: [
10 | {
11 | constructor: 0,
12 | fields: [
13 | (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
14 | return;
15 | },
16 | ]
17 | },
18 | {
19 | constructor: 0,
20 | fields: [
21 | {
22 | bytes: DatumParameterKey.PoolAssetAPolicyId
23 | },
24 | {
25 | bytes: DatumParameterKey.PoolAssetAAssetName
26 | }
27 | ]
28 | },
29 | {
30 | constructor: 0,
31 | fields: [
32 | {
33 | bytes: DatumParameterKey.PoolAssetBPolicyId
34 | },
35 | {
36 | bytes: DatumParameterKey.PoolAssetBAssetName
37 | }
38 | ]
39 | },
40 | {
41 | int: DatumParameterKey.TotalLpTokens
42 | },
43 | {
44 | int: DatumParameterKey.ReserveA
45 | },
46 | {
47 | int: DatumParameterKey.ReserveB
48 | },
49 | {
50 | int: DatumParameterKey.BaseFee
51 | },
52 | {
53 | int: DatumParameterKey.FeeSharingNumerator
54 | },
55 | (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
56 | return;
57 | },
58 | ]
59 | }
--------------------------------------------------------------------------------
/src/dex/definitions/minswap/order.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | /**
4 | * https://github.com/CatspersCoffee/contracts/blob/bd6831e6806798032f6bb754d94a06d72d4d28a1/dex/src/Minswap/BatchOrder/Types.hs
5 | */
6 | export default {
7 | constructor: 0,
8 | fields: [
9 | {
10 | constructor: 0,
11 | fields: [
12 | {
13 | constructor: 0,
14 | fields: [
15 | {
16 | bytes: DatumParameterKey.SenderPubKeyHash,
17 | }
18 | ]
19 | },
20 | {
21 | constructor: 0,
22 | fields: [
23 | {
24 | constructor: 0,
25 | fields: [
26 | {
27 | constructor: 0,
28 | fields: [
29 | {
30 | bytes: DatumParameterKey.SenderPubKeyHash,
31 | }
32 | ]
33 | }
34 | ]
35 | }
36 | ]
37 | }
38 | ]
39 | },
40 | {
41 | constructor: 0,
42 | fields: [
43 | {
44 | constructor: 0,
45 | fields: [
46 | {
47 | bytes: DatumParameterKey.ReceiverPubKeyHash,
48 | }
49 | ]
50 | },
51 | {
52 | constructor: 0,
53 | fields: [
54 | {
55 | constructor: 0,
56 | fields: [
57 | {
58 | constructor: 0,
59 | fields: [
60 | {
61 | bytes: DatumParameterKey.ReceiverStakingKeyHash,
62 | }
63 | ]
64 | }
65 | ]
66 | }
67 | ]
68 | }
69 | ]
70 | },
71 | {
72 | constructor: 1,
73 | fields: []
74 | },
75 | {
76 | constructor: 0,
77 | fields: [
78 | {
79 | constructor: 0,
80 | fields: [
81 | {
82 | bytes: DatumParameterKey.SwapOutTokenPolicyId
83 | },
84 | {
85 | bytes: DatumParameterKey.SwapOutTokenAssetName
86 | }
87 | ]
88 | },
89 | {
90 | int: DatumParameterKey.MinReceive
91 | }
92 | ]
93 | },
94 | {
95 | int: DatumParameterKey.BatcherFee
96 | },
97 | {
98 | int: DatumParameterKey.DepositFee
99 | }
100 | ]
101 | }
--------------------------------------------------------------------------------
/src/dex/definitions/minswap/pool.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | /**
4 | * https://github.com/CatspersCoffee/contracts/blob/bd6831e6806798032f6bb754d94a06d72d4d28a1/dex/src/Minswap/ConstantProductPool/OnChain.hs
5 | */
6 | export default {
7 | constructor: 0,
8 | fields: [
9 | {
10 | constructor: 0,
11 | fields: [
12 | {
13 | bytes: DatumParameterKey.PoolAssetAPolicyId
14 | },
15 | {
16 | bytes: DatumParameterKey.PoolAssetAAssetName
17 | }
18 | ]
19 | },
20 | {
21 | constructor: 0,
22 | fields: [
23 | {
24 | bytes: DatumParameterKey.PoolAssetBPolicyId
25 | },
26 | {
27 | bytes: DatumParameterKey.PoolAssetBAssetName
28 | }
29 | ]
30 | },
31 | {
32 | int: DatumParameterKey.TotalLpTokens
33 | },
34 | {
35 | int: DatumParameterKey.RootKLast
36 | },
37 | {
38 | constructor: 0,
39 | fields: [
40 | {
41 | constructor: 0,
42 | fields: [
43 | {
44 | constructor: 0,
45 | fields: [
46 | {
47 | constructor: 0,
48 | fields: [
49 | {
50 | bytes: DatumParameterKey.SenderPubKeyHash
51 | }
52 | ]
53 | },
54 | {
55 | constructor: 0,
56 | fields: [
57 | {
58 | constructor: 0,
59 | fields: [
60 | {
61 | constructor: 0,
62 | fields: [
63 | {
64 | bytes: DatumParameterKey.SenderStakingKeyHash
65 | }
66 | ]
67 | }
68 | ]
69 | }
70 | ]
71 | }
72 | ]
73 | },
74 | {
75 | constructor: 1,
76 | fields: []
77 | }
78 | ]
79 | }
80 | ]
81 | }
82 | ]
83 | }
--------------------------------------------------------------------------------
/src/dex/definitions/muesliswap/order.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | export default {
4 | constructor: 0,
5 | fields: [
6 | {
7 | constructor: 0,
8 | fields: [
9 | {
10 | constructor: 0,
11 | fields: [
12 | {
13 | constructor: 0,
14 | fields: [
15 | {
16 | bytes: DatumParameterKey.SenderPubKeyHash
17 | }
18 | ]
19 | },
20 | {
21 | constructor: 0,
22 | fields: [
23 | {
24 | constructor: 0,
25 | fields: [
26 | {
27 | constructor: 0,
28 | fields: [
29 | {
30 | bytes: DatumParameterKey.SenderStakingKeyHash
31 | }
32 | ]
33 | }
34 | ]
35 | }
36 | ]
37 | }
38 | ]
39 | },
40 | {
41 | bytes: DatumParameterKey.SwapOutTokenPolicyId
42 | },
43 | {
44 | bytes: DatumParameterKey.SwapOutTokenAssetName
45 | },
46 | {
47 | bytes: DatumParameterKey.SwapInTokenPolicyId
48 | },
49 | {
50 | bytes: DatumParameterKey.SwapInTokenAssetName
51 | },
52 | {
53 | int: DatumParameterKey.MinReceive
54 | },
55 | {
56 | constructor: DatumParameterKey.AllowPartialFill,
57 | fields: []
58 | },
59 | {
60 | // matchMakerFee + deposit
61 | int: DatumParameterKey.TotalFees
62 | }
63 | ]
64 | }
65 | ]
66 | };
--------------------------------------------------------------------------------
/src/dex/definitions/muesliswap/pool.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | /**
4 | * https://github.com/MuesliSwapTeam/muesliswap-cardano-pool-contracts/blob/main/dex/src/MuesliSwapPools/ConstantProductPool/OnChain.hs
5 | */
6 | export default {
7 | constructor: 0,
8 | fields: [
9 | {
10 | constructor: 0,
11 | fields: [
12 | {
13 | bytes: DatumParameterKey.PoolAssetAPolicyId
14 | },
15 | {
16 | bytes: DatumParameterKey.PoolAssetAAssetName
17 | }
18 | ]
19 | },
20 | {
21 | constructor: 0,
22 | fields: [
23 | {
24 | bytes: DatumParameterKey.PoolAssetBPolicyId
25 | },
26 | {
27 | bytes: DatumParameterKey.PoolAssetBAssetName
28 | }
29 | ]
30 | },
31 | {
32 | int: DatumParameterKey.TotalLpTokens
33 | },
34 | {
35 | int: DatumParameterKey.LpFee
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/src/dex/definitions/splash/order.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 | import { DatumParameters, DefinitionField } from '@app/types';
3 |
4 | export default {
5 | constructor: 0,
6 | fields: [
7 | {
8 | bytes: DatumParameterKey.Action
9 | },
10 | {
11 | bytes: DatumParameterKey.Beacon
12 | },
13 | {
14 | constructor: 0,
15 | fields: [
16 | {
17 | bytes: DatumParameterKey.SwapInTokenPolicyId
18 | },
19 | {
20 | bytes: DatumParameterKey.SwapInTokenAssetName
21 | }
22 | ]
23 | },
24 | {
25 | int: DatumParameterKey.SwapInAmount
26 | },
27 | {
28 | int: DatumParameterKey.BaseFee
29 | },
30 | {
31 | int: DatumParameterKey.MinReceive
32 | },
33 | {
34 | constructor: 0,
35 | fields: [
36 | {
37 | bytes: DatumParameterKey.SwapOutTokenPolicyId
38 | },
39 | {
40 | bytes: DatumParameterKey.SwapOutTokenAssetName
41 | }
42 | ]
43 | },
44 | {
45 | constructor: 0,
46 | fields: [
47 | {
48 | int: DatumParameterKey.LpFeeNumerator,
49 | },
50 | {
51 | int: DatumParameterKey.LpFeeDenominator,
52 | }
53 | ]
54 | },
55 | {
56 | int: DatumParameterKey.ExecutionFee
57 | },
58 | {
59 | constructor: 0,
60 | fields: [
61 | {
62 | constructor: 0,
63 | fields: [
64 | {
65 | bytes: DatumParameterKey.SenderPubKeyHash
66 | }
67 | ]
68 | },
69 | {
70 | constructor: 0,
71 | fields: [
72 | {
73 | constructor: 0,
74 | fields: [
75 | (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
76 | if (! shouldExtract) {
77 | const stakeKeyHash: string = parameters[DatumParameterKey.SenderStakingKeyHash] as string ?? null;
78 |
79 | if (! stakeKeyHash) return;
80 |
81 | return {
82 | constructor: 0,
83 | fields: [
84 | {
85 | bytes: stakeKeyHash,
86 | }
87 | ],
88 | };
89 | }
90 |
91 | if ('fields' in field) {
92 | if (field.constructor === 1) return;
93 |
94 | if (field.fields.length > 0 && 'bytes' in field.fields[0]) {
95 | parameters[DatumParameterKey.SenderStakingKeyHash] = field.fields[0].bytes;
96 | }
97 | }
98 |
99 | return;
100 | },
101 | ]
102 | }
103 | ]
104 | }
105 | ]
106 | },
107 | {
108 | bytes: DatumParameterKey.SenderPubKeyHash
109 | },
110 | [
111 | {
112 | bytes: DatumParameterKey.Batcher
113 | }
114 | ]
115 | ]
116 | }
117 |
--------------------------------------------------------------------------------
/src/dex/definitions/splash/pool.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 | import { DatumParameters, DefinitionField } from '@app/types';
3 |
4 | export default {
5 | constructor: 0,
6 | fields: [
7 | {
8 | constructor: 0,
9 | fields: [
10 | {
11 | bytes: DatumParameterKey.TokenPolicyId // Pool NFT
12 | },
13 | {
14 | bytes: DatumParameterKey.TokenAssetName
15 | }
16 | ]
17 | },
18 | {
19 | constructor: 0,
20 | fields: [
21 | {
22 | bytes: DatumParameterKey.PoolAssetAPolicyId
23 | },
24 | {
25 | bytes: DatumParameterKey.PoolAssetAAssetName
26 | }
27 | ]
28 | },
29 | {
30 | constructor: 0,
31 | fields: [
32 | {
33 | bytes: DatumParameterKey.PoolAssetBPolicyId
34 | },
35 | {
36 | bytes: DatumParameterKey.PoolAssetBAssetName
37 | }
38 | ]
39 | },
40 | {
41 | constructor: 0,
42 | fields: [
43 | {
44 | bytes: DatumParameterKey.LpTokenPolicyId
45 | },
46 | {
47 | bytes: DatumParameterKey.LpTokenAssetName
48 | }
49 | ]
50 | },
51 | {
52 | int: DatumParameterKey.Unknown
53 | },
54 | {
55 | int: DatumParameterKey.Unknown
56 | },
57 | {
58 | int: DatumParameterKey.PoolAssetATreasury
59 | },
60 | {
61 | int: DatumParameterKey.PoolAssetBTreasury
62 | },
63 | (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
64 | return;
65 | },
66 | ]
67 | }
68 |
--------------------------------------------------------------------------------
/src/dex/definitions/sundaeswap-v1/order.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | export default {
4 | constructor: 0,
5 | fields: [
6 | {
7 | bytes: DatumParameterKey.PoolIdentifier
8 | },
9 | {
10 | constructor: 0,
11 | fields: [
12 | {
13 | constructor: 0,
14 | fields: [
15 | {
16 | constructor: 0,
17 | fields: [
18 | {
19 | constructor: 0,
20 | fields: [
21 | {
22 | bytes: DatumParameterKey.SenderPubKeyHash
23 | }
24 | ]
25 | },
26 | {
27 | constructor: 0,
28 | fields: [
29 | {
30 | constructor: 0,
31 | fields: [
32 | {
33 | constructor: 0,
34 | fields: [
35 | {
36 | bytes: DatumParameterKey.SenderStakingKeyHash
37 | }
38 | ]
39 | }
40 | ]
41 | }
42 | ]
43 | }
44 | ]
45 | },
46 | {
47 | constructor: 1,
48 | fields: []
49 | }
50 | ]
51 | },
52 | {
53 | constructor: 1,
54 | fields: []
55 | }
56 | ]
57 | },
58 | {
59 | int: DatumParameterKey.ScooperFee
60 | },
61 | {
62 | constructor: 0,
63 | fields: [
64 | {
65 | constructor: DatumParameterKey.Action,
66 | fields: []
67 | },
68 | {
69 | int: DatumParameterKey.SwapInAmount
70 | },
71 | {
72 | constructor: 0,
73 | fields: [
74 | {
75 | int: DatumParameterKey.MinReceive
76 | }
77 | ]
78 | }
79 | ]
80 | }
81 | ]
82 | }
--------------------------------------------------------------------------------
/src/dex/definitions/sundaeswap-v1/pool.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | export default {
4 | constructor: 0,
5 | fields: [
6 | {
7 | constructor: 0,
8 | fields: [
9 | {
10 | constructor: 0,
11 | fields: [
12 | {
13 | bytes: DatumParameterKey.PoolAssetAPolicyId
14 | },
15 | {
16 | bytes: DatumParameterKey.PoolAssetAAssetName
17 | }
18 | ]
19 | },
20 | {
21 | constructor: 0,
22 | fields: [
23 | {
24 | bytes: DatumParameterKey.PoolAssetBPolicyId
25 | },
26 | {
27 | bytes: DatumParameterKey.PoolAssetBAssetName
28 | }
29 | ]
30 | }
31 | ]
32 | },
33 | {
34 | bytes: DatumParameterKey.PoolIdentifier
35 | },
36 | {
37 | int: DatumParameterKey.TotalLpTokens
38 | },
39 | {
40 | constructor: 0,
41 | fields: [
42 | {
43 | int: DatumParameterKey.LpFeeNumerator
44 | },
45 | {
46 | int: DatumParameterKey.LpFeeDenominator
47 | }
48 | ]
49 | }
50 | ]
51 | }
--------------------------------------------------------------------------------
/src/dex/definitions/sundaeswap-v3/order.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | export default {
4 | constructor: 0,
5 | fields: [
6 | {
7 | constructor: 0,
8 | fields: [
9 | {
10 | bytes: DatumParameterKey.PoolIdentifier,
11 | },
12 | ],
13 | },
14 | {
15 | constructor: 0,
16 | fields: [
17 | {
18 | bytes: DatumParameterKey.SenderStakingKeyHash,
19 | },
20 | ],
21 | },
22 | {
23 | int: DatumParameterKey.ProtocolFee,
24 | },
25 | {
26 | constructor: 0,
27 | fields: [
28 | {
29 | constructor: 0,
30 | fields: [
31 | {
32 | constructor: 0,
33 | fields: [
34 | {
35 | bytes: DatumParameterKey.SenderPubKeyHash,
36 | },
37 | ],
38 | },
39 | {
40 | constructor: 0,
41 | fields: [
42 | {
43 | constructor: 0,
44 | fields: [
45 | {
46 | constructor: 0,
47 | fields: [
48 | {
49 | bytes: DatumParameterKey.SenderStakingKeyHash,
50 | },
51 | ],
52 | },
53 | ],
54 | },
55 | ],
56 | },
57 | ],
58 | },
59 | {
60 | constructor: 0,
61 | fields: [],
62 | },
63 | ],
64 | },
65 | {
66 | constructor: 1,
67 | fields: [
68 | [
69 | {
70 | bytes: DatumParameterKey.SwapInTokenPolicyId,
71 | },
72 | {
73 | bytes: DatumParameterKey.SwapInTokenAssetName,
74 | },
75 | {
76 | int: DatumParameterKey.SwapInAmount,
77 | },
78 | ],
79 | [
80 | {
81 | bytes: DatumParameterKey.SwapOutTokenPolicyId,
82 | },
83 | {
84 | bytes: DatumParameterKey.SwapOutTokenAssetName,
85 | },
86 | {
87 | int: DatumParameterKey.MinReceive,
88 | },
89 | ],
90 | ],
91 | },
92 | {
93 | bytes: DatumParameterKey.CancelDatum,
94 | },
95 | ],
96 | };
97 |
--------------------------------------------------------------------------------
/src/dex/definitions/sundaeswap-v3/pool.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 | import { DatumParameters, DefinitionField } from '@app/types';
3 |
4 | export default {
5 | constructor: 0,
6 | fields: [
7 | {
8 | bytes: DatumParameterKey.PoolIdentifier,
9 | },
10 | {
11 | list: [
12 | {
13 | list: [
14 | {
15 | bytes: DatumParameterKey.PoolAssetAPolicyId
16 | },
17 | {
18 | bytes: DatumParameterKey.PoolAssetAAssetName
19 | }
20 | ],
21 | },
22 | {
23 | list: [
24 | {
25 | bytes: DatumParameterKey.PoolAssetBPolicyId
26 | },
27 | {
28 | bytes: DatumParameterKey.PoolAssetBAssetName
29 | }
30 | ],
31 | },
32 | ],
33 | },
34 | {
35 | int: DatumParameterKey.TotalLpTokens
36 | },
37 | {
38 | int: DatumParameterKey.OpeningFee
39 | },
40 | {
41 | int: DatumParameterKey.FinalFee
42 | },
43 | (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
44 | return;
45 | },
46 | ],
47 | };
48 |
--------------------------------------------------------------------------------
/src/dex/definitions/vyfinance/order.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | export default {
4 | constructor: 0,
5 | fields: [
6 | {
7 | bytes: DatumParameterKey.SenderKeyHashes
8 | },
9 | {
10 | constructor: DatumParameterKey.Action,
11 | fields: [
12 | {
13 | int: DatumParameterKey.MinReceive
14 | }
15 | ]
16 | }
17 | ]
18 | };
--------------------------------------------------------------------------------
/src/dex/definitions/vyfinance/pool.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | export default {
4 | constructor: 0,
5 | fields: [
6 | {
7 | int: DatumParameterKey.PoolAssetABarFee
8 | },
9 | {
10 | int: DatumParameterKey.PoolAssetBBarFee
11 | },
12 | {
13 | int: DatumParameterKey.TotalLpTokens
14 | }
15 | ]
16 | };
--------------------------------------------------------------------------------
/src/dex/definitions/wingriders-v2/order.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | export default {
4 | constructor: 0,
5 | fields: [
6 | {
7 | int: DatumParameterKey.DepositFee
8 | },
9 | {
10 | constructor: 0,
11 | fields: [
12 | {
13 | constructor: 0,
14 | fields: [
15 | {
16 | bytes: DatumParameterKey.ReceiverPubKeyHash
17 | }
18 | ]
19 | },
20 | {
21 | constructor: 0,
22 | fields: [
23 | {
24 | constructor: 0,
25 | fields: [
26 | {
27 | constructor: 0,
28 | fields: [
29 | {
30 | bytes: DatumParameterKey.ReceiverStakingKeyHash
31 | }
32 | ]
33 | }
34 | ]
35 | }
36 | ]
37 | }
38 | ]
39 | },
40 | {
41 | constructor: 0,
42 | fields: [
43 | {
44 | constructor: 0,
45 | fields: [
46 | {
47 | bytes: DatumParameterKey.ReceiverPubKeyHash
48 | }
49 | ]
50 | },
51 | {
52 | constructor: 0,
53 | fields: [
54 | {
55 | constructor: 0,
56 | fields: [
57 | {
58 | constructor: 0,
59 | fields: [
60 | {
61 | bytes: DatumParameterKey.ReceiverStakingKeyHash
62 | }
63 | ]
64 | }
65 | ]
66 | }
67 | ]
68 | }
69 | ]
70 | },
71 | [],
72 | {
73 | constructor: 0,
74 | fields: []
75 | },
76 | {
77 | int: DatumParameterKey.Expiration
78 | },
79 | {
80 | bytes: DatumParameterKey.PoolAssetAPolicyId
81 | },
82 | {
83 | bytes: DatumParameterKey.PoolAssetAAssetName
84 | },
85 | {
86 | bytes: DatumParameterKey.PoolAssetBPolicyId
87 | },
88 | {
89 | bytes: DatumParameterKey.PoolAssetBAssetName
90 | },
91 | {
92 | constructor: 0,
93 | fields: [
94 | {
95 | constructor: DatumParameterKey.Action,
96 | fields: []
97 | },
98 | {
99 | int: DatumParameterKey.MinReceive
100 | }
101 | ]
102 | },
103 | {
104 | int: DatumParameterKey.AScale
105 | },
106 | {
107 | int: DatumParameterKey.BScale
108 | }
109 | ]
110 | };
--------------------------------------------------------------------------------
/src/dex/definitions/wingriders-v2/pool.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 | import { DatumParameters, DefinitionField } from '@app/types';
3 |
4 | export default {
5 | constructor: 0,
6 | fields: [
7 | {
8 | bytes: DatumParameterKey.RequestScriptHash
9 | },
10 | {
11 | bytes: DatumParameterKey.PoolAssetAPolicyId,
12 | },
13 | {
14 | bytes: DatumParameterKey.PoolAssetAAssetName,
15 | },
16 | {
17 | bytes: DatumParameterKey.PoolAssetBPolicyId,
18 | },
19 | {
20 | bytes: DatumParameterKey.PoolAssetBAssetName,
21 | },
22 | {
23 | int: DatumParameterKey.SwapFee
24 | },
25 | {
26 | int: DatumParameterKey.ProtocolFee
27 | },
28 | {
29 | int: DatumParameterKey.ProjectFeeInBasis
30 | },
31 | {
32 | int: DatumParameterKey.ReserveFeeInBasis
33 | },
34 | {
35 | int: DatumParameterKey.FeeBasis
36 | },
37 | {
38 | int: DatumParameterKey.AgentFee
39 | },
40 | {
41 | int: DatumParameterKey.LastInteraction
42 | },
43 | {
44 | int: DatumParameterKey.PoolAssetATreasury
45 | },
46 | {
47 | int: DatumParameterKey.PoolAssetBTreasury
48 | },
49 | {
50 | int: DatumParameterKey.Unknown
51 | },
52 | {
53 | int: DatumParameterKey.Unknown
54 | },
55 | {
56 | int: DatumParameterKey.Unknown
57 | },
58 | {
59 | int: DatumParameterKey.Unknown
60 | },
61 | (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
62 | return parameters;
63 | },
64 | (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
65 | return parameters;
66 | },
67 | (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
68 | return parameters;
69 | },
70 | ]
71 | };
--------------------------------------------------------------------------------
/src/dex/definitions/wingriders/order.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | /**
4 | * https://github.com/WingRiders/dex-serializer/blob/main/src/RequestDatum.ts
5 | */
6 | export default {
7 | constructor: 0,
8 | fields: [
9 | {
10 | constructor: 0,
11 | fields: [
12 | {
13 | constructor: 0,
14 | fields: [
15 | {
16 | constructor: 0,
17 | fields: [
18 | {
19 | bytes: DatumParameterKey.ReceiverPubKeyHash
20 | }
21 | ]
22 | },
23 | {
24 | constructor: 0,
25 | fields: [
26 | {
27 | constructor: 0,
28 | fields: [
29 | {
30 | constructor: 0,
31 | fields: [
32 | {
33 | bytes: DatumParameterKey.ReceiverStakingKeyHash
34 | }
35 | ]
36 | }
37 | ]
38 | }
39 | ]
40 | }
41 | ]
42 | },
43 | {
44 | bytes: DatumParameterKey.SenderPubKeyHash
45 | },
46 | {
47 | int: DatumParameterKey.Expiration
48 | },
49 | {
50 | constructor: 0,
51 | fields: [
52 | {
53 | constructor: 0,
54 | fields: [
55 | {
56 | bytes: DatumParameterKey.PoolAssetAPolicyId
57 | },
58 | {
59 | bytes: DatumParameterKey.PoolAssetAAssetName
60 | }
61 | ]
62 | },
63 | {
64 | constructor: 0,
65 | fields: [
66 | {
67 | bytes: DatumParameterKey.PoolAssetBPolicyId
68 | },
69 | {
70 | bytes: DatumParameterKey.PoolAssetBAssetName
71 | }
72 | ]
73 | }
74 | ]
75 | }
76 | ]
77 | },
78 | {
79 | constructor: 0,
80 | fields: [
81 | {
82 | constructor: DatumParameterKey.Action,
83 | fields: []
84 | },
85 | {
86 | int: DatumParameterKey.MinReceive
87 | }
88 | ]
89 | }
90 | ]
91 | };
--------------------------------------------------------------------------------
/src/dex/definitions/wingriders/pool.ts:
--------------------------------------------------------------------------------
1 | import { DatumParameterKey } from '@app/constants';
2 |
3 | /**
4 | * https://github.com/WingRiders/dex-serializer/blob/main/src/LiquidityPoolDatum.ts
5 | */
6 | export default {
7 | constructor: 0,
8 | fields: [
9 | {
10 | bytes: DatumParameterKey.RequestScriptHash
11 | },
12 | {
13 | constructor: 0,
14 | fields: [
15 | {
16 | constructor: 0,
17 | fields: [
18 | {
19 | constructor: 0,
20 | fields: [
21 | {
22 | bytes: DatumParameterKey.PoolAssetAPolicyId,
23 | },
24 | {
25 | bytes: DatumParameterKey.PoolAssetAAssetName,
26 | }
27 | ]
28 | },
29 | {
30 | constructor: 0,
31 | fields: [
32 | {
33 | bytes: DatumParameterKey.PoolAssetBPolicyId,
34 | },
35 | {
36 | bytes: DatumParameterKey.PoolAssetBAssetName,
37 | }
38 | ]
39 | }
40 | ]
41 | },
42 | {
43 | int: DatumParameterKey.LastInteraction,
44 | },
45 | {
46 | int: DatumParameterKey.PoolAssetATreasury,
47 | },
48 | {
49 | int: DatumParameterKey.PoolAssetBTreasury,
50 | }
51 | ]
52 | }
53 | ]
54 | };
--------------------------------------------------------------------------------
/src/dex/logo/minswap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoProtocol/dexter/78ea53d408617409164a63b8ed25e57955dfc715/src/dex/logo/minswap.png
--------------------------------------------------------------------------------
/src/dex/logo/minswapv2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoProtocol/dexter/78ea53d408617409164a63b8ed25e57955dfc715/src/dex/logo/minswapv2.png
--------------------------------------------------------------------------------
/src/dex/logo/muesliswap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoProtocol/dexter/78ea53d408617409164a63b8ed25e57955dfc715/src/dex/logo/muesliswap.png
--------------------------------------------------------------------------------
/src/dex/logo/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoProtocol/dexter/78ea53d408617409164a63b8ed25e57955dfc715/src/dex/logo/splash.png
--------------------------------------------------------------------------------
/src/dex/logo/sundaeswap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoProtocol/dexter/78ea53d408617409164a63b8ed25e57955dfc715/src/dex/logo/sundaeswap.png
--------------------------------------------------------------------------------
/src/dex/logo/vyfinance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoProtocol/dexter/78ea53d408617409164a63b8ed25e57955dfc715/src/dex/logo/vyfinance.png
--------------------------------------------------------------------------------
/src/dex/logo/wingriders.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoProtocol/dexter/78ea53d408617409164a63b8ed25e57955dfc715/src/dex/logo/wingriders.png
--------------------------------------------------------------------------------
/src/dex/logo/wingridersv2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoProtocol/dexter/78ea53d408617409164a63b8ed25e57955dfc715/src/dex/logo/wingridersv2.png
--------------------------------------------------------------------------------
/src/dex/models/asset.ts:
--------------------------------------------------------------------------------
1 | export class Asset {
2 |
3 | public policyId: string;
4 | public nameHex: string;
5 | public decimals: number;
6 |
7 | constructor(policyId: string, nameHex: string, decimals: number = 0) {
8 | this.policyId = policyId;
9 | this.nameHex = nameHex;
10 | this.decimals = decimals;
11 | }
12 |
13 | static fromIdentifier(id: string, decimals: number = 0): Asset {
14 | id = id.replace('.', '');
15 |
16 | return new Asset(
17 | id.slice(0, 56),
18 | id.slice(56),
19 | decimals,
20 | );
21 | }
22 |
23 | identifier(dilimeter: '' | '.' = ''): string {
24 | return this.policyId + dilimeter + this.nameHex;
25 | }
26 |
27 | get assetName(): string {
28 | return Buffer.from(this.nameHex, 'hex').toString();
29 | }
30 |
31 | }
32 |
33 | export type Token = Asset | 'lovelace';
34 |
--------------------------------------------------------------------------------
/src/dex/models/dex-transaction.ts:
--------------------------------------------------------------------------------
1 | import { TransactionStatus } from '@app/constants';
2 | import { BaseWalletProvider } from '@providers/wallet/base-wallet-provider';
3 | import { DexTransactionError, PayToAddress } from '@app/types';
4 |
5 | interface TransactionCallback {
6 | (transaction: DexTransaction): void;
7 | }
8 |
9 | export class DexTransaction {
10 |
11 | public providerData: any = {};
12 | public error: DexTransactionError | undefined = undefined;
13 |
14 | private _hash: string;
15 | private _isSigned: boolean = false;
16 | private _payments: PayToAddress[] = [];
17 | private _walletProvider: BaseWalletProvider;
18 | private _currentStatus: TransactionStatus = TransactionStatus.Building;
19 | private _listeners: TransactionCallback[] = [];
20 |
21 | constructor(walletProvider: BaseWalletProvider) {
22 | this._walletProvider = walletProvider;
23 | }
24 |
25 | get hash(): string {
26 | return this._hash;
27 | }
28 |
29 | get isSigned(): boolean {
30 | return this._isSigned;
31 | }
32 |
33 | get payments(): PayToAddress[] {
34 | return this._payments;
35 | }
36 |
37 | get status(): TransactionStatus {
38 | return this._currentStatus;
39 | }
40 |
41 | set status(status: TransactionStatus) {
42 | this._currentStatus = status;
43 |
44 | this._listeners.forEach((callback: TransactionCallback) => {
45 | callback(this);
46 | });
47 | }
48 |
49 | public attachMetadata(key: number, json: Object): DexTransaction {
50 | return this._walletProvider.attachMetadata(this, key, json);
51 | }
52 |
53 | public payToAddresses(payToAddresses: PayToAddress[]): Promise {
54 | return this._walletProvider.paymentsForTransaction(this, payToAddresses)
55 | .then(() => {
56 | this._payments = payToAddresses;
57 |
58 | return this as DexTransaction;
59 | });
60 | }
61 |
62 | public sign(): Promise {
63 | if (this._isSigned) {
64 | throw new Error('Transaction was already signed.');
65 | }
66 |
67 | return this._walletProvider.signTransaction(this)
68 | .then(() => {
69 | this._isSigned = true;
70 |
71 | return this as DexTransaction;
72 | });
73 | }
74 |
75 | public submit(): Promise {
76 | if (! this._isSigned) {
77 | throw new Error('Must sign transaction before submitting.');
78 | }
79 | if (this._hash) {
80 | throw new Error('Transaction was already submitted.');
81 | }
82 |
83 | return this._walletProvider.submitTransaction(this)
84 | .then((txHash: string) => {
85 | this._hash = txHash;
86 |
87 | return this as DexTransaction;
88 | });
89 | }
90 |
91 | public onBuilding(callback: TransactionCallback): DexTransaction {
92 | this.addListener((transaction: DexTransaction) => {
93 | if (transaction.status === TransactionStatus.Building) {
94 | callback(transaction);
95 | }
96 | });
97 |
98 | return this;
99 | }
100 |
101 | public onSigning(callback: TransactionCallback): DexTransaction {
102 | this.addListener((transaction: DexTransaction) => {
103 | if (transaction.status === TransactionStatus.Signing) {
104 | callback(transaction);
105 | }
106 | });
107 |
108 | return this;
109 | }
110 |
111 | public onSubmitting(callback: TransactionCallback): DexTransaction {
112 | this.addListener((transaction: DexTransaction) => {
113 | if (transaction.status === TransactionStatus.Submitting) {
114 | callback(transaction);
115 | }
116 | });
117 |
118 | return this;
119 | }
120 |
121 | public onSubmitted(callback: TransactionCallback): DexTransaction {
122 | this.addListener((transaction: DexTransaction) => {
123 | if (transaction.status === TransactionStatus.Submitted) {
124 | callback(transaction);
125 | }
126 | });
127 |
128 | return this;
129 | }
130 |
131 | public onError(callback: TransactionCallback): DexTransaction {
132 | this.addListener((transaction: DexTransaction) => {
133 | if (transaction.status === TransactionStatus.Errored) {
134 | callback(transaction);
135 | }
136 | });
137 |
138 | return this;
139 | }
140 |
141 | public onFinally(callback: TransactionCallback): DexTransaction {
142 | this.addListener((transaction: DexTransaction) => {
143 | if (transaction.status === TransactionStatus.Submitted || transaction.status === TransactionStatus.Errored) {
144 | callback(transaction);
145 | }
146 | });
147 |
148 | return this;
149 | }
150 |
151 | private addListener(callback: TransactionCallback): void {
152 | this._listeners.push(callback);
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/src/dex/models/liquidity-pool.ts:
--------------------------------------------------------------------------------
1 | import { Asset, Token } from './asset';
2 |
3 | export class LiquidityPool {
4 |
5 | dex: string;
6 | assetA: Token;
7 | assetB: Token;
8 | reserveA: bigint;
9 | reserveB: bigint;
10 | address: string;
11 | marketOrderAddress: string;
12 | limitOrderAddress: string;
13 |
14 | lpToken: Asset;
15 | poolNft: Asset;
16 | identifier: string = '';
17 | poolFeePercent: number = 0;
18 | totalLpTokens: bigint = 0n;
19 | extra: any = {};
20 |
21 | constructor(dex: string, assetA: Token, assetB: Token, reserveA: bigint, reserveB: bigint, address: string, marketOrderAddress: string = '', limitOrderAddress: string = '') {
22 | this.dex = dex;
23 | this.assetA = assetA;
24 | this.assetB = assetB;
25 | this.reserveA = reserveA;
26 | this.reserveB = reserveB;
27 | this.address = address;
28 | this.marketOrderAddress = marketOrderAddress;
29 | this.limitOrderAddress = limitOrderAddress;
30 | }
31 |
32 | get uuid(): string {
33 | return `${this.dex}.${this.pair}.${this.identifier}`;
34 | }
35 |
36 | get pair(): string {
37 | const assetAName: string = this.assetA === 'lovelace' ? 'ADA' : this.assetA.assetName;
38 | const assetBName: string = this.assetB === 'lovelace' ? 'ADA' : this.assetB.assetName;
39 |
40 | return `${assetAName}/${assetBName}`;
41 | }
42 |
43 | get price(): number {
44 | const assetADecimals: number = this.assetA === 'lovelace' ? 6 : this.assetA.decimals;
45 | const assetBDecimals: number = this.assetB === 'lovelace' ? 6 : this.assetB.decimals;
46 |
47 | const adjustedReserveA: number = Number(this.reserveA) / (10**assetADecimals);
48 | const adjustedReserveB: number = Number(this.reserveB) / (10**assetBDecimals);
49 |
50 | return adjustedReserveA / adjustedReserveB;
51 | }
52 |
53 | get totalValueLocked(): number {
54 | const assetADecimals: number = this.assetA === 'lovelace' ? 6 : this.assetA.decimals;
55 | const assetBDecimals: number = this.assetB === 'lovelace' ? 6 : this.assetB.decimals;
56 |
57 | if (this.assetA === 'lovelace') {
58 | return (Number(this.reserveA) / 10**assetADecimals) + ((Number(this.reserveB) / 10**assetBDecimals) * this.price);
59 | }
60 |
61 | return ((Number(this.reserveA) / 10**assetADecimals) * this.price) * ((Number(this.reserveB) / 10**assetBDecimals) * (1 / this.price));
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/dexter.ts:
--------------------------------------------------------------------------------
1 | import { BaseDataProvider } from '@providers/data/base-data-provider';
2 | import { AvailableDexs, DexterConfig, RequestConfig } from '@app/types';
3 | import { Minswap } from '@dex/minswap';
4 | import { SundaeSwapV1 } from '@dex/sundaeswap-v1';
5 | import { MuesliSwap } from '@dex/muesliswap';
6 | import { WingRiders } from '@dex/wingriders';
7 | import { SwapRequest } from '@requests/swap-request';
8 | import { BaseWalletProvider } from '@providers/wallet/base-wallet-provider';
9 | import { BaseDex } from '@dex/base-dex';
10 | import { VyFinance } from '@dex/vyfinance';
11 | import { BaseMetadataProvider } from '@providers/asset-metadata/base-metadata-provider';
12 | import { TokenRegistryProvider } from '@providers/asset-metadata/token-registry-provider';
13 | import { CancelSwapRequest } from '@requests/cancel-swap-request';
14 | import { FetchRequest } from '@requests/fetch-request';
15 | import axios from 'axios';
16 | import axiosRetry from 'axios-retry';
17 | import { SplitSwapRequest } from '@requests/split-swap-request';
18 | import { SplitCancelSwapRequest } from '@requests/split-cancel-swap-request';
19 | import { SundaeSwapV3 } from '@dex/sundaeswap-v3';
20 | import { MinswapV2 } from '@dex/minswap-v2';
21 | import { WingRidersV2 } from '@dex/wingriders-v2';
22 | import { Splash } from '@dex/splash';
23 |
24 | export class Dexter {
25 |
26 | public config: DexterConfig;
27 | public requestConfig: RequestConfig;
28 |
29 | public dataProvider?: BaseDataProvider;
30 | public walletProvider?: BaseWalletProvider;
31 | public metadataProvider: BaseMetadataProvider;
32 |
33 | public availableDexs: AvailableDexs;
34 |
35 | constructor(config: DexterConfig = {}, requestConfig: RequestConfig = {}) {
36 | this.config = Object.assign(
37 | {},
38 | {
39 | shouldFetchMetadata: true,
40 | shouldFallbackToApi: true,
41 | shouldSubmitOrders: false,
42 | metadataMsgBranding: 'Dexter',
43 | } as DexterConfig,
44 | config,
45 | );
46 | this.requestConfig = Object.assign(
47 | {},
48 | {
49 | timeout: 5000,
50 | proxyUrl: '',
51 | retries: 3,
52 | } as RequestConfig,
53 | requestConfig,
54 | );
55 |
56 | // Axios configurations
57 | axiosRetry(axios, { retries: this.requestConfig.retries });
58 | axios.defaults.timeout = this.requestConfig.timeout;
59 |
60 | this.metadataProvider = new TokenRegistryProvider(this.requestConfig);
61 | this.availableDexs = {
62 | [Minswap.identifier]: new Minswap(this.requestConfig),
63 | [SundaeSwapV1.identifier]: new SundaeSwapV1(this.requestConfig),
64 | [SundaeSwapV3.identifier]: new SundaeSwapV3(this.requestConfig),
65 | [MinswapV2.identifier]: new MinswapV2(this.requestConfig),
66 | [MuesliSwap.identifier]: new MuesliSwap(this.requestConfig),
67 | [WingRiders.identifier]: new WingRiders(this.requestConfig),
68 | [WingRidersV2.identifier]: new WingRidersV2(this.requestConfig),
69 | [VyFinance.identifier]: new VyFinance(this.requestConfig),
70 | [Splash.identifier]: new Splash(this.requestConfig),
71 | };
72 | }
73 |
74 | /**
75 | * Retrieve DEX instance from unique name.
76 | */
77 | public dexByName(name: string): BaseDex | undefined {
78 | return this.availableDexs[name];
79 | }
80 |
81 | /**
82 | * Switch to a new data provider.
83 | */
84 | public withDataProvider(dataProvider: BaseDataProvider): Dexter {
85 | this.dataProvider = dataProvider;
86 |
87 | return this;
88 | }
89 |
90 | /**
91 | * Switch to a new wallet provider.
92 | */
93 | public withWalletProvider(walletProvider: BaseWalletProvider): Dexter {
94 | this.walletProvider = walletProvider;
95 |
96 | return this;
97 | }
98 |
99 | /**
100 | * Switch to a new asset metadata provider.
101 | */
102 | public withMetadataProvider(metadataProvider: BaseMetadataProvider): Dexter {
103 | this.metadataProvider = metadataProvider;
104 |
105 | return this;
106 | }
107 |
108 | /**
109 | * New request for data fetching.
110 | */
111 | public newFetchRequest(): FetchRequest {
112 | return new FetchRequest(this);
113 | }
114 |
115 | /**
116 | * New request for a swap order.
117 | */
118 | public newSwapRequest(): SwapRequest {
119 | return new SwapRequest(this);
120 | }
121 |
122 | /**
123 | * New request for a split swap order.
124 | */
125 | public newSplitSwapRequest(): SplitSwapRequest {
126 | return new SplitSwapRequest(this);
127 | }
128 |
129 | /**
130 | * New request for cancelling a swap order.
131 | */
132 | public newCancelSwapRequest(): CancelSwapRequest {
133 | if (! this.walletProvider) {
134 | throw new Error('Wallet provider must be set before requesting a cancel order.');
135 | }
136 | if (! this.walletProvider.isWalletLoaded) {
137 | throw new Error('Wallet must be loaded before requesting a cancel order.');
138 | }
139 |
140 | return new CancelSwapRequest(this);
141 | }
142 |
143 | /**
144 | * New request for a split cancel swap order.
145 | */
146 | public newSplitCancelSwapRequest(): SplitCancelSwapRequest {
147 | if (! this.walletProvider) {
148 | throw new Error('Wallet provider must be set before requesting a split cancel order.');
149 | }
150 | if (! this.walletProvider.isWalletLoaded) {
151 | throw new Error('Wallet must be loaded before requesting a split cancel order.');
152 | }
153 |
154 | return new SplitCancelSwapRequest(this);
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Base exports.
3 | */
4 | export * from './constants';
5 | export * from './types';
6 | export * from './utils';
7 | export * from './dexter';
8 | export * from './definition-builder';
9 |
10 | /**
11 | * Provider exports.
12 | */
13 | export * from './providers/data/base-data-provider';
14 | export * from './providers/data/blockfrost-provider';
15 | export * from './providers/data/kupo-provider';
16 | export * from './providers/data/mock-data-provider';
17 |
18 | export * from './providers/wallet/base-wallet-provider';
19 | export * from './providers/wallet/mock-wallet-provider';
20 | export * from './providers/wallet/lucid-provider';
21 |
22 | export * from './providers/asset-metadata/base-metadata-provider';
23 | export * from './providers/asset-metadata/token-registry-provider';
24 |
25 | /**
26 | * Request exports.
27 | */
28 | export * from './requests/fetch-request';
29 | export * from './requests/swap-request';
30 | export * from './requests/split-swap-request';
31 | export * from './requests/cancel-swap-request';
32 | export * from './requests/split-cancel-swap-request';
33 |
34 | /**
35 | * DEX exports.
36 | */
37 | export * from './dex/models/asset';
38 | export * from './dex/models/liquidity-pool';
39 | export * from './dex/models/dex-transaction';
40 |
41 | export * from './dex/base-dex';
42 | export * from './dex/minswap';
43 | export * from './dex/minswap-v2';
44 | export * from './dex/sundaeswap-v1';
45 | export * from './dex/sundaeswap-v3';
46 | export * from './dex/muesliswap';
47 | export * from './dex/wingriders';
48 | export * from './dex/wingriders-v2';
49 | export * from './dex/vyfinance';
50 | export * from './dex/splash';
51 |
--------------------------------------------------------------------------------
/src/providers/asset-metadata/base-metadata-provider.ts:
--------------------------------------------------------------------------------
1 | import { AssetMetadata } from '@app/types';
2 | import { Asset } from '@dex/models/asset';
3 |
4 | export abstract class BaseMetadataProvider {
5 |
6 | /**
7 | * Fetch Asset metadata.
8 | */
9 | abstract fetch(assets: Asset[]): Promise;
10 |
11 | }
--------------------------------------------------------------------------------
/src/providers/asset-metadata/token-registry-provider.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosInstance } from 'axios';
2 | import { AssetMetadata, RequestConfig } from '@app/types';
3 | import { Asset } from '@dex/models/asset';
4 | import { BaseMetadataProvider } from './base-metadata-provider';
5 | import { appendSlash } from '@app/utils';
6 |
7 | export class TokenRegistryProvider extends BaseMetadataProvider {
8 |
9 | private _api: AxiosInstance;
10 | private _requestConfig: RequestConfig;
11 |
12 | /**
13 | * https://input-output-hk.github.io/offchain-metadata-tools/api/latest/
14 | */
15 | constructor(requestConfig: RequestConfig = {}) {
16 | super();
17 |
18 | this._requestConfig = requestConfig;
19 |
20 | this._api = axios.create({
21 | timeout: requestConfig.timeout ?? 5000,
22 | baseURL: `${appendSlash(requestConfig.proxyUrl)}https://tokens.cardano.org/`,
23 | headers: {
24 | 'Content-Type': 'application/json',
25 | },
26 | });
27 | }
28 |
29 | /**
30 | * https://input-output-hk.github.io/offchain-metadata-tools/api/latest/#tag/query/paths/~1metadata~1query/post
31 | */
32 | fetch(assets: Asset[]): Promise {
33 | return this._api.post('/metadata/query', {
34 | subjects: assets.map((asset: Asset) => asset.identifier()),
35 | }).then((response) => response.data.subjects.map((entry: any) => {
36 | return {
37 | policyId: entry.subject.slice(0, 56),
38 | nameHex: entry.subject.slice(56),
39 | decimals: entry.decimals ? Number(entry.decimals.value) : 0,
40 | } as AssetMetadata;
41 | }));
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/providers/data/base-data-provider.ts:
--------------------------------------------------------------------------------
1 | import { AssetAddress, DefinitionField, Transaction, UTxO } from '@app/types';
2 | import { Asset } from '@dex/models/asset';
3 |
4 | export abstract class BaseDataProvider {
5 |
6 | /**
7 | * Fetch all UTxOs for an address. Will filter on UTxOs containing
8 | * assetId (concatenation of policy ID & asset name) if provided.
9 | */
10 | abstract utxos(address: string, asset?: Asset): Promise;
11 |
12 | /**
13 | * Fetch all UTxOs for a transaction.
14 | */
15 | abstract transactionUtxos(txHash: string): Promise;
16 |
17 | /**
18 | * Fetch all transactions containing and asset.
19 | */
20 | abstract assetTransactions(asset: Asset): Promise;
21 |
22 | /**
23 | * Fetch all addresses containing an asset.
24 | */
25 | abstract assetAddresses(asset: Asset): Promise
26 |
27 | /**
28 | * Fetch JSON value of a datum by its hash.
29 | */
30 | abstract datumValue(datumHash: string): Promise;
31 |
32 | }
--------------------------------------------------------------------------------
/src/providers/data/blockfrost-provider.ts:
--------------------------------------------------------------------------------
1 | import { BaseDataProvider } from './base-data-provider';
2 | import axios, { AxiosInstance } from 'axios';
3 | import {
4 | AssetAddress,
5 | AssetBalance,
6 | BlockfrostConfig,
7 | DefinitionField,
8 | RequestConfig,
9 | Transaction,
10 | UTxO
11 | } from '@app/types';
12 | import { Asset } from '@dex/models/asset';
13 | import Bottleneck from 'bottleneck';
14 | import { appendSlash } from '@app/utils';
15 |
16 | const API_BURST_SIZE: number = 500;
17 | const API_COOLDOWN_SIZE: number = 10;
18 | const API_COOLDOWN_MS: number = 1000;
19 |
20 | export class BlockfrostProvider extends BaseDataProvider {
21 |
22 | private _api: AxiosInstance;
23 | private _requestConfig: RequestConfig;
24 | private _limiter: Bottleneck;
25 |
26 | /**
27 | * https://docs.blockfrost.io/
28 | */
29 | constructor(config: BlockfrostConfig, requestConfig: RequestConfig = {}) {
30 | super();
31 |
32 | this._requestConfig = Object.assign(
33 | {},
34 | {
35 | timeout: 5000,
36 | proxyUrl: '',
37 | } as RequestConfig,
38 | requestConfig,
39 | );
40 |
41 | this._api = axios.create({
42 | baseURL: (appendSlash(requestConfig.proxyUrl)) + config.url,
43 | timeout: this._requestConfig.timeout,
44 | headers: {
45 | 'Content-Type': 'application/json',
46 | project_id: config.projectId,
47 | },
48 | });
49 |
50 | // https://docs.blockfrost.io/#section/Limits
51 | this._limiter = new Bottleneck({
52 | reservoir: API_BURST_SIZE,
53 | reservoirIncreaseAmount: API_COOLDOWN_SIZE,
54 | reservoirIncreaseInterval: API_COOLDOWN_MS,
55 | reservoirIncreaseMaximum: API_BURST_SIZE,
56 | });
57 | }
58 |
59 | /**
60 | * https://docs.blockfrost.io/#tag/Cardano-Addresses/paths/~1addresses~1%7Baddress%7D~1utxos/get
61 | * https://docs.blockfrost.io/#tag/Cardano-Addresses/paths/~1addresses~1%7Baddress%7D~1utxos~1%7Basset%7D/get
62 | */
63 | public async utxos(address: string, asset?: Asset): Promise {
64 | return this.sendPaginatedRequest(`/addresses/${address}/utxos/${asset ? asset.identifier() : ''}`)
65 | .then((results: any) => {
66 | return results.map((utxo: any) => {
67 | return {
68 | txHash: utxo.tx_hash,
69 | address: utxo.address,
70 | datumHash: utxo.data_hash,
71 | outputIndex: utxo.output_index,
72 | assetBalances: utxo.amount.reduce((assets: AssetBalance[], amount: any) => {
73 | assets.push({
74 | asset: amount.unit === 'lovelace' ? amount.unit : Asset.fromIdentifier(amount.unit),
75 | quantity: BigInt(amount.quantity),
76 | })
77 | return assets;
78 | }, []),
79 | } as UTxO;
80 | }) as UTxO[];
81 | });
82 | }
83 |
84 | /**
85 | * https://docs.blockfrost.io/#tag/Cardano-Transactions/paths/~1txs~1%7Bhash%7D~1utxos/get
86 | */
87 | public async transactionUtxos(txHash: string): Promise {
88 | return this._limiter.schedule(() => this._api.get(`/txs/${txHash}/utxos`))
89 | .then((response: any) => {
90 | return response.data.outputs.map((utxo: any) => {
91 | return {
92 | txHash: response.data.hash,
93 | address: utxo.address,
94 | datumHash: utxo.data_hash,
95 | datum: utxo.inline_datum,
96 | outputIndex: utxo.output_index,
97 | assetBalances: utxo.amount.reduce((assets: AssetBalance[], amount: any) => {
98 | assets.push({
99 | asset: amount.unit === 'lovelace' ? amount.unit : Asset.fromIdentifier(amount.unit),
100 | quantity: BigInt(amount.quantity),
101 | })
102 | return assets;
103 | }, []),
104 | } as UTxO;
105 | }) as UTxO[];
106 | });
107 | }
108 |
109 | /**
110 | * https://docs.blockfrost.io/#tag/Cardano-Assets/paths/~1assets~1%7Basset%7D~1transactions/get
111 | */
112 | public async assetTransactions(asset: Asset): Promise {
113 | return this.sendPaginatedRequest(`/assets/${asset.identifier()}/transactions`)
114 | .then((results: any) => {
115 | return results.map((tx: any) => {
116 | return {
117 | hash: tx.tx_hash,
118 | } as Transaction;
119 | }) as Transaction[];
120 | });
121 | }
122 |
123 | /**
124 | * https://docs.blockfrost.io/#tag/Cardano-Assets/paths/~1assets~1%7Basset%7D~1transactions/get
125 | */
126 | public async assetAddresses(asset: Asset): Promise {
127 | return this.sendPaginatedRequest(`/assets/${asset.identifier()}/addresses`)
128 | .then((results: any) => {
129 | return results.map((result: any) => {
130 | return {
131 | address: result.address,
132 | quantity: BigInt(result.quantity),
133 | } as AssetAddress;
134 | }) as AssetAddress[];
135 | });
136 | }
137 |
138 | /**
139 | * https://docs.blockfrost.io/#tag/Cardano-Scripts/paths/~1scripts~1datum~1%7Bdatum_hash%7D/get
140 | */
141 | public async datumValue(datumHash: string): Promise {
142 | return this._limiter.schedule(() => this._api.get(`/scripts/datum/${datumHash}`))
143 | .then((response: any) => {
144 | return response.data.json_value as DefinitionField;
145 | });
146 | }
147 |
148 | /**
149 | * https://docs.blockfrost.io/#section/Concepts
150 | */
151 | private sendPaginatedRequest(url: string, page: number = 1, results: any = []): Promise {
152 | return this._limiter.schedule(() =>
153 | this._api.get(url, {
154 | params: {
155 | page,
156 | },
157 | })
158 | ).then((response: any) => {
159 | results = results.concat(response.data);
160 | page++;
161 |
162 | // Possibly more data to grab
163 | if (response.data.length === 100) {
164 | return this.sendPaginatedRequest(url, page, results);
165 | }
166 |
167 | return results;
168 | });
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/src/providers/data/kupo-provider.ts:
--------------------------------------------------------------------------------
1 | import { BaseDataProvider } from './base-data-provider';
2 | import { Asset } from '@dex/models/asset';
3 | import {
4 | AssetAddress,
5 | AssetBalance,
6 | DefinitionBytes,
7 | DefinitionConstr,
8 | DefinitionField,
9 | DefinitionInt,
10 | KupoConfig,
11 | RequestConfig,
12 | Transaction,
13 | UTxO
14 | } from '@app/types';
15 | import axios, { AxiosInstance } from 'axios';
16 | import { Data } from 'lucid-cardano';
17 | import { appendSlash } from '@app/utils';
18 |
19 | export class KupoProvider extends BaseDataProvider {
20 |
21 | private _config: KupoConfig;
22 | private _kupoApi: AxiosInstance;
23 | private _requestConfig: RequestConfig;
24 |
25 | constructor(config: KupoConfig, requestConfig: RequestConfig = {}) {
26 | super();
27 |
28 | this._requestConfig = Object.assign(
29 | {},
30 | {
31 | timeout: 5000,
32 | proxyUrl: '',
33 | } as RequestConfig,
34 | requestConfig,
35 | );
36 |
37 | this._config = config;
38 | this._kupoApi = axios.create({
39 | baseURL: appendSlash(requestConfig.proxyUrl) + config.url,
40 | timeout: this._requestConfig.timeout,
41 | headers: {
42 | 'Content-Type': 'application/json',
43 | },
44 | });
45 | }
46 |
47 | public async utxos(address: string, asset?: Asset): Promise {
48 | const url: string = asset
49 | ? `/matches/${address}?policy_id=${asset.policyId}&asset_name=${asset.nameHex}&unspent`
50 | : `/matches/${address}?unspent`;
51 |
52 | return this._kupoApi.get(url)
53 | .then((results: any) => {
54 | return this.toUtxos(results.data);
55 | });
56 | }
57 |
58 | public async transactionUtxos(txHash: string): Promise {
59 | return this._kupoApi.get(`/matches/*@${txHash}`)
60 | .then((results: any) => {
61 | return this.toUtxos(results.data);
62 | });
63 | }
64 |
65 | public async datumValue(datumHash: string): Promise {
66 | return this._kupoApi.get(`/datums/${datumHash}`)
67 | .then((result: any) => {
68 | if (! result.data.datum) {
69 | throw new Error('Datum hash not found.');
70 | }
71 |
72 | return this.toDefinitionDatum(Data.from(result.data.datum));
73 | });
74 | }
75 |
76 | public async assetTransactions(asset: Asset): Promise {
77 | return this._kupoApi.get(`/matches/${asset.identifier('.')}`)
78 | .then((results: any) => {
79 | return results.data.map((result: any) => {
80 | return {
81 | hash: result.transaction_id,
82 | } as Transaction
83 | }) as Transaction[];
84 | });
85 | }
86 |
87 | public async assetAddresses(asset: Asset): Promise {
88 | return this._kupoApi.get(`/matches/${asset.identifier('.')}?unspent`)
89 | .then((results: any) => {
90 | return results.data.map((result: any) => {
91 | return {
92 | address: result.address,
93 | quantity: BigInt(result.value.assets[asset.identifier('.')]),
94 | } as AssetAddress
95 | }) as AssetAddress[];
96 | });
97 | }
98 |
99 | private toUtxos(results: any): UTxO[] {
100 | return results.map((utxo: any) => {
101 | return {
102 | txHash: utxo.transaction_id,
103 | address: utxo.address,
104 | datumHash: utxo.datum_hash,
105 | outputIndex: utxo.output_index,
106 | assetBalances: (() => {
107 | const balances: AssetBalance[] = [
108 | {
109 | asset: 'lovelace',
110 | quantity: BigInt(utxo.value.coins),
111 | }
112 | ];
113 | Object.keys(utxo.value.assets).forEach((unit: string) => {
114 | balances.push({
115 | asset: Asset.fromIdentifier(unit),
116 | quantity: BigInt(utxo.value.assets[unit]),
117 | });
118 | });
119 | return balances;
120 | })(),
121 | } as UTxO;
122 | }) as UTxO[];
123 | }
124 |
125 | private toDefinitionDatum(unconstructedField: any): DefinitionField {
126 | if (unconstructedField?.fields) {
127 | return {
128 | constructor: unconstructedField.index,
129 | fields: unconstructedField.fields.map((field: any) => this.toDefinitionDatum(field)),
130 | } as DefinitionConstr;
131 | }
132 |
133 | if (typeof unconstructedField === 'bigint') {
134 | return {
135 | int: Number(unconstructedField)
136 | } as DefinitionInt;
137 | }
138 |
139 | if (typeof unconstructedField === 'string') {
140 | return {
141 | bytes: unconstructedField
142 | } as DefinitionBytes;
143 | }
144 |
145 | return unconstructedField;
146 | }
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/src/providers/data/mock-data-provider.ts:
--------------------------------------------------------------------------------
1 | import { BaseDataProvider } from './base-data-provider';
2 | import { AssetAddress, DefinitionField, Transaction, UTxO } from '@app/types';
3 | import { Asset } from '@dex/models/asset';
4 |
5 | export class MockDataProvider extends BaseDataProvider {
6 |
7 | public async utxos(address: string, asset?: Asset): Promise {
8 | return Promise.resolve([]);
9 | }
10 |
11 | public async assetTransactions(asset: Asset): Promise {
12 | return Promise.resolve([]);
13 | }
14 |
15 | public async transactionUtxos(txHash: string): Promise {
16 | return Promise.resolve([]);
17 | }
18 |
19 | public async assetAddresses(asset: Asset): Promise {
20 | return Promise.resolve([]);
21 | }
22 |
23 | public datumValue(datumHash: string): Promise {
24 | return Promise.resolve({
25 | constructor: 0,
26 | fields: [],
27 | });
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/src/providers/wallet/base-wallet-provider.ts:
--------------------------------------------------------------------------------
1 | import { Cip30Api, PayToAddress, WalletOptions } from '@app/types';
2 | import { DexTransaction } from '@dex/models/dex-transaction';
3 |
4 | export abstract class BaseWalletProvider {
5 |
6 | public abstract isWalletLoaded: boolean;
7 |
8 | abstract address(): string;
9 |
10 | abstract publicKeyHash(): string;
11 |
12 | abstract stakingKeyHash(): string;
13 |
14 | abstract loadWallet(walletApi: Cip30Api, config: any): Promise;
15 |
16 | abstract loadWalletFromSeedPhrase(seed: string[], options: WalletOptions, config: any): Promise;
17 |
18 | abstract createTransaction(): DexTransaction;
19 |
20 | abstract attachMetadata(transaction: DexTransaction, key: number, json: Object): DexTransaction;
21 |
22 | abstract paymentsForTransaction(transaction: DexTransaction, payToAddresses: PayToAddress[]): Promise;
23 |
24 | abstract signTransaction(transaction: DexTransaction): Promise;
25 |
26 | abstract submitTransaction(transaction: DexTransaction): Promise;
27 |
28 | }
--------------------------------------------------------------------------------
/src/providers/wallet/lucid-provider.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AssetBalance,
3 | BlockfrostConfig,
4 | Cip30Api,
5 | KupmiosConfig,
6 | PayToAddress,
7 | SpendUTxO,
8 | WalletOptions
9 | } from '@app/types';
10 | import { DexTransaction } from '@dex/models/dex-transaction';
11 | import { BaseWalletProvider } from './base-wallet-provider';
12 | import {
13 | Address,
14 | AddressDetails,
15 | Assets,
16 | Blockfrost,
17 | Datum, Kupmios,
18 | Lucid,
19 | TxComplete,
20 | TxHash,
21 | TxSigned,
22 | Unit
23 | } from 'lucid-cardano';
24 | import { AddressType } from '@app/constants';
25 |
26 | export class LucidProvider extends BaseWalletProvider {
27 |
28 | public isWalletLoaded: boolean = false;
29 |
30 | private _api: Lucid;
31 | private _usableAddress: string;
32 | private _paymentCredential: string;
33 | private _stakingCredential: string | undefined;
34 |
35 | public address(): string {
36 | return this._usableAddress;
37 | }
38 |
39 | public publicKeyHash(): string {
40 | return this._paymentCredential;
41 | }
42 |
43 | public stakingKeyHash(): string {
44 | return this._stakingCredential ?? '';
45 | }
46 |
47 | public loadWallet(walletApi: Cip30Api, config: BlockfrostConfig | KupmiosConfig): Promise {
48 | return this.loadLucid(config)
49 | .then((lucid: Lucid) => {
50 | this._api = lucid;
51 |
52 | this._api.selectWallet(walletApi);
53 |
54 | return this.loadWalletInformation();
55 | });
56 | }
57 |
58 | public loadWalletFromSeedPhrase(seed: string[], options: WalletOptions = {}, config: BlockfrostConfig | KupmiosConfig): Promise {
59 | return this.loadLucid(config)
60 | .then((lucid: Lucid) => {
61 | this._api = lucid;
62 |
63 | const addressType: 'Base' | 'Enterprise' = options.addressType === AddressType.Enterprise
64 | ? 'Enterprise'
65 | : 'Base';
66 |
67 | this._api.selectWalletFromSeed(
68 | seed.join(' '),
69 | {
70 | addressType: addressType,
71 | accountIndex: options.accountIndex ?? 0,
72 | },
73 | );
74 |
75 | return this.loadWalletInformation();
76 | });
77 | }
78 |
79 | public createTransaction(): DexTransaction {
80 | const transaction: DexTransaction = new DexTransaction(this);
81 | transaction.providerData.tx = this._api.newTx();
82 |
83 | return transaction;
84 | }
85 |
86 | public attachMetadata(transaction: DexTransaction, key: number, json: Object): DexTransaction {
87 | if (! transaction.providerData.tx) {
88 | return transaction;
89 | }
90 |
91 | transaction.providerData.tx.attachMetadata(key, json);
92 |
93 | return transaction;
94 | }
95 |
96 | public async paymentsForTransaction(transaction: DexTransaction, payToAddresses: PayToAddress[]): Promise {
97 | payToAddresses.forEach((payToAddress: PayToAddress) => {
98 | const payment: Assets = this.paymentFromAssets(payToAddress.assetBalances);
99 |
100 | // Include UTxOs to spend
101 | if (payToAddress.spendUtxos && payToAddress.spendUtxos.length > 0) {
102 | payToAddress.spendUtxos.forEach((spendUtxo: SpendUTxO) => {
103 | transaction.providerData.tx.collectFrom([
104 | {
105 | txHash: spendUtxo.utxo.txHash,
106 | outputIndex: spendUtxo.utxo.outputIndex,
107 | address: spendUtxo.utxo.address,
108 | datumHash: spendUtxo.utxo.datum ? null : spendUtxo.utxo.datumHash,
109 | datum: spendUtxo.utxo.datum,
110 | assets: this.paymentFromAssets(spendUtxo.utxo.assetBalances),
111 | }
112 | ], spendUtxo.redeemer);
113 |
114 | if (spendUtxo.validator) {
115 | transaction.providerData.tx.attachSpendingValidator(spendUtxo.validator);
116 | }
117 |
118 | if (spendUtxo.signer) {
119 | transaction.providerData.tx.addSigner(spendUtxo.signer);
120 | }
121 | });
122 | }
123 |
124 | switch (payToAddress.addressType) {
125 | case AddressType.Contract:
126 | transaction.providerData.tx.payToContract(
127 | payToAddress.address,
128 | payToAddress.isInlineDatum
129 | ? {
130 | inline: payToAddress.datum as Datum,
131 | }
132 | : payToAddress.datum as Datum,
133 | payment,
134 | );
135 | break;
136 | case AddressType.Base:
137 | case AddressType.Enterprise:
138 | transaction.providerData.tx.payToAddress(
139 | payToAddress.address,
140 | payment,
141 | );
142 | break;
143 | default:
144 | throw new Error('Encountered unknown address type.');
145 | }
146 | });
147 |
148 | return transaction.providerData.tx.complete()
149 | .then((tx: TxComplete) => {
150 | transaction.providerData.tx = tx;
151 |
152 | return transaction;
153 | });
154 | }
155 |
156 | public signTransaction(transaction: DexTransaction): Promise {
157 | if (! this.isWalletLoaded) {
158 | throw new Error('Must load wallet before signing transaction.');
159 | }
160 |
161 | return transaction.providerData.tx.sign().complete()
162 | .then((signedTx: TxSigned) => {
163 | transaction.providerData.tx = signedTx;
164 |
165 | return transaction;
166 | });
167 | }
168 |
169 | public submitTransaction(transaction: DexTransaction): Promise {
170 | return transaction.providerData.tx.submit()
171 | .then((txHash: TxHash) => {
172 | return txHash;
173 | });
174 | }
175 |
176 | private paymentFromAssets(assetBalances: AssetBalance[]): Assets {
177 | return assetBalances
178 | .reduce((payment: Record, assetBalance: AssetBalance) => {
179 | payment[assetBalance.asset === 'lovelace' ? 'lovelace' : assetBalance.asset.identifier()] = assetBalance.quantity;
180 |
181 | return payment;
182 | }, {} as Assets);
183 | }
184 |
185 | private loadWalletInformation(): Promise {
186 | return this._api.wallet.address()
187 | .then((address: Address) => {
188 | const details: AddressDetails = this._api.utils.getAddressDetails(
189 | address,
190 | );
191 |
192 | this._usableAddress = address;
193 | this._paymentCredential = details.paymentCredential?.hash ?? '';
194 | this._stakingCredential = details.stakeCredential?.hash ?? '';
195 |
196 | this.isWalletLoaded = true;
197 |
198 | return this as BaseWalletProvider;
199 | });
200 | }
201 |
202 | private loadLucid(config: BlockfrostConfig | KupmiosConfig): Promise {
203 | return Lucid.new(
204 | 'kupoUrl' in config
205 | ? new Kupmios(
206 | config.kupoUrl,
207 | config.ogmiosUrl
208 | )
209 | : new Blockfrost(
210 | config.url,
211 | config.projectId
212 | )
213 | );
214 | }
215 |
216 | }
217 |
--------------------------------------------------------------------------------
/src/providers/wallet/mock-wallet-provider.ts:
--------------------------------------------------------------------------------
1 | import { Cip30Api, PayToAddress, WalletOptions } from '@app/types';
2 | import { DexTransaction } from '@dex/models/dex-transaction';
3 | import { BaseWalletProvider } from './base-wallet-provider';
4 |
5 | export class MockWalletProvider extends BaseWalletProvider {
6 |
7 | public isWalletLoaded: boolean = false;
8 |
9 | private _usableAddress: string;
10 | private _paymentCredential: string;
11 | private _stakingCredential: string;
12 |
13 | constructor() {
14 | super();
15 |
16 | this._usableAddress = 'addr1test';
17 | this._paymentCredential = 'ed56';
18 | this._stakingCredential = 'bac6';
19 | }
20 |
21 | public address(): string {
22 | return this._usableAddress;
23 | }
24 |
25 | public publicKeyHash(): string {
26 | return this._paymentCredential;
27 | }
28 |
29 | public stakingKeyHash(): string {
30 | return this._stakingCredential;
31 | }
32 |
33 | public loadWallet(walletApi: Cip30Api): Promise {
34 | this.isWalletLoaded = true;
35 |
36 | return Promise.resolve(this as BaseWalletProvider);
37 | }
38 |
39 | public loadWalletFromSeedPhrase(seed: string[], options: WalletOptions = {}): Promise {
40 | this.isWalletLoaded = true;
41 |
42 | return Promise.resolve(this as BaseWalletProvider);
43 | }
44 |
45 | public createTransaction(): DexTransaction {
46 | return new DexTransaction(this);
47 | }
48 |
49 | public attachMetadata(transaction: DexTransaction, key: number, json: Object): DexTransaction {
50 | return transaction;
51 | }
52 |
53 | public paymentsForTransaction(transaction: DexTransaction, payToAddresses: PayToAddress[]): Promise {
54 | return Promise.resolve(transaction);
55 | }
56 |
57 | public signTransaction(transaction: DexTransaction): Promise {
58 | return Promise.resolve(transaction);
59 | }
60 |
61 | public submitTransaction(transaction: DexTransaction): Promise {
62 | return Promise.resolve('hashtest');
63 | }
64 |
65 | }
--------------------------------------------------------------------------------
/src/requests/cancel-swap-request.ts:
--------------------------------------------------------------------------------
1 | import { Dexter } from '@app/dexter';
2 | import { DexTransaction } from '@dex/models/dex-transaction';
3 | import { PayToAddress, UTxO } from '@app/types';
4 | import { MetadataKey, TransactionStatus } from '@app/constants';
5 | import { BaseDataProvider } from '@providers/data/base-data-provider';
6 |
7 | export class CancelSwapRequest {
8 |
9 | private _dexter: Dexter;
10 | private _txHash: string;
11 | private _dexName: string;
12 |
13 | constructor(dexter: Dexter) {
14 | this._dexter = dexter;
15 | }
16 |
17 | public forTransaction(txHash: string): CancelSwapRequest {
18 | this._txHash = txHash;
19 |
20 | return this;
21 | }
22 |
23 | public forDex(name: string): CancelSwapRequest {
24 | this._dexName = name;
25 |
26 | return this;
27 | }
28 |
29 | public getPaymentsToAddresses(): Promise {
30 | if (! this._dexter.walletProvider) {
31 | throw new Error('Wallet provider must be set before submitting a swap order.');
32 | }
33 |
34 | const returnAddress: string = this._dexter.walletProvider.address();
35 |
36 | return (this._dexter.dataProvider as BaseDataProvider).transactionUtxos(this._txHash)
37 | .then((utxos: UTxO[]) => {
38 | return this._dexter.availableDexs[this._dexName].buildCancelSwapOrder(utxos, returnAddress);
39 | })
40 | .catch(() => {
41 | throw new Error('Unable to grab UTxOs for the provided Tx hash. Ensure the one provided is a valid Tx hash.')
42 | });
43 | }
44 |
45 | public cancel(): DexTransaction {
46 | if (! this._dexter.walletProvider) {
47 | throw new Error('Wallet provider must be set before submitting a swap order.');
48 | }
49 | if (! this._txHash) {
50 | throw new Error('Tx hash must be provided before cancelling a swap order.');
51 | }
52 | if (! this._dexName) {
53 | throw new Error('DEX must be provided before cancelling a swap order.');
54 | }
55 |
56 | const cancelTransaction: DexTransaction = this._dexter.walletProvider.createTransaction();
57 |
58 | this.getPaymentsToAddresses()
59 | .then((payToAddresses: PayToAddress[]) => {
60 | this.sendCancelOrder(cancelTransaction, payToAddresses);
61 | })
62 | .catch((error) => {
63 | throw new Error(`Unable to cancel swap order. ${error}`)
64 | });
65 |
66 | return cancelTransaction;
67 | }
68 |
69 | private sendCancelOrder(cancelTransaction: DexTransaction, payToAddresses: PayToAddress[]) {
70 | cancelTransaction.status = TransactionStatus.Building;
71 |
72 | cancelTransaction.attachMetadata(MetadataKey.Message, {
73 | msg: [
74 | `[${this._dexter.config.metadataMsgBranding}] ${this._dexName} Cancel Swap`
75 | ]
76 | });
77 |
78 | // Build transaction
79 | cancelTransaction.payToAddresses(payToAddresses)
80 | .then(() => {
81 | cancelTransaction.status = TransactionStatus.Signing;
82 |
83 | // Sign transaction
84 | cancelTransaction.sign()
85 | .then(() => {
86 | cancelTransaction.status = TransactionStatus.Submitting;
87 |
88 | // Submit transaction
89 | cancelTransaction.submit()
90 | .then(() => {
91 | cancelTransaction.status = TransactionStatus.Submitted;
92 | })
93 | .catch((error) => {
94 | cancelTransaction.error = {
95 | step: TransactionStatus.Submitting,
96 | reason: 'Failed submitting transaction.',
97 | reasonRaw: error,
98 | };
99 | cancelTransaction.status = TransactionStatus.Errored;
100 | });
101 | })
102 | .catch((error) => {
103 | cancelTransaction.error = {
104 | step: TransactionStatus.Signing,
105 | reason: 'Failed to sign transaction.',
106 | reasonRaw: error,
107 | };
108 | cancelTransaction.status = TransactionStatus.Errored;
109 | });
110 | })
111 | .catch((error) => {
112 | cancelTransaction.error = {
113 | step: TransactionStatus.Building,
114 | reason: 'Failed to build transaction.',
115 | reasonRaw: error,
116 | };
117 | cancelTransaction.status = TransactionStatus.Errored;
118 | });
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/src/requests/split-cancel-swap-request.ts:
--------------------------------------------------------------------------------
1 | import { Dexter } from '@app/dexter';
2 | import { CancelSwapRequest } from '@requests/cancel-swap-request';
3 | import { PayToAddress, SplitCancelSwapMapping } from '@app/types';
4 | import { DexTransaction } from '@dex/models/dex-transaction';
5 | import { MetadataKey, TransactionStatus } from '@app/constants';
6 |
7 | export class SplitCancelSwapRequest {
8 |
9 | private _dexter: Dexter;
10 | private _cancelRequests: CancelSwapRequest[] = [];
11 |
12 | constructor(dexter: Dexter) {
13 | this._dexter = dexter;
14 | }
15 |
16 | public forTransactions(mappings: SplitCancelSwapMapping[]): SplitCancelSwapRequest {
17 | this._cancelRequests = mappings.map((mapping: SplitCancelSwapMapping) => {
18 | return this._dexter.newCancelSwapRequest()
19 | .forTransaction(mapping.txHash)
20 | .forDex(mapping.dex);
21 | });
22 |
23 | return this;
24 | }
25 |
26 | public submit(): DexTransaction {
27 | if (! this._dexter.walletProvider) {
28 | throw new Error('Wallet provider must be set before submitting a cancel swap order.');
29 | }
30 | if (! this._dexter.walletProvider.isWalletLoaded) {
31 | throw new Error('Wallet must be loaded before submitting a cancel swap order.');
32 | }
33 | if (this._cancelRequests.length === 0) {
34 | throw new Error('Cancel requests were never initialized.');
35 | }
36 |
37 | const cancelTransaction: DexTransaction = this._dexter.walletProvider.createTransaction();
38 |
39 | Promise.all(this._cancelRequests.map((cancelRequest: CancelSwapRequest) => cancelRequest.getPaymentsToAddresses()))
40 | .then((payToAddresses: PayToAddress[][]) => {
41 | this.sendSplitCancelSwapOrder(cancelTransaction, payToAddresses.flat());
42 | });
43 |
44 | return cancelTransaction;
45 | }
46 |
47 | private sendSplitCancelSwapOrder(cancelTransaction: DexTransaction, payToAddresses: PayToAddress[]) {
48 | cancelTransaction.status = TransactionStatus.Building;
49 |
50 | cancelTransaction.attachMetadata(MetadataKey.Message, {
51 | msg: [
52 | `[${this._dexter.config.metadataMsgBranding}] Split Cancel Swap`
53 | ]
54 | });
55 |
56 | // Build transaction
57 | cancelTransaction.payToAddresses(payToAddresses)
58 | .then(() => {
59 | cancelTransaction.status = TransactionStatus.Signing;
60 |
61 | // Sign transaction
62 | cancelTransaction.sign()
63 | .then(() => {
64 | cancelTransaction.status = TransactionStatus.Submitting;
65 |
66 | // Submit transaction
67 | cancelTransaction.submit()
68 | .then(() => {
69 | cancelTransaction.status = TransactionStatus.Submitted;
70 | })
71 | .catch((error) => {
72 | cancelTransaction.error = {
73 | step: TransactionStatus.Submitting,
74 | reason: 'Failed submitting transaction.',
75 | reasonRaw: error,
76 | };
77 | cancelTransaction.status = TransactionStatus.Errored;
78 | });
79 | })
80 | .catch((error) => {
81 | cancelTransaction.error = {
82 | step: TransactionStatus.Signing,
83 | reason: 'Failed to sign transaction.',
84 | reasonRaw: error,
85 | };
86 | cancelTransaction.status = TransactionStatus.Errored;
87 | });
88 | })
89 | .catch((error) => {
90 | cancelTransaction.error = {
91 | step: TransactionStatus.Building,
92 | reason: 'Failed to build transaction.',
93 | reasonRaw: error,
94 | };
95 | cancelTransaction.status = TransactionStatus.Errored;
96 | });
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import { AddressType, DatumParameterKey, TransactionStatus } from './constants';
2 | import { Token } from '@dex/models/asset';
3 | import { BaseDex } from '@dex/base-dex';
4 | import { LiquidityPool } from '@dex/models/liquidity-pool';
5 | import { Script } from 'lucid-cardano';
6 |
7 | export interface DexterConfig {
8 | shouldFetchMetadata?: boolean;
9 | shouldFallbackToApi?: boolean;
10 | shouldSubmitOrders?: boolean;
11 | metadataMsgBranding?: string;
12 | }
13 |
14 | export interface RequestConfig {
15 | timeout?: number;
16 | proxyUrl?: string;
17 | retries?: number;
18 | }
19 |
20 | export interface BlockfrostConfig {
21 | url: string;
22 | projectId: string;
23 | }
24 |
25 | export interface KupoConfig {
26 | url: string;
27 | }
28 |
29 | export interface KupmiosConfig {
30 | kupoUrl: string;
31 | ogmiosUrl: string;
32 | }
33 |
34 | export type AvailableDexs = {
35 | [dex: string]: BaseDex;
36 | };
37 |
38 | export type DatumParameters = {
39 | [key in DatumParameterKey | string]?: string | number | bigint;
40 | };
41 |
42 | export type AssetBalance = {
43 | asset: Token;
44 | quantity: bigint;
45 | };
46 |
47 | export type UTxO = {
48 | txHash: string;
49 | address: string;
50 | datumHash: string;
51 | datum?: string;
52 | outputIndex: number;
53 | assetBalances: AssetBalance[];
54 | };
55 |
56 | export type Transaction = {
57 | hash: string;
58 | inputs: UTxO[];
59 | outputs: UTxO[];
60 | };
61 |
62 | export type AssetAddress = {
63 | address: string;
64 | quantity: bigint;
65 | };
66 |
67 | export type DefinitionBytes = {
68 | bytes: string | DatumParameterKey;
69 | };
70 |
71 | export type DefinitionInt = {
72 | int: number | DatumParameterKey;
73 | };
74 |
75 | export type DefinitionList = {
76 | list: DefinitionField[] | DefinitionList[];
77 | };
78 |
79 | export type DefinitionField = DefinitionConstr | DefinitionBytes | DefinitionInt | DefinitionList| Function | DefinitionField[];
80 |
81 | export type DefinitionConstr = {
82 | constructor: number | DatumParameterKey;
83 | fields: DefinitionField[];
84 | };
85 |
86 | export type WalletOptions = {
87 | addressType?: AddressType;
88 | accountIndex?: number;
89 | };
90 |
91 | export type SpendUTxO = {
92 | utxo: UTxO;
93 | redeemer?: string;
94 | validator?: Script;
95 | signer?: string;
96 | };
97 |
98 | export type PayToAddress = {
99 | address: string;
100 | addressType: AddressType;
101 | assetBalances: AssetBalance[];
102 | spendUtxos?: SpendUTxO[];
103 | datum?: string;
104 | isInlineDatum: boolean;
105 | };
106 |
107 | export type SwapFee = {
108 | id: string;
109 | title: string;
110 | description: string;
111 | value: bigint;
112 | isReturned: boolean;
113 | };
114 |
115 | export type SwapInAmountMapping = {
116 | swapInAmount: bigint;
117 | liquidityPool: LiquidityPool;
118 | };
119 |
120 | export type SwapOutAmountMapping = {
121 | swapOutAmount: bigint;
122 | liquidityPool: LiquidityPool;
123 | };
124 |
125 | export type SplitCancelSwapMapping = {
126 | txHash: string;
127 | dex: string;
128 | };
129 |
130 | export type DexTransactionError = {
131 | step: TransactionStatus;
132 | reason: string;
133 | reasonRaw: string;
134 | };
135 |
136 | export type AssetMetadata = {
137 | policyId: string;
138 | nameHex: string;
139 | decimals: number;
140 | };
141 |
142 | export type Cip30Api = {
143 | getNetworkId(): Promise;
144 | getUtxos(): Promise;
145 | getBalance(): Promise;
146 | getUsedAddresses(): Promise;
147 | getUnusedAddresses(): Promise;
148 | getChangeAddress(): Promise;
149 | getRewardAddresses(): Promise;
150 | signTx(tx: string, partialSign: boolean): Promise;
151 | signData(
152 | address: string,
153 | payload: string
154 | ): Promise<{
155 | signature: string;
156 | key: string;
157 | }>;
158 | submitTx(tx: string): Promise;
159 | getCollateral(): Promise;
160 | experimental: {
161 | getCollateral(): Promise;
162 | on(eventName: string, callback: (...args: unknown[]) => void): void;
163 | off(eventName: string, callback: (...args: unknown[]) => void): void;
164 | };
165 | };
166 |
167 | export type DatumJson = {
168 | int?: number;
169 | bytes?: string;
170 | list?: Array;
171 | map?: Array<{ k: unknown; v: unknown }>;
172 | fields?: Array;
173 | [constructor: string]: unknown;
174 | };
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { Token } from '@dex/models/asset';
2 | import { LiquidityPool } from '@dex/models/liquidity-pool';
3 | import { C, Datum, fromHex, Lucid, toHex, Utils } from 'lucid-cardano';
4 | import { DatumJson } from '@app/types';
5 | import { encoder } from 'js-encoding-utils';
6 |
7 | export const lucidUtils: Utils = new Utils(new Lucid());
8 |
9 | export function tokensMatch(tokenA: Token, tokenB: Token): boolean {
10 | const tokenAId: string = tokenA === 'lovelace' ? 'lovelace' : tokenA.identifier();
11 | const tokenBId: string = tokenB === 'lovelace' ? 'lovelace' : tokenB.identifier();
12 |
13 | return tokenAId === tokenBId;
14 | }
15 |
16 | export function correspondingReserves(liquidityPool: LiquidityPool, token: Token): bigint[] {
17 | return tokensMatch(token, liquidityPool.assetA)
18 | ? [liquidityPool.reserveA, liquidityPool.reserveB]
19 | : [liquidityPool.reserveB, liquidityPool.reserveA]
20 | }
21 |
22 | export function appendSlash(value?: string) {
23 | if (! value) return '';
24 | if (value.endsWith('/')) return;
25 |
26 | return `${value}/`;
27 | }
28 |
29 | /**
30 | * Modified version from lucid
31 | */
32 | export function datumJsonToCbor(json: DatumJson): Datum {
33 | const convert = (json: DatumJson): C.PlutusData => {
34 | if (!isNaN(json.int!)) {
35 | return C.PlutusData.new_integer(C.BigInt.from_str(json.int!.toString()));
36 | } else if (json.bytes || !isNaN(Number(json.bytes))) {
37 | return C.PlutusData.new_bytes(fromHex(json.bytes!));
38 | } else if (json.map) {
39 | const l = C.PlutusList.new();
40 | (json as any).forEach((v: DatumJson) => {
41 | l.add(convert(v));
42 | });
43 | return C.PlutusData.new_list(l);
44 | } else if (json.list) {
45 | const l = C.PlutusList.new();
46 | json.list.forEach((v: DatumJson) => {
47 | l.add(convert(v));
48 | });
49 | return C.PlutusData.new_list(l);
50 | } else if (!isNaN(json.constructor! as unknown as number)) {
51 | const l = C.PlutusList.new();
52 | json.fields!.forEach((v: DatumJson) => {
53 | l.add(convert(v));
54 | });
55 | return C.PlutusData.new_constr_plutus_data(
56 | C.ConstrPlutusData.new(
57 | C.BigNum.from_str(json.constructor!.toString()),
58 | l,
59 | ),
60 | );
61 | }
62 | throw new Error("Unsupported type");
63 | };
64 |
65 | return toHex(convert(json).to_bytes());
66 | }
67 |
68 | export const bytesToHex = (bytes: Uint8Array): string => encoder.arrayBufferToHexString(bytes);
69 | export const hexToBytes = (hex: string): Uint8Array => encoder.hexStringToArrayBuffer(hex);
--------------------------------------------------------------------------------
/tests/dex-transaction.test.ts:
--------------------------------------------------------------------------------
1 | import { DexTransaction, MockWalletProvider, TransactionStatus } from '../src';
2 |
3 | describe('DexTransaction', () => {
4 |
5 | const transaction: DexTransaction = new DexTransaction(new MockWalletProvider());
6 |
7 | it('Fails to submit when un-signed', () => {
8 | expect(() => { transaction.submit(); }).toThrowError();
9 | });
10 |
11 | it('Can sign', () => {
12 | return transaction.sign()
13 | .then(() => {
14 | expect(transaction.isSigned).toBe(true);
15 | });
16 | });
17 |
18 | it('Can submit', () => {
19 | return transaction.submit()
20 | .then(() => {
21 | expect(transaction.hash).toBe('hashtest');
22 | });
23 | });
24 |
25 | it('Can expose building status', () => {
26 | const testTransaction: DexTransaction = new DexTransaction(new MockWalletProvider());
27 | let receivedStatus: boolean = false;
28 |
29 | testTransaction.onBuilding(() => receivedStatus = true);
30 |
31 | testTransaction.status = TransactionStatus.Building;
32 |
33 | expect(receivedStatus).toBe(true);
34 | });
35 |
36 | it('Can expose signing status', () => {
37 | const testTransaction: DexTransaction = new DexTransaction(new MockWalletProvider());
38 | let receivedStatus: boolean = false;
39 |
40 | testTransaction.onSigning(() => receivedStatus = true);
41 |
42 | testTransaction.status = TransactionStatus.Signing;
43 |
44 | expect(receivedStatus).toBe(true);
45 | });
46 |
47 | it('Can expose submitting status', () => {
48 | const testTransaction: DexTransaction = new DexTransaction(new MockWalletProvider());
49 | let receivedStatus: boolean = false;
50 |
51 | testTransaction.onSubmitting(() => receivedStatus = true);
52 |
53 | testTransaction.status = TransactionStatus.Submitting;
54 |
55 | expect(receivedStatus).toBe(true);
56 | });
57 |
58 | it('Can expose submitted status', () => {
59 | const testTransaction: DexTransaction = new DexTransaction(new MockWalletProvider());
60 | let receivedStatus: boolean = false;
61 |
62 | testTransaction.onSubmitted(() => receivedStatus = true);
63 |
64 | testTransaction.status = TransactionStatus.Submitted;
65 |
66 | expect(receivedStatus).toBe(true);
67 | });
68 |
69 | it('Can expose error status', () => {
70 | const testTransaction: DexTransaction = new DexTransaction(new MockWalletProvider());
71 | let receivedStatus: boolean = false;
72 |
73 | testTransaction.onError(() => receivedStatus = true);
74 |
75 | testTransaction.status = TransactionStatus.Errored;
76 |
77 | expect(receivedStatus).toBe(true);
78 | });
79 |
80 | });
81 |
--------------------------------------------------------------------------------
/tests/minswap.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Asset,
3 | Dexter,
4 | LiquidityPool,
5 | Minswap,
6 | MockDataProvider,
7 | SwapRequest,
8 | MockWalletProvider,
9 | DatumParameters,
10 | DatumParameterKey,
11 | PayToAddress,
12 | AddressType,
13 | UTxO
14 | } from '../src';
15 |
16 | describe('Minswap', () => {
17 | let minswap: Minswap;
18 | const returnAddress = 'mockBlockchainAddress123';
19 |
20 | beforeEach(() => {
21 | minswap = new Minswap();
22 | });
23 | const walletProvider: MockWalletProvider = new MockWalletProvider();
24 | walletProvider.loadWalletFromSeedPhrase(['']);
25 | const dexter: Dexter = (new Dexter())
26 | .withDataProvider(new MockDataProvider())
27 | .withWalletProvider(walletProvider);
28 | const asset: Asset = new Asset('f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b69880', '69555344', 6);
29 |
30 |
31 | describe('Set Swap In', () => {
32 |
33 | const liquidityPool: LiquidityPool = new LiquidityPool(
34 | Minswap.identifier,
35 | 'lovelace',
36 | asset,
37 | 30817255371488n,
38 | 349805856622734n,
39 | 'addr1',
40 | );
41 | liquidityPool.poolFeePercent = 0.3;
42 |
43 | const swapRequest: SwapRequest = dexter.newSwapRequest()
44 | .forLiquidityPool(liquidityPool)
45 | .withSwapInToken('lovelace')
46 | .withSwapInAmount(10_000_000_000000n)
47 | .withSlippagePercent(0.5);
48 |
49 | it('Can calculate swap parameters', () => {
50 | expect(+swapRequest.getPriceImpactPercent().toFixed(2)).toEqual(24.37);
51 | expect(swapRequest.getEstimatedReceive()).toEqual(85_506_228_814959n);
52 | expect(swapRequest.getMinimumReceive()).toEqual(85_080_824_691501n);
53 | });
54 |
55 | it('Can build swap order', () => {
56 | const minswap: Minswap = new Minswap();
57 | const defaultSwapParameters: DatumParameters = {
58 | [DatumParameterKey.SenderPubKeyHash]: walletProvider.publicKeyHash(),
59 | [DatumParameterKey.SenderStakingKeyHash]: walletProvider.stakingKeyHash(),
60 | [DatumParameterKey.ReceiverPubKeyHash]: walletProvider.publicKeyHash(),
61 | [DatumParameterKey.ReceiverStakingKeyHash]: walletProvider.stakingKeyHash(),
62 | [DatumParameterKey.SwapInAmount]: swapRequest.swapInAmount,
63 | [DatumParameterKey.MinReceive]: swapRequest.getMinimumReceive(),
64 | [DatumParameterKey.SwapInTokenPolicyId]: '',
65 | [DatumParameterKey.SwapInTokenAssetName]: '',
66 | [DatumParameterKey.SwapOutTokenPolicyId]: asset.policyId,
67 | [DatumParameterKey.SwapOutTokenAssetName]: asset.nameHex,
68 | };
69 |
70 | return minswap.buildSwapOrder(liquidityPool, defaultSwapParameters)
71 | .then((payments: PayToAddress[]) => {
72 | expect(() => { minswap.buildSwapOrder(liquidityPool, defaultSwapParameters); }).not.toThrowError();
73 | expect(payments[0].addressType).toBe(AddressType.Contract);
74 | expect(payments[0].assetBalances[0].quantity).toEqual(10000004000000n);
75 | expect(payments[0].datum).toBe('d8799fd8799fd8799f42ed56ffd8799fd8799fd8799f42ed56ffffffffd8799fd8799f42ed56ffd8799fd8799fd8799f42bac6ffffffffd87a80d8799fd8799f581cf66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b698804469555344ff1b00004d616c553b2dff1a001e84801a001e8480ff');
76 | });
77 | });
78 |
79 | });
80 |
81 | describe('Set Swap Out', () => {
82 | const liquidityPool: LiquidityPool = new LiquidityPool(
83 | Minswap.identifier,
84 | 'lovelace',
85 | asset,
86 | 5126788000507n,
87 | 1405674367646n,
88 | 'addr1',
89 | );
90 | liquidityPool.poolFeePercent = 0.3;
91 |
92 | const swapRequest: SwapRequest = dexter.newSwapRequest()
93 | .forLiquidityPool(liquidityPool)
94 | .withSwapInToken('lovelace')
95 | .withSwapOutAmount(100_000000n)
96 | .withSlippagePercent(0.5);
97 |
98 | it('Can calculate swap parameters', () => {
99 | expect(swapRequest.swapInAmount).toEqual(365844367n);
100 | });
101 |
102 | });
103 |
104 | describe('Minswap Cancel Order', () => {
105 |
106 | it('should successfully cancel an order', async () => {
107 | let marketOrderAddress = minswap.marketOrderAddress;
108 | const txOutputs: UTxO[] = [
109 | {
110 | txHash: 'mockTxHash123',
111 | address: marketOrderAddress,
112 | datumHash: 'mockDatumHash123',
113 | outputIndex: 0,
114 | assetBalances: [{ asset: 'lovelace', quantity: 1000000000000n }]
115 | }
116 | ];
117 |
118 | const result = await minswap.buildCancelSwapOrder(txOutputs, returnAddress);
119 |
120 | expect(result).toBeDefined();
121 | expect(result[0].address).toBe(returnAddress);
122 | });
123 |
124 | it('should fail to cancel an order with invalid UTxO', async () => {
125 | const invalidTxOutputs: UTxO[] = [
126 | {
127 | txHash: 'invalidTxHash',
128 | address: 'invalidAddress',
129 | datumHash: 'invalidDatumHash',
130 | outputIndex: 0,
131 | assetBalances: [{ asset: 'lovelace', quantity: 1000n }]
132 | }
133 | ];
134 |
135 |
136 | try {
137 | await minswap.buildCancelSwapOrder(invalidTxOutputs, returnAddress);
138 | fail('Expected buildCancelSwapOrder to throw an error');
139 | } catch (error: unknown) {
140 | if (error instanceof Error) {
141 | expect(error.message).toContain('Unable to find relevant UTxO for cancelling the swap order.');
142 | }
143 | }
144 | });
145 | });
146 |
147 | });
148 |
--------------------------------------------------------------------------------
/tests/muesliswap.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Asset,
3 | Dexter,
4 | LiquidityPool,
5 | MuesliSwap,
6 | MockDataProvider,
7 | SwapRequest,
8 | MockWalletProvider,
9 | DatumParameters,
10 | DatumParameterKey,
11 | PayToAddress,
12 | AddressType,
13 | UTxO
14 | } from '../src';
15 |
16 | describe('MuesliSwap', () => {
17 |
18 | const walletProvider: MockWalletProvider = new MockWalletProvider();
19 | walletProvider.loadWalletFromSeedPhrase(['']);
20 | const dexter: Dexter = (new Dexter())
21 | .withDataProvider(new MockDataProvider())
22 | .withWalletProvider(walletProvider);
23 | const asset: Asset = new Asset('f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b69880', '69555344', 6);
24 |
25 | describe('Set Swap In', () => {
26 |
27 | const liquidityPool: LiquidityPool = new LiquidityPool(
28 | MuesliSwap.identifier,
29 | 'lovelace',
30 | asset,
31 | 1386837721743n,
32 | 485925n,
33 | 'addr1',
34 | );
35 | liquidityPool.poolFeePercent = 0.3;
36 |
37 | const swapRequest: SwapRequest = dexter.newSwapRequest()
38 | .forLiquidityPool(liquidityPool)
39 | .withSwapInToken('lovelace')
40 | .withSwapInAmount(100_000_000000n)
41 | .withSlippagePercent(3.0);
42 |
43 | it('Can calculate swap parameters', () => {
44 | expect(+swapRequest.getPriceImpactPercent().toFixed(2)).toEqual(7.51);
45 | expect(swapRequest.getEstimatedReceive()).toEqual(32590n);
46 | expect(swapRequest.getMinimumReceive()).toEqual(31640n);
47 | });
48 |
49 | it('Can build swap order', () => {
50 | const muesliswap: MuesliSwap = new MuesliSwap();
51 | const defaultSwapParameters: DatumParameters = {
52 | [DatumParameterKey.SenderPubKeyHash]: walletProvider.publicKeyHash(),
53 | [DatumParameterKey.SenderStakingKeyHash]: walletProvider.stakingKeyHash(),
54 | [DatumParameterKey.ReceiverPubKeyHash]: walletProvider.publicKeyHash(),
55 | [DatumParameterKey.ReceiverStakingKeyHash]: walletProvider.stakingKeyHash(),
56 | [DatumParameterKey.SwapInAmount]: swapRequest.swapInAmount,
57 | [DatumParameterKey.MinReceive]: swapRequest.getMinimumReceive(),
58 | [DatumParameterKey.SwapInTokenPolicyId]: '',
59 | [DatumParameterKey.SwapInTokenAssetName]: '',
60 | [DatumParameterKey.SwapOutTokenPolicyId]: asset.policyId,
61 | [DatumParameterKey.SwapOutTokenAssetName]: asset.nameHex,
62 | };
63 |
64 | return muesliswap.buildSwapOrder(liquidityPool, defaultSwapParameters)
65 | .then((payments: PayToAddress[]) => {
66 | expect(() => { muesliswap.buildSwapOrder(liquidityPool, defaultSwapParameters); }).not.toThrowError();
67 | expect(payments[0].addressType).toBe(AddressType.Contract);
68 | expect(payments[0].assetBalances[0].quantity).toEqual(100002650000n);
69 | expect(payments[0].datum).toBe('d8799fd8799fd8799fd8799f42ed56ffd8799fd8799fd8799f42bac6ffffffff581cf66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b6988044695553444040197b98d87a801a00286f90ffff');
70 | });
71 | });
72 |
73 | });
74 |
75 | describe('Set Swap Out', () => {
76 | const liquidityPool: LiquidityPool = new LiquidityPool(
77 | MuesliSwap.identifier,
78 | 'lovelace',
79 | asset,
80 | 67011967873n,
81 | 5026969972n,
82 | 'addr1',
83 | );
84 | liquidityPool.poolFeePercent = 0.3;
85 |
86 | const swapRequest: SwapRequest = dexter.newSwapRequest()
87 | .forLiquidityPool(liquidityPool)
88 | .withSwapInToken('lovelace')
89 | .withSwapOutAmount(10_000000n)
90 | .withSlippagePercent(0.5);
91 |
92 | it('Can calculate swap parameters', () => {
93 | expect(swapRequest.swapInAmount).toEqual(133971309n);
94 | });
95 |
96 | });
97 |
98 | describe('Muesliswap Cancel Order', () => {
99 | let muesliswap: MuesliSwap;
100 | const returnAddress = 'mockBlockchainAddress123';
101 |
102 | beforeEach(() => {
103 | muesliswap = new MuesliSwap();
104 | });
105 |
106 | it('should successfully cancel an order', async () => {
107 | let orderAddress = muesliswap.orderAddress;
108 | const txOutputs: UTxO[] = [
109 | {
110 | txHash: 'mockTxHash123',
111 | address: orderAddress,
112 | datumHash: 'mockDatumHash123',
113 | outputIndex: 0,
114 | assetBalances: [{ asset: 'lovelace', quantity: 10000n }]
115 | }
116 | ];
117 |
118 | const result = await muesliswap.buildCancelSwapOrder(txOutputs, returnAddress);
119 |
120 | expect(result).toBeDefined();
121 | expect(result[0].address).toBe(returnAddress);
122 | });
123 |
124 | it('should fail to cancel an order with invalid UTxO', async () => {
125 | const invalidTxOutputs: UTxO[] = [
126 | {
127 | txHash: 'invalidTxHash',
128 | address: 'invalidAddress',
129 | datumHash: 'invalidDatumHash',
130 | outputIndex: 0,
131 | assetBalances: [{ asset: 'lovelace', quantity: 10000n }]
132 | }
133 | ];
134 |
135 | try {
136 | await muesliswap.buildCancelSwapOrder(invalidTxOutputs, returnAddress);
137 | fail('Expected buildCancelSwapOrder to throw an error');
138 | } catch (error: unknown) {
139 | if (error instanceof Error) {
140 | expect(error.message).toContain('Unable to find relevant UTxO for cancelling the swap order.');
141 | }
142 | }
143 | });
144 |
145 | });
146 |
147 | });
148 |
--------------------------------------------------------------------------------
/tests/split-swap-request.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Asset,
3 | Dexter,
4 | LiquidityPool,
5 | MockDataProvider,
6 | MockWalletProvider,
7 | WingRiders,
8 | Minswap
9 | } from '../src';
10 | import { SplitSwapRequest } from '../src/requests/split-swap-request';
11 |
12 | describe('SplitSwapRequest', () => {
13 |
14 | const walletProvider: MockWalletProvider = new MockWalletProvider();
15 | walletProvider.loadWalletFromSeedPhrase(['']);
16 | const dexter: Dexter = (new Dexter())
17 | .withDataProvider(new MockDataProvider())
18 | .withWalletProvider(walletProvider);
19 | const asset: Asset = new Asset('f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b69880', '69555344', 6);
20 |
21 | const minswapPool: LiquidityPool = new LiquidityPool(
22 | Minswap.identifier,
23 | 'lovelace',
24 | asset,
25 | 30817255371488n,
26 | 349805856622734n,
27 | 'addr1',
28 | );
29 | minswapPool.poolFeePercent = 0.3
30 |
31 | const wingridersPool: LiquidityPool = new LiquidityPool(
32 | WingRiders.identifier,
33 | 'lovelace',
34 | asset,
35 | 923224398616n,
36 | 7942169n,
37 | 'addr1',
38 | );
39 | wingridersPool.poolFeePercent = 0.35;
40 |
41 | describe('Parameter setting', () => {
42 |
43 | const swapRequest: SplitSwapRequest = dexter.newSplitSwapRequest();
44 |
45 | it('Can set swap in token', () => {
46 | swapRequest.withSwapInToken('lovelace')
47 |
48 | expect(swapRequest.swapInToken).toBe('lovelace');
49 | });
50 |
51 | it('Can set swap out token', () => {
52 | swapRequest.withSwapOutToken('lovelace')
53 |
54 | expect(swapRequest.swapOutToken).toBe('lovelace');
55 | });
56 |
57 | it('Can set swap in mappings', () => {
58 | swapRequest.withSwapInAmountMappings([
59 | {
60 | swapInAmount: 2_000000n,
61 | liquidityPool: minswapPool,
62 | }
63 | ])
64 |
65 | expect(swapRequest.swapRequests[0].liquidityPool.uuid).toBe(minswapPool.uuid);
66 | expect(swapRequest.swapRequests[0].swapInAmount).toBe(2_000000n);
67 | });
68 |
69 | it('Can set swap out mappings', () => {
70 | swapRequest.withSwapOutAmountMappings([
71 | {
72 | swapOutAmount: 2_000000n,
73 | liquidityPool: minswapPool,
74 | }
75 | ])
76 |
77 | expect(swapRequest.swapRequests[0].liquidityPool.uuid).toBe(minswapPool.uuid);
78 | });
79 |
80 | })
81 |
82 | describe('Alter Order', () => {
83 |
84 | it('Can flip all swap in tokens', () => {
85 | const swapRequest: SplitSwapRequest = dexter.newSplitSwapRequest();
86 |
87 | swapRequest
88 | .withSwapInToken(asset)
89 | .withSwapInAmountMappings([
90 | {
91 | swapInAmount: 2_000000n,
92 | liquidityPool: minswapPool,
93 | },
94 | {
95 | swapInAmount: 2_000000n,
96 | liquidityPool: wingridersPool,
97 | }
98 | ])
99 | .flip();
100 |
101 | expect(swapRequest.swapRequests[0].swapInToken).toBe('lovelace');
102 | expect(swapRequest.swapRequests[1].swapInToken).toBe('lovelace');
103 | });
104 |
105 | it('Can calculate avg. price impact percent', () => {
106 | const swapRequest: SplitSwapRequest = dexter.newSplitSwapRequest();
107 |
108 | swapRequest
109 | .withSwapInToken('lovelace')
110 | .withSlippagePercent(0.5)
111 | .withSwapInAmountMappings([
112 | {
113 | swapInAmount: 10_000_000_000000n,
114 | liquidityPool: minswapPool,
115 | },
116 | {
117 | swapInAmount: 10_000_000000n,
118 | liquidityPool: wingridersPool,
119 | }
120 | ]);
121 |
122 | expect(+swapRequest.getAvgPriceImpactPercent().toFixed(2)).toEqual(12.90);
123 | });
124 |
125 | });
126 |
127 | });
128 |
--------------------------------------------------------------------------------
/tests/sundaeswap-v1.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Asset,
3 | Dexter,
4 | LiquidityPool,
5 | SundaeSwapV1,
6 | MockDataProvider,
7 | SwapRequest,
8 | MockWalletProvider,
9 | DatumParameters,
10 | DatumParameterKey,
11 | PayToAddress,
12 | AddressType,
13 | UTxO,
14 | } from '../src';
15 |
16 | describe('SundaeSwapV1', () => {
17 |
18 | const walletProvider: MockWalletProvider = new MockWalletProvider();
19 | walletProvider.loadWalletFromSeedPhrase(['']);
20 | const dexter: Dexter = (new Dexter())
21 | .withDataProvider(new MockDataProvider())
22 | .withWalletProvider(walletProvider);
23 | const asset: Asset = new Asset('f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b69880', '69555344', 6);
24 |
25 | describe('Set Swap In', () => {
26 |
27 | const liquidityPool: LiquidityPool = new LiquidityPool(
28 | SundaeSwapV1.identifier,
29 | 'lovelace',
30 | asset,
31 | 3699642000000n,
32 | 78391015000000n,
33 | 'addr1',
34 | );
35 | liquidityPool.poolFeePercent = 0.3;
36 |
37 |
38 | const swapRequest: SwapRequest = dexter.newSwapRequest()
39 | .forLiquidityPool(liquidityPool)
40 | .withSwapInToken('lovelace')
41 | .withSwapInAmount(10_000_000000n)
42 | .withSlippagePercent(1.0);
43 |
44 | it('Can calculate swap parameters', () => {
45 | expect(+swapRequest.getPriceImpactPercent().toFixed(2)).toEqual(0.27);
46 | expect(swapRequest.getEstimatedReceive()).toEqual(210_684_680649n);
47 | expect(swapRequest.getMinimumReceive()).toEqual(208_598_693711n);
48 | });
49 |
50 | it('Can build swap order', () => {
51 | const sundaeswap: SundaeSwapV1 = new SundaeSwapV1();
52 | const defaultSwapParameters: DatumParameters = {
53 | [DatumParameterKey.PoolIdentifier]: '1234',
54 | [DatumParameterKey.SenderPubKeyHash]: walletProvider.publicKeyHash(),
55 | [DatumParameterKey.SenderStakingKeyHash]: walletProvider.stakingKeyHash(),
56 | [DatumParameterKey.ReceiverPubKeyHash]: walletProvider.publicKeyHash(),
57 | [DatumParameterKey.ReceiverStakingKeyHash]: walletProvider.stakingKeyHash(),
58 | [DatumParameterKey.SwapInAmount]: swapRequest.swapInAmount,
59 | [DatumParameterKey.MinReceive]: swapRequest.getMinimumReceive(),
60 | [DatumParameterKey.SwapInTokenPolicyId]: '',
61 | [DatumParameterKey.SwapInTokenAssetName]: '',
62 | [DatumParameterKey.SwapOutTokenPolicyId]: asset.policyId,
63 | [DatumParameterKey.SwapOutTokenAssetName]: asset.nameHex,
64 | };
65 |
66 | return sundaeswap.buildSwapOrder(liquidityPool, defaultSwapParameters)
67 | .then((payments: PayToAddress[]) => {
68 | expect(() => { sundaeswap.buildSwapOrder(liquidityPool, defaultSwapParameters); }).not.toThrowError();
69 | expect(payments[0].addressType).toBe(AddressType.Contract);
70 | expect(payments[0].assetBalances[0].quantity).toEqual(10004500000n);
71 | expect(payments[0].datum).toBe('d8799f421234d8799fd8799fd8799fd8799f42ed56ffd8799fd8799fd8799f42bac6ffffffffd87a80ffd87a80ff1a002625a0d8799fd879801b00000002540be400d8799f1b000000309173774fffffff');
72 | });
73 | });
74 |
75 | });
76 |
77 | describe('Set Swap Out', () => {
78 |
79 | const liquidityPool: LiquidityPool = new LiquidityPool(
80 | SundaeSwapV1.identifier,
81 | 'lovelace',
82 | asset,
83 | 1032791394311n,
84 | 74925313821n,
85 | 'addr1',
86 | );
87 | liquidityPool.poolFeePercent = 0.3;
88 |
89 | const swapRequest: SwapRequest = dexter.newSwapRequest()
90 | .forLiquidityPool(liquidityPool)
91 | .withSwapInToken('lovelace')
92 | .withSwapOutAmount(100_000000n)
93 | .withSlippagePercent(0.5);
94 |
95 | it('Can calculate swap parameters', () => {
96 | expect(swapRequest.swapInAmount).toEqual(1384410858n);
97 | });
98 |
99 | });
100 |
101 | describe('SundaeSwap Cancel Order', () => {
102 | let sundaeswap: SundaeSwapV1;
103 | const returnAddress = 'addr1';
104 |
105 | beforeEach(() => {
106 | sundaeswap = new SundaeSwapV1();
107 | });
108 |
109 | it('should successfully cancel an order', async () => {
110 | let marketOrderAddress = sundaeswap.orderAddress;
111 | const txOutputs: UTxO[] = [
112 | {
113 | txHash: 'mockTxHash123',
114 | address: marketOrderAddress,
115 | datumHash: 'mockDatumHash123',
116 | outputIndex: 0,
117 | assetBalances: [{ asset: 'lovelace', quantity: 1000000000000n }]
118 | }
119 | ];
120 |
121 | const result = await sundaeswap.buildCancelSwapOrder(txOutputs, returnAddress);
122 |
123 | expect(result).toBeDefined();
124 | expect(result[0].address).toBe(returnAddress);
125 | });
126 |
127 | it('should fail to cancel an order with invalid UTxO', async () => {
128 | const invalidTxOutputs: UTxO[] = [
129 | {
130 | txHash: 'invalidTxHash',
131 | address: 'invalidAddress',
132 | datumHash: 'invalidDatumHash',
133 | outputIndex: 0,
134 | assetBalances: [{ asset: 'lovelace', quantity: 1000000000000n }]
135 | }
136 | ];
137 | try {
138 | await sundaeswap.buildCancelSwapOrder(invalidTxOutputs, returnAddress);
139 | fail('Expected buildCancelSwapOrder to throw an error');
140 | } catch (error: unknown) {
141 | if (error instanceof Error) {
142 | expect(error.message).toContain('Unable to find relevant UTxO for cancelling the swap order.');
143 | }
144 | }
145 |
146 | });
147 |
148 | });
149 |
150 | });
151 |
--------------------------------------------------------------------------------
/tests/swap-request.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Asset,
3 | Dexter,
4 | LiquidityPool,
5 | MockDataProvider,
6 | MockWalletProvider,
7 | SwapRequest,
8 | WingRiders,
9 | Minswap
10 | } from '../src';
11 |
12 | describe('SwapRequest', () => {
13 |
14 | const walletProvider: MockWalletProvider = new MockWalletProvider();
15 | walletProvider.loadWalletFromSeedPhrase(['']);
16 | const dexter: Dexter = (new Dexter())
17 | .withDataProvider(new MockDataProvider())
18 | .withWalletProvider(walletProvider);
19 | const asset: Asset = new Asset('f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b69880', '69555344', 6);
20 |
21 | describe('Parameter setting', () => {
22 |
23 | const liquidityPool: LiquidityPool = new LiquidityPool(
24 | WingRiders.identifier,
25 | 'lovelace',
26 | asset,
27 | 1_000000n,
28 | 1_000000n,
29 | 'addr1',
30 | );
31 | const swapRequest: SwapRequest = dexter.newSwapRequest()
32 | .forLiquidityPool(liquidityPool);
33 |
34 | it('Can set liquidity pool', () => {
35 | expect(swapRequest.liquidityPool.uuid).toBe(liquidityPool.uuid);
36 | });
37 |
38 | it('Can set swap tokens', () => {
39 | swapRequest.withSwapInToken('lovelace');
40 |
41 | expect(swapRequest.swapInToken).toBe('lovelace');
42 | expect((swapRequest.swapOutToken as Asset).identifier()).toBe(asset.identifier());
43 | });
44 |
45 | it('Fails on invalid swap in token', () => {
46 | expect(() => { swapRequest.withSwapInToken(new Asset('test1', 'test2')); }).toThrowError();
47 | });
48 |
49 | it('Can flip swap tokens', () => {
50 | swapRequest.withSwapInToken('lovelace')
51 | .flip();
52 |
53 | expect((swapRequest.swapInToken as Asset).identifier()).toBe(asset.identifier());
54 | expect(swapRequest.swapOutToken).toBe('lovelace');
55 | });
56 |
57 | it('Can set swap in amount', () => {
58 | swapRequest.withSwapInToken('lovelace')
59 | .withSwapInAmount(100n);
60 |
61 | expect(swapRequest.swapInAmount).toBe(100n);
62 | });
63 |
64 | it('Fails on incorrect swap in amount', () => {
65 | swapRequest.withSwapInToken('lovelace');
66 | swapRequest.withSwapInAmount(-1n)
67 |
68 | expect(swapRequest.swapInAmount).toBe(0n);
69 | });
70 |
71 | it('Can set slippage percent', () => {
72 | swapRequest.withSlippagePercent(5.0);
73 |
74 | expect(swapRequest.slippagePercent).toBe(5.0);
75 | });
76 |
77 | it('Fails on incorrect slippage percent', () => {
78 | expect(() => { swapRequest.withSlippagePercent(-5.0); }).toThrowError();
79 | });
80 |
81 | });
82 |
83 | describe('Alter Order', () => {
84 |
85 | const liquidityPool: LiquidityPool = new LiquidityPool(
86 | Minswap.identifier,
87 | 'lovelace',
88 | asset,
89 | 30817255371488n,
90 | 349805856622734n,
91 | 'addr1',
92 | );
93 | liquidityPool.poolFeePercent = 0.3;
94 |
95 | const swapRequest: SwapRequest = dexter.newSwapRequest()
96 | .forLiquidityPool(liquidityPool)
97 | .withSwapInToken('lovelace')
98 | .withSwapInAmount(10_000_000_000000n)
99 | .withSlippagePercent(0.5);
100 |
101 | it('Can flip swap in & swap out amounts', () => {
102 | swapRequest.flip();
103 |
104 | expect(swapRequest.swapOutToken).toBe('lovelace');
105 | expect(swapRequest.swapInAmount).toBe(168542118380811n);
106 | expect(swapRequest.getEstimatedReceive()).toBe(10_000_000_000000n);
107 | });
108 |
109 | });
110 |
111 | });
112 |
--------------------------------------------------------------------------------
/tests/vyfinance.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AddressType,
3 | Asset,
4 | DatumParameterKey,
5 | DatumParameters,
6 | Dexter,
7 | LiquidityPool,
8 | MockDataProvider,
9 | MockWalletProvider,
10 | PayToAddress,
11 | SwapRequest,
12 | UTxO,
13 | VyFinance,
14 | } from '../src';
15 |
16 | describe('VyFinance', () => {
17 |
18 | const walletProvider: MockWalletProvider = new MockWalletProvider();
19 | walletProvider.loadWalletFromSeedPhrase(['']);
20 | const dexter: Dexter = (new Dexter())
21 | .withDataProvider(new MockDataProvider())
22 | .withWalletProvider(walletProvider);
23 | const asset: Asset = new Asset('f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b69880', '69555344', 6);
24 |
25 | describe('Set Swap In', () => {
26 |
27 | const liquidityPool: LiquidityPool = new LiquidityPool(
28 | VyFinance.identifier,
29 | 'lovelace',
30 | asset,
31 | 519219742499n,
32 | 39619096012n,
33 | 'addr1',
34 | );
35 | liquidityPool.poolFeePercent = 0.3;
36 | liquidityPool.marketOrderAddress = 'addr1testorder';
37 |
38 | const swapRequest: SwapRequest = dexter.newSwapRequest()
39 | .forLiquidityPool(liquidityPool)
40 | .withSwapInToken('lovelace')
41 | .withSwapInAmount(100_000_000000n)
42 | .withSlippagePercent(0.5);
43 |
44 | it('Can calculate swap parameters', () => {
45 | expect(+swapRequest.getPriceImpactPercent().toFixed(2)).toEqual(16.11);
46 | expect(swapRequest.getEstimatedReceive()).toEqual(6382126148n);
47 | expect(swapRequest.getMinimumReceive()).toEqual(6350374276n);
48 | });
49 |
50 | it('Can build swap order', () => {
51 | const vyfi: VyFinance = new VyFinance();
52 | const defaultSwapParameters: DatumParameters = {
53 | [DatumParameterKey.SenderPubKeyHash]: walletProvider.publicKeyHash(),
54 | [DatumParameterKey.SenderStakingKeyHash]: walletProvider.stakingKeyHash(),
55 | [DatumParameterKey.ReceiverPubKeyHash]: walletProvider.publicKeyHash(),
56 | [DatumParameterKey.ReceiverStakingKeyHash]: walletProvider.stakingKeyHash(),
57 | [DatumParameterKey.SwapInAmount]: swapRequest.swapInAmount,
58 | [DatumParameterKey.MinReceive]: swapRequest.getMinimumReceive(),
59 | [DatumParameterKey.SwapInTokenPolicyId]: '',
60 | [DatumParameterKey.SwapInTokenAssetName]: '',
61 | [DatumParameterKey.SwapOutTokenPolicyId]: asset.policyId,
62 | [DatumParameterKey.SwapOutTokenAssetName]: asset.nameHex,
63 | };
64 |
65 | return vyfi.buildSwapOrder(liquidityPool, defaultSwapParameters)
66 | .then((payments: PayToAddress[]) => {
67 | expect(() => { vyfi.buildSwapOrder(liquidityPool, defaultSwapParameters); }).not.toThrowError();
68 | expect(payments[0].addressType).toBe(AddressType.Contract);
69 | expect(payments[0].assetBalances[0].quantity).toBe(100_003_900000n);
70 | expect(payments[0].datum).toBe('d8799f44ed56bac6d87c9f1b000000017a830584ffff');
71 | });
72 | });
73 |
74 | });
75 |
76 | describe('Set Swap Out', () => {
77 |
78 | const liquidityPool: LiquidityPool = new LiquidityPool(
79 | VyFinance.identifier,
80 | 'lovelace',
81 | asset,
82 | 519179782499n,
83 | 39622139292n,
84 | 'addr1',
85 | );
86 | liquidityPool.poolFeePercent = 0.3;
87 |
88 | const swapRequest: SwapRequest = dexter.newSwapRequest()
89 | .forLiquidityPool(liquidityPool)
90 | .withSwapInToken('lovelace')
91 | .withSwapOutAmount(1_000_000000n)
92 | .withSlippagePercent(0.5);
93 |
94 | it('Can calculate swap parameters', () => {
95 | expect(swapRequest.swapInAmount).toEqual(13482992348n);
96 | });
97 |
98 | });
99 |
100 | describe('VyFinance Cancel Order', () => {
101 | let vyFinance: VyFinance;
102 | const returnAddress = 'addr1';
103 | beforeEach(() => {
104 | vyFinance = new VyFinance();
105 | vyFinance.api.liquidityPools = async () => {
106 | const liquidityPool = new LiquidityPool(
107 | VyFinance.identifier,
108 | 'lovelace',
109 | new Asset('f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b69880', '69555344'),
110 | 519219742499n,
111 | 39619096012n,
112 | 'mockPoolAddress',
113 | 'mockMarketOrderAddress',
114 | 'mockLimitOrderAddress',
115 | );
116 | liquidityPool.extra = {
117 | nft: {
118 | policyId: 'mockNftPolicyId'
119 | }
120 | };
121 |
122 | return [liquidityPool];
123 | };
124 | });
125 |
126 | it('should successfully cancel an order', async () => {
127 | const MockUTxO: UTxO[] = [{
128 | txHash: 'mockTxHash',
129 | address: 'mockMarketOrderAddress',
130 | datumHash: 'mockDatumHash',
131 | outputIndex: 0,
132 | assetBalances: [{asset: 'lovelace', quantity: 1000000000000n}]
133 | }];
134 |
135 | const cancelOrder = await vyFinance.buildCancelSwapOrder(MockUTxO, returnAddress);
136 | expect(cancelOrder).toBeDefined();
137 | expect(cancelOrder[0].address).toBe('addr1');
138 | expect(cancelOrder[0].assetBalances[0].quantity).toBe(1000000000000n);
139 | });
140 |
141 | it('should fail to cancel an order when the liquidity pool is not found', async () => {
142 | const mockUTxO: UTxO[] = [{
143 | txHash: 'mockTxHash',
144 | address: 'mockAddress',
145 | datumHash: 'mockDatumHash',
146 | outputIndex: 0,
147 | assetBalances: [{asset: 'lovelace', quantity: 1000000000000n}]
148 | }];
149 |
150 | try {
151 | await vyFinance.buildCancelSwapOrder(mockUTxO, returnAddress);
152 | fail('Expected buildCancelSwapOrder to throw an error');
153 | } catch (error: unknown) {
154 | if (error instanceof Error) {
155 | expect(error.message).toContain('Unable to find relevant liquidity pool for cancelling the swap order.');
156 | }
157 | }
158 |
159 | });
160 |
161 | it('should fail to cancel an order with invalid UTxO', async () => {
162 | const invalidTxOutputs: UTxO[] = [
163 | {
164 | txHash: 'invalidTxHash',
165 | address: 'invalidAddress',
166 | datumHash: 'invalidDatumHash',
167 | outputIndex: 0,
168 | assetBalances: [{ asset: 'lovelace', quantity: 1000000000000n }]
169 | }
170 | ];
171 |
172 | try {
173 | await vyFinance.buildCancelSwapOrder(invalidTxOutputs, returnAddress);
174 | fail('Expected buildCancelSwapOrder to throw an error');
175 | } catch (error: unknown) {
176 | if (error instanceof Error) {
177 | expect(error.message).toContain('Unable to find relevant UTxO for cancelling the swap order.');
178 | }
179 | }
180 |
181 | });
182 |
183 | });
184 |
185 | });
186 |
--------------------------------------------------------------------------------
/tests/wingriders.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Dexter,
3 | LiquidityPool,
4 | WingRiders,
5 | MockDataProvider,
6 | SwapRequest,
7 | Asset,
8 | MockWalletProvider,
9 | DatumParameters,
10 | DatumParameterKey,
11 | PayToAddress,
12 | AddressType, UTxO,
13 | } from '../src';
14 |
15 | describe('WingRiders', () => {
16 |
17 | const walletProvider: MockWalletProvider = new MockWalletProvider();
18 | walletProvider.loadWalletFromSeedPhrase(['']);
19 | const dexter: Dexter = (new Dexter())
20 | .withDataProvider(new MockDataProvider())
21 | .withWalletProvider(walletProvider);
22 | const asset: Asset = new Asset('f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b69880', '69555344', 6);
23 |
24 | describe('Set Swap In', () => {
25 |
26 | const liquidityPool: LiquidityPool = new LiquidityPool(
27 | WingRiders.identifier,
28 | 'lovelace',
29 | asset,
30 | 923224398616n,
31 | 7942169n,
32 | 'addr1',
33 | );
34 | liquidityPool.poolFeePercent = 0.35;
35 |
36 | const swapRequest: SwapRequest = dexter.newSwapRequest()
37 | .forLiquidityPool(liquidityPool)
38 | .withSwapInToken('lovelace')
39 | .withSwapInAmount(10_000_000000n)
40 | .withSlippagePercent(0.5);
41 |
42 | it('Can calculate swap parameters', () => {
43 | expect(+swapRequest.getPriceImpactPercent().toFixed(2)).toEqual(1.43);
44 | expect(swapRequest.getEstimatedReceive()).toEqual(84809n);
45 | expect(swapRequest.getMinimumReceive()).toEqual(84387n);
46 | });
47 |
48 | it('Can build swap order', () => {
49 | const wingriders: WingRiders = new WingRiders();
50 | const defaultSwapParameters: DatumParameters = {
51 | [DatumParameterKey.SenderPubKeyHash]: walletProvider.publicKeyHash(),
52 | [DatumParameterKey.SenderStakingKeyHash]: walletProvider.stakingKeyHash(),
53 | [DatumParameterKey.ReceiverPubKeyHash]: walletProvider.publicKeyHash(),
54 | [DatumParameterKey.ReceiverStakingKeyHash]: walletProvider.stakingKeyHash(),
55 | [DatumParameterKey.SwapInAmount]: swapRequest.swapInAmount,
56 | [DatumParameterKey.MinReceive]: swapRequest.getMinimumReceive(),
57 | [DatumParameterKey.SwapInTokenPolicyId]: '',
58 | [DatumParameterKey.SwapInTokenAssetName]: '',
59 | [DatumParameterKey.SwapOutTokenPolicyId]: asset.policyId,
60 | [DatumParameterKey.SwapOutTokenAssetName]: asset.nameHex,
61 | };
62 |
63 | return wingriders.buildSwapOrder(liquidityPool, defaultSwapParameters)
64 | .then((payments: PayToAddress[]) => {
65 | expect(() => { wingriders.buildSwapOrder(liquidityPool, defaultSwapParameters); }).not.toThrowError();
66 | expect(payments[0].addressType).toBe(AddressType.Contract);
67 | expect(payments[0].assetBalances[0].quantity).toEqual(10004000000n);
68 | });
69 | });
70 |
71 | it('Can calculate price impact with 0 decimals', () => {
72 | const hosky: Asset = new Asset('a0028f350aaabe0545fdcb56b039bfb08e4bb4d8c4d7c3c7d481c235', '484f534b59', 0);
73 | const hoskyPool: LiquidityPool = new LiquidityPool(
74 | WingRiders.identifier,
75 | 'lovelace',
76 | hosky,
77 | 52428070796n,
78 | 1424861277563n,
79 | 'addr1',
80 | );
81 | hoskyPool.poolFeePercent = 0.35;
82 |
83 | const swap: SwapRequest = dexter.newSwapRequest()
84 | .forLiquidityPool(hoskyPool)
85 | .withSwapInToken('lovelace')
86 | .withSwapInAmount(1_000_000000n)
87 | .withSlippagePercent(0.5);
88 |
89 | expect(+swap.getPriceImpactPercent().toFixed(2)).toEqual(2.23);
90 | });
91 |
92 | });
93 |
94 | describe('Set Swap Out', () => {
95 |
96 | const liquidityPool: LiquidityPool = new LiquidityPool(
97 | WingRiders.identifier,
98 | 'lovelace',
99 | asset,
100 | 925723148616n,
101 | 7920796n,
102 | 'addr1',
103 | );
104 | liquidityPool.poolFeePercent = 0.35;
105 |
106 | const swapRequest: SwapRequest = dexter.newSwapRequest()
107 | .forLiquidityPool(liquidityPool)
108 | .withSwapInToken('lovelace')
109 | .withSwapOutAmount(1_000000n)
110 | .withSlippagePercent(0.5);
111 |
112 | it('Can calculate swap parameters', () => {
113 | expect(swapRequest.swapInAmount).toEqual(134229438286n);
114 | });
115 |
116 | });
117 |
118 | describe('Wingriders Cancel Order', () => {
119 | let wingRiders: WingRiders;
120 | const returnAddress = 'addr1';
121 | beforeEach(() => {
122 | wingRiders = new WingRiders();
123 | });
124 |
125 | it('should successfully cancel an order', async () => {
126 | let marketOrderAddress = wingRiders.orderAddress;
127 | const txOutputs: UTxO[] = [
128 | {
129 | txHash: 'mockTxHash123',
130 | address: marketOrderAddress,
131 | datumHash: 'mockDatumHash123',
132 | outputIndex: 0,
133 | assetBalances: [{ asset: 'lovelace', quantity: 1000000000000n }]
134 | }
135 | ];
136 |
137 | const result = await wingRiders.buildCancelSwapOrder(txOutputs, returnAddress);
138 |
139 | expect(result).toBeDefined();
140 | expect(result[0].address).toBe(returnAddress);
141 | });
142 |
143 | it('should fail to cancel an order with invalid UTxO', async () => {
144 | const invalidTxOutputs: UTxO[] = [
145 | {
146 | txHash: 'invalidTxHash',
147 | address: 'invalidAddress',
148 | datumHash: 'invalidDatumHash',
149 | outputIndex: 0,
150 | assetBalances: [{ asset: 'lovelace', quantity: 1000000000000n }]
151 | }
152 | ];
153 | try {
154 | await wingRiders.buildCancelSwapOrder(invalidTxOutputs, returnAddress);
155 | fail('Expected buildCancelSwapOrder to throw an error');
156 | } catch (error: unknown) {
157 | if (error instanceof Error) {
158 | expect(error.message).toContain('Unable to find relevant UTxO for cancelling the swap order.');
159 | }
160 | }
161 |
162 | });
163 |
164 | });
165 |
166 | });
167 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./build",
4 | "baseUrl": ".",
5 | "module": "es2020",
6 | "target": "es2020",
7 | "lib": ["es2020", "DOM"],
8 | "moduleResolution": "node",
9 | "allowJs": true,
10 | "noImplicitAny": true,
11 | "declaration": true,
12 | "strict": true,
13 | "strictNullChecks": true,
14 | "strictFunctionTypes": true,
15 | "strictBindCallApply": true,
16 | "strictPropertyInitialization": false,
17 | "noImplicitThis": true,
18 | "alwaysStrict": false,
19 | "noUnusedLocals": false,
20 | "noUnusedParameters": false,
21 | "noImplicitReturns": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "esModuleInterop": true,
24 | "skipLibCheck": true,
25 | "forceConsistentCasingInFileNames": true,
26 | "resolveJsonModule": true,
27 | "allowSyntheticDefaultImports": true,
28 | "paths": {
29 | "@app/*": ["src/*"],
30 | "@dex/*": ["src/dex/*"],
31 | "@providers/*": ["src/providers/*"],
32 | "@requests/*": ["src/requests/*"],
33 | }
34 | },
35 | "ts-node": {
36 | "compilerOptions": {
37 | "module": "CommonJS"
38 | }
39 | },
40 | "include": ["src/**/*.ts"],
41 | "exclude": ["node_modules", "tests", "build"],
42 | }
43 |
--------------------------------------------------------------------------------