diff --git a/packages/swap/api/index.ts b/packages/swap/api/index.ts index 5c9ae3aa8d..2202117ad9 100644 --- a/packages/swap/api/index.ts +++ b/packages/swap/api/index.ts @@ -1,16 +1,40 @@ import { Swap } from '@yoroi/types'; -import { SwapOrdersApi } from './orders'; -import { SwapPoolsApi } from './pools'; -import { SwapTokensApi } from './tokens'; +import { cancelOrder, createOrder, getOrders } from './orders'; +import { getPools } from './pools'; +import { getTokens } from './tokens'; -export class SwapApi { - public readonly orders: SwapOrdersApi; - public readonly pools: SwapPoolsApi; - public readonly tokens: SwapTokensApi; +export class SwapApi implements Swap.ISwapApi { + constructor(public readonly network: Swap.Netowrk) {} - constructor(network: Swap.Netowrk) { - this.orders = new SwapOrdersApi(network); - this.pools = new SwapPoolsApi(network); - this.tokens = new SwapTokensApi(network); + public async createOrder( + order: Swap.CreateOrderData + ): ReturnType { + return createOrder(this.network, order); + } + + public async cancelOrder( + orderUTxO: string, + collateralUTxO: string, + walletAddress: string + ): ReturnType { + return cancelOrder(this.network, orderUTxO, collateralUTxO, walletAddress); + } + + public async getOrders(stakeKeyHash: string): ReturnType { + return getOrders(this.network, stakeKeyHash); + } + + public async getPools( + tokenA: Swap.BaseTokenInfo, + tokenB: Swap.BaseTokenInfo + ): ReturnType { + return getPools(this.network, tokenA, tokenB); + } + + public getTokens( + policyId = '', + assetName = '' + ): ReturnType { + return getTokens(this.network, policyId, assetName); } } diff --git a/packages/swap/api/orders.spec.ts b/packages/swap/api/orders.spec.ts index 90e89e1773..f5e33e7a60 100644 --- a/packages/swap/api/orders.spec.ts +++ b/packages/swap/api/orders.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi, Mocked } from 'vitest'; -import { SwapOrdersApi } from './orders'; +import { createOrder, cancelOrder, getOrders } from './orders'; import axios from 'axios'; import { axiosClient } from './config'; @@ -18,16 +18,6 @@ const GENS_TOKEN = { const mockAxios = axiosClient as Mocked; describe('SwapOrdersApi', () => { - const api: SwapOrdersApi = new SwapOrdersApi('mainnet'); - - it('should be initialized for mainnet or testnet', () => { - const mainnet = new SwapOrdersApi('mainnet'); - expect(mainnet.network).to.eq('mainnet'); - - const preprod = new SwapOrdersApi('preprod'); - expect(preprod.network).to.eq('preprod'); - }); - describe('getOrders', () => { it('Should return orders list using staking key hash', async () => { mockAxios.get.mockImplementationOnce(() => @@ -36,7 +26,8 @@ describe('SwapOrdersApi', () => { status: 200, }) ); - const result = await api.getOrders( + const result = await getOrders( + 'preprod', '24fd15671a17a39268b7a31e2a6703f5893f254d4568411322baeeb7' ); expect(result).to.have.lengthOf(1); @@ -52,7 +43,7 @@ describe('SwapOrdersApi', () => { }) ); - const order = await api.createOrder(createOrderParams); + const order = await createOrder('mainnet', createOrderParams); expect(order.contractAddress).to.eq(mockedCreateOrderResult.address); expect(order.datum).to.eq(mockedCreateOrderResult.datum); @@ -67,7 +58,7 @@ describe('SwapOrdersApi', () => { data: { status: 'failed', reason: 'error_message' }, }) ); - await api.createOrder(createOrderParams); + await createOrder('preprod', createOrderParams); }).rejects.toThrowError(/^error_message$/); }); @@ -76,7 +67,7 @@ describe('SwapOrdersApi', () => { mockAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 400 }) ); - await api.createOrder(createOrderParams); + await createOrder('mainnet', createOrderParams); }).rejects.toThrow('Failed to construct swap datum'); }); }); @@ -90,7 +81,8 @@ describe('SwapOrdersApi', () => { }) ); - const txCbor = await api.cancelOrder( + const txCbor = await cancelOrder( + 'mainnet', 'orderUtxo', 'collateralUtxo', 'addr1' @@ -104,7 +96,8 @@ describe('SwapOrdersApi', () => { mockAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 400 }) ); - await api.cancelOrder( + await cancelOrder( + 'mainnet', cancelOrderParams.utxo, cancelOrderParams.collateralUTxOs, cancelOrderParams.address diff --git a/packages/swap/api/orders.ts b/packages/swap/api/orders.ts index 2a3bc7939a..c38aa0ae6f 100644 --- a/packages/swap/api/orders.ts +++ b/packages/swap/api/orders.ts @@ -1,110 +1,94 @@ import { Swap } from '@yoroi/types'; import { SWAP_API_ENDPOINTS, axiosClient } from './config'; -// todo: use axios params +export async function createOrder( + network: Swap.Netowrk, + order: Swap.CreateOrderData +): Promise { + const apiUrl = SWAP_API_ENDPOINTS[network].constructSwapDatum; + const response = await axiosClient.get< + | { status: 'failed'; reason?: string } + | { status: 'success'; hash: string; datum: string; address: string } + >('/', { + baseURL: apiUrl, + params: { + walletAddr: order.address, + protocol: order.protocol, + poolId: order.poolId, + sellTokenPolicyID: order.sell.policyId, + sellTokenNameHex: order.sell.assetName, + sellAmount: order.sell.amount, + buyTokenPolicyID: order.buy.policyId, + buyTokenNameHex: order.buy.assetName, + buyAmount: order.buy.amount, + }, + }); -export class SwapOrdersApi { - private readonly constructSwapDatumApiUrl: string; - private readonly cancelSwapTransactionApiUrl: string; - private readonly getOrdersApiUrl: string; - - constructor(public readonly network: Swap.Netowrk) { - const { constructSwapDatum, cancelSwapTransaction, getOrders } = - SWAP_API_ENDPOINTS[network]; - this.constructSwapDatumApiUrl = constructSwapDatum; - this.cancelSwapTransactionApiUrl = cancelSwapTransaction; - this.getOrdersApiUrl = getOrders; - } - - /** - * @param order the order to construct the datum for and the address to send the order to. - * @returns the order datum, order datum hash, and address to send the order to. - */ - public async createOrder( - order: Swap.Order - ): Promise> { - const response = await axiosClient.get< - | { status: 'failed'; reason?: string } - | { status: 'success'; hash: string; datum: string; address: string } - >('/', { - baseURL: this.constructSwapDatumApiUrl, - params: { - walletAddr: order.address, - protocol: order.protocol, - poolId: order.poolId, - sellTokenPolicyID: order.sell.policyId, - sellTokenNameHex: order.sell.assetName, - sellAmount: order.sell.amount, - buyTokenPolicyID: order.buy.policyId, - buyTokenNameHex: order.buy.assetName, - buyAmount: order.buy.amount, - }, + if (response.status !== 200) { + throw new Error('Failed to construct swap datum', { + cause: response.data, }); + } - if (response.status !== 200) { - throw new Error('Failed to construct swap datum', { - cause: response.data, - }); - } - - if (response.data.status === 'failed') { - throw new Error(response.data.reason || 'Unexpected error occurred'); - } - - return { - datumHash: response.data.hash, - datum: response.data.datum, - contractAddress: response.data.address, - }; + if (response.data.status === 'failed') { + throw new Error(response.data.reason || 'Unexpected error occurred'); } - /** - * @param orderUTxO order UTxO from the smart contract to cancel. e.g. "txhash#0" - * @param collateralUTxOs collateral UTxOs to use for canceling the order in cbor format. - * @param walletAddress address of the wallet that owns the order in cbor format. - * @returns an unsigned transaction to cancel the order. - */ - public async cancelOrder( - orderUTxO: string, - collateralUTxO: string, - walletAddress: string - ): Promise { - const response = await axiosClient.get('/', { - baseURL: this.cancelSwapTransactionApiUrl, - params: { - wallet: walletAddress, - utxo: orderUTxO, - collateralUTxO, - }, - }); + return { + datumHash: response.data.hash, + datum: response.data.datum, + contractAddress: response.data.address, + }; +} - if (response.status !== 200) { - throw new Error('Failed to cancel swap transaction', { - cause: response.data, - }); - } +/** + * @param orderUTxO order UTxO from the smart contract to cancel. e.g. "txhash#0" + * @param collateralUTxOs collateral UTxOs to use for canceling the order in cbor format. + * @param walletAddress address of the wallet that owns the order in cbor format. + * @returns an unsigned transaction to cancel the order. + */ +export async function cancelOrder( + network: Swap.Netowrk, + orderUTxO: string, + collateralUTxO: string, + walletAddress: string +): Promise { + const apiUrl = SWAP_API_ENDPOINTS[network].cancelSwapTransaction; + const response = await axiosClient.get('/', { + baseURL: apiUrl, + params: { + wallet: walletAddress, + utxo: orderUTxO, + collateralUTxO, + }, + }); - return response.data.cbor; + if (response.status !== 200) { + throw new Error('Failed to cancel swap transaction', { + cause: response.data, + }); } - /** - * @param stakeKeyHash the stake key hash of the wallet to get orders for. - * @returns all unfufilled orders for the given stake key hash. - */ - public async getOrders(stakeKeyHash: string): Promise { - const response = await axiosClient.get('/', { - baseURL: this.getOrdersApiUrl, - params: { - 'stake-key-hash': stakeKeyHash, - }, - }); + return response.data.cbor; +} - if (response.status !== 200) { - throw new Error(`Failed to get orders for ${stakeKeyHash}`, { - cause: response.data, - }); - } +export async function getOrders( + network: Swap.Netowrk, + stakeKeyHash: string +): Promise { + const apiUrl = SWAP_API_ENDPOINTS[network].getPools; + const response = await axiosClient.get('/', { + baseURL: apiUrl, + params: { + 'stake-key-hash': stakeKeyHash, + }, + }); - return response.data; + if (response.status !== 200) { + throw new Error(`Failed to get orders for ${stakeKeyHash}`, { + cause: response.data, + }); } + + return response.data; } diff --git a/packages/swap/api/pools.spec.ts b/packages/swap/api/pools.spec.ts index a81c419f6e..33b31897b4 100644 --- a/packages/swap/api/pools.spec.ts +++ b/packages/swap/api/pools.spec.ts @@ -1,12 +1,11 @@ import { describe, expect, it, vi, Mocked } from 'vitest'; -import { SwapPoolsApi } from './pools'; +import { getPools } from './pools'; import { axiosClient } from './config'; vi.mock('./config.ts'); const mockAxios = axiosClient as Mocked; describe('SwapPoolsApi', () => { - const api = new SwapPoolsApi('preprod'); it('should get pools list for a given token pair', async () => { mockAxios.get.mockImplementationOnce(() => Promise.resolve({ @@ -15,7 +14,11 @@ describe('SwapPoolsApi', () => { }) ); - const result = await api.getPools(getPoolsParams.sell, getPoolsParams.buy); + const result = await getPools( + 'mainnet', + getPoolsParams.sell, + getPoolsParams.buy + ); expect(result).to.be.of.lengthOf(1); }); @@ -24,7 +27,7 @@ describe('SwapPoolsApi', () => { mockAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 500 }) ); - await api.getPools(getPoolsParams.sell, getPoolsParams.buy); + await getPools('preprod', getPoolsParams.sell, getPoolsParams.buy); }).rejects.toThrow('Failed to fetch pools for token pair'); }); }); diff --git a/packages/swap/api/pools.ts b/packages/swap/api/pools.ts index 38ff605cf1..e019fd1e23 100644 --- a/packages/swap/api/pools.ts +++ b/packages/swap/api/pools.ts @@ -1,52 +1,34 @@ import { Swap } from '@yoroi/types'; import { SWAP_API_ENDPOINTS, axiosClient } from './config'; -import fs from 'node:fs'; -type BaseTokenInfo = - | { policyId: string; assetName: string } - | { policyId: string; assetNameHex: string }; - -export class SwapPoolsApi { - private readonly apiUrl: string; - - constructor(public readonly network: Swap.Netowrk) { - this.apiUrl = SWAP_API_ENDPOINTS[network].getPools; - } - - /** - * @param tokenA the token to swap from. - * @param tokenB the token to swap to. - * @returns a list of pools for the given token pair. - */ - public async getPools( - tokenA: BaseTokenInfo, - tokenB: BaseTokenInfo - ): Promise { - const params: { [key: string]: string } = { - 'policy-id1': tokenA.policyId, - 'policy-id2': tokenB.policyId, - }; - - if ('assetName' in tokenA) params['tokenname1'] = tokenA.assetName; - if ('assetName' in tokenB) params['tokenname2'] = tokenB.assetName; - - // note: {tokenname-hex} will overwrites {tokenname} - if ('assetNameHex' in tokenA) - params['tokenname-hex1'] = tokenA.assetNameHex; - if ('assetNameHex' in tokenB) - params['tokenname-hex2'] = tokenB.assetNameHex; - - const response = await axiosClient.get('', { - baseURL: this.apiUrl, - params, +export async function getPools( + network: Swap.Netowrk, + tokenA: Swap.BaseTokenInfo, + tokenB: Swap.BaseTokenInfo +): Promise { + const params: { [key: string]: string } = { + 'policy-id1': tokenA.policyId, + 'policy-id2': tokenB.policyId, + }; + + if ('assetName' in tokenA) params['tokenname1'] = tokenA.assetName; + if ('assetName' in tokenB) params['tokenname2'] = tokenB.assetName; + + // note: {tokenname-hex} will overwrites {tokenname} + if ('assetNameHex' in tokenA) params['tokenname-hex1'] = tokenA.assetNameHex; + if ('assetNameHex' in tokenB) params['tokenname-hex2'] = tokenB.assetNameHex; + + const apiUrl = SWAP_API_ENDPOINTS[network].getPools; + const response = await axiosClient.get('', { + baseURL: apiUrl, + params, + }); + + if (response.status !== 200) { + throw new Error('Failed to fetch pools for token pair', { + cause: response.data, }); - - if (response.status !== 200) { - throw new Error('Failed to fetch pools for token pair', { - cause: response.data, - }); - } - - return response.data; } + + return response.data; } diff --git a/packages/swap/api/tokens.spec.ts b/packages/swap/api/tokens.spec.ts index 88856f29db..353e889114 100644 --- a/packages/swap/api/tokens.spec.ts +++ b/packages/swap/api/tokens.spec.ts @@ -1,27 +1,29 @@ import { expect, describe, it, vi, Mocked } from 'vitest'; -import { SwapTokensApi } from './tokens'; +import { getTokens } from './tokens'; import { axiosClient } from './config'; vi.mock('./config.ts'); const mockAxios = axiosClient as Mocked; describe('SwapTokensApi', () => { - const api = new SwapTokensApi('mainnet'); - it('should get all supported tokens list', async () => { mockAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 200, data: mockedGetTokensRes }) ); - const result = await api.getTokens(); + const result = await getTokens('mainnet'); expect(result).to.be.lengthOf(1); }); + it('should return empty list on preprod network', async () => { + expect(await getTokens('preprod')).to.be.empty; + }); + it('should throw error for invalid response', async () => { await expect(async () => { mockAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 500 }) ); - await api.getTokens(); + await getTokens('mainnet'); }).rejects.toThrow('Failed to fetch tokens'); }); }); diff --git a/packages/swap/api/tokens.ts b/packages/swap/api/tokens.ts index aaee9a6384..cc363c43ce 100644 --- a/packages/swap/api/tokens.ts +++ b/packages/swap/api/tokens.ts @@ -1,31 +1,25 @@ import { Swap } from '@yoroi/types'; import { SWAP_API_ENDPOINTS, axiosClient } from './config'; -export class SwapTokensApi { - private readonly apiUrl: string; +export async function getTokens( + network: Swap.Netowrk, + policyId = '', + assetName = '' +): Promise { + if (network === 'preprod') return []; - constructor(public readonly netwok: Swap.Netowrk) { - this.apiUrl = SWAP_API_ENDPOINTS[netwok].getTokens; - } - - public async getTokens( - policyId = '', - assetName = '' - ): Promise { - if (this.netwok === 'preprod') return []; - - const response = await axiosClient.get('', { - baseURL: this.apiUrl, - params: { - 'base-policy-id': policyId, - 'base-tokenname': assetName, - }, - }); + const apiUrl = SWAP_API_ENDPOINTS[network].getTokens; + const response = await axiosClient.get('', { + baseURL: apiUrl, + params: { + 'base-policy-id': policyId, + 'base-tokenname': assetName, + }, + }); - if (response.status !== 200) { - throw new Error('Failed to fetch tokens', { cause: response.data }); - } - - return response.data; + if (response.status !== 200) { + throw new Error('Failed to fetch tokens', { cause: response.data }); } + + return response.data; } diff --git a/packages/swap/dex.spec.ts b/packages/swap/dex.spec.ts deleted file mode 100644 index b77a27b31a..0000000000 --- a/packages/swap/dex.spec.ts +++ /dev/null @@ -1,606 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { - calculateAmountsGivenInput, - calculateAmountsGivenOutput, - constructLimitOrder, - constructMarketOrder, - getOpenOrders, - getOrderDatum, - getSupportedTokens, - getTokenPairPools, -} from './dex'; - -describe.skip('Yoroi DEX aggregator', () => { - it('should be able to get the tokens supported by the DEX aggregator', async () => { - const tokens = await getSupportedTokens(); - - expect(tokens).containSubset([mockTokens[0]]); - }); - - it('should be able to get the pools for the selected token pair', async () => { - const pools = await getTokenPairPools(ADA_TOKEN, GENS_TOKEN); - - expect(pools.length).toBeGreaterThan(0); - expect(pools).containSubset([SUNDAE_POOL]); - }); - - it('should be able to calculate the amounts given input for a trade', () => { - const tokenPair = calculateAmountsGivenInput(SUNDAE_POOL, { - address: ADA_TOKEN, - amount: '20000000', - }); - - expect(Number(tokenPair.receive.amount)).approximately(21_000_000, 500_000); - }); - - it('should be able to calculate the amounts given output for a trade', () => { - const tokenPair = calculateAmountsGivenOutput(SUNDAE_POOL, { - address: GENS_TOKEN, - amount: '21000000', - }); - - expect(Number(tokenPair.send.amount)).approximately(20_000_000, 500_000); - }); - - it('should get the same result for a trade regardless of the direction', () => { - const tokenPairInput = calculateAmountsGivenInput(SUNDAE_POOL, { - address: ADA_TOKEN, - amount: '', - }); - - const tokenPairOutput = calculateAmountsGivenOutput(SUNDAE_POOL, { - address: GENS_TOKEN, - amount: '21366237', - }); - - expect(Number(tokenPairInput.send.amount)).approximately( - Number(tokenPairOutput.send.amount), - 10 - ); - expect(Number(tokenPairInput.receive.amount)).approximately( - Number(tokenPairOutput.receive.amount), - 10 - ); - }); - - it('should be able to construct a LIMIT order for a trade given a wallet address and slippage tolerance', () => { - const slippage = 15; // 5% - const address = 'some wallet address here'; - - const order = constructLimitOrder( - { - address: ADA_TOKEN, - amount: '', - }, - { - address: GENS_TOKEN, - amount: '50000000', - }, - SUNDAE_POOL, - slippage, - address - ); - - expect(order?.protocol).toEqual('sundaeswap'); - expect(order?.walletAddress).toEqual(address); - expect(order?.send.amount).toEqual('25000000'); - expect(Number(order?.receive.amount)).approximately( - Number('42500000'), - 100_000 - ); - }); - - it('should be able to construct a MARKET order for a trade given a wallet address and slippage tolerance', async () => { - const pools = await getTokenPairPools(ADA_TOKEN, GENS_TOKEN); - const address = 'some wallet address here'; - const slippage = 5; // 5% - - const order = constructMarketOrder( - { - address: ADA_TOKEN, - amount: '25000000', - }, - GENS_TOKEN, - pools, - slippage, - address - ); - - expect(order?.protocol).toEqual('wingriders'); - expect(order?.walletAddress).toEqual(address); - expect(order?.send.amount).toEqual('25000000'); - expect(Number(order?.receive.amount)).approximately( - Number('26700000'), - 100_000 - ); - }); - - it('should be able to get open orders for a wallet stake key hash', async () => { - const orders = await getOpenOrders( - '24fd15671a17a39268b7a31e2a6703f5893f254d4568411322baeeb7' - ); - console.log(JSON.stringify(orders, null, 2)); - - expect(orders.length).toBeGreaterThan(0); - expect(orders).containSubset([ - { - protocol: 'sundaeswap', - depposit: '2000000', - utxo: '6c4b4e55301d79128071f05a018cf05b7de86bc3f92d05b6668423e220152a86#0', - send: { - address: { - policyId: '', - assetName: '', - }, - amount: '1000000', - }, - receive: { - address: { - policyId: - '8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa', - assetName: '4d494c4b', - }, - amount: '20', - }, - }, - ]); - }); - - it('should be able to get the datum for a constructed order', async () => { - const pools = await getTokenPairPools(ADA_TOKEN, GENS_TOKEN); - const address = 'some wallet address here'; - const slippage = 5; // 5% - - const order = constructMarketOrder( - { - address: ADA_TOKEN, - amount: '25000000', - }, - GENS_TOKEN, - pools, - slippage, - address - ); - - const orderDatum = await getOrderDatum(order!); - - expect(orderDatum.contractAddress).toEqual(mockOrder.address); - expect(orderDatum.datumHash).toEqual(mockOrder.hash); - expect(orderDatum.datum).toEqual(mockOrder.datum); - }); -}); - -vi.mock('./openswap', async () => { - const actual = await vi.importActual('./openswap'); - - return { - ...(actual as {}), - createOrder: async () => mockOrder, - getOrders: async () => [mockOpenOrder], - getPools: async () => mockPools, - getTokens: async () => mockTokens, - }; -}); - -const mockOpenOrder = { - from: { - amount: '1000000', - token: '.', - }, - to: { - amount: '20', - token: '8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa.4d494c4b', - }, - sender: - 'addr1qy0556dz9jssrrnhv0g3ga98uczdd465cut9jjs5a4k5qy3yl52kwxsh5wfx3darrc4xwql43ylj2n29dpq3xg46a6mska8vfz', - owner: - 'addr1qy0556dz9jssrrnhv0g3ga98uczdd465cut9jjs5a4k5qy3yl52kwxsh5wfx3darrc4xwql43ylj2n29dpq3xg46a6mska8vfz', - ownerPubKeyHash: '1f4a69a22ca1018e7763d11474a7e604d6d754c716594a14ed6d4012', - batcherFee: { - amount: '2500000', - token: '.', - }, - deposit: '2000000', - valueAttached: [ - { - amount: '5500000', - token: '.', - }, - ], - utxo: '6c4b4e55301d79128071f05a018cf05b7de86bc3f92d05b6668423e220152a86#0', - provider: 'sundaeswap', - batcherAddress: 'addr1wxaptpmxcxawvr3pzlhgnpmzz3ql43n2tc8mn3av5kx0yzs09tqh8', - poolId: '14', - swapDirection: 0, -}; - -const mockOrder = { - status: 'success', - datum: - 'd8799fd8799fd8799fd8799f581c353b8bc29a15603f0b73eac44653d1bd944d92e0e0dcd5eb185164a2ffd8799fd8799fd8799f581cda22c532206a75a628778eebaf63826f9d93fbe9b4ac69a7f8e4cd78ffffffff581c353b8bc29a15603f0b73eac44653d1bd944d92e0e0dcd5eb185164a21b00000188f2408726d8799fd8799f4040ffd8799f581cdda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb480014df1047454e53ffffffd8799fd879801a0006517affff', - hash: '4ae3fc5498e9d0f04daaf2ee739e41dc3f6f4119391e7274f0b3fa15aa2163ff', - address: 'addr1wxr2a8htmzuhj39y2gq7ftkpxv98y2g67tg8zezthgq4jkg0a4ul4', -}; - -const mockPools = [ - { - provider: 'muesliswap_v2', - fee: '0.3', - tokenA: { amount: '2778918813', token: '.' }, - tokenB: { - amount: '3046518484', - token: - 'dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53', - }, - price: 0.9121621377301974, - batcherFee: { amount: '950000', token: '.' }, - depositFee: { amount: '2000000', token: '.' }, - deposit: 2000000, - utxo: 'b9d6cef3002de24896e5949619bf5e76ddcbd44147d1a127ee04e2b7486747df#7', - poolId: - '909133088303c49f3a30f1cc8ed553a73857a29779f6c6561cd8093f.34e551a0dabee7dfddcb5dcea93d22040a8b9e36348057da2987a6f1bc731935', - timestamp: '2023-05-26 15:12:11', - lpToken: { - amount: '2864077440', - token: - 'af3d70acf4bd5b3abb319a7d75c89fb3e56eafcdd46b2e9b57a2557f.34e551a0dabee7dfddcb5dcea93d22040a8b9e36348057da2987a6f1bc731935', - }, - }, - { - provider: 'minswap', - fee: '0.3', - tokenA: { amount: '159431695049', token: '.' }, - tokenB: { - amount: '179285403975', - token: - 'dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53', - }, - price: 0.8892619896220417, - batcherFee: { amount: '2000000', token: '.' }, - depositFee: { amount: '2000000', token: '.' }, - deposit: 2000000, - utxo: 'a9c6386fcca2cc9ad86e86940566b3f1b22ac3e47f59fa040d42c62811184e82#3', - poolId: - '0be55d262b29f564998ff81efe21bdc0022621c12f15af08d0f2ddb1.4705d99a4cf6bce9181e60fdbbf961edf6acad7141ed69186c8a8883600e59c5', - timestamp: '2023-05-26 15:12:11', - lpToken: { - amount: '169067370752', - token: - 'e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86.4705d99a4cf6bce9181e60fdbbf961edf6acad7141ed69186c8a8883600e59c5', - }, - }, - { - provider: 'sundaeswap', - fee: '0.05', - tokenA: { amount: '1762028491', token: '.' }, - tokenB: { - amount: '1904703890', - token: - 'dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53', - }, - price: 0.925093134030403, - batcherFee: { amount: '2500000', token: '.' }, - depositFee: { amount: '2000000', token: '.' }, - deposit: 2000000, - utxo: 'b28ccb32adf80618a4e78d97012754f8c402b29907f1d2e6ed5df973003844f4#0', - poolId: '0029cb7c88c7567b63d1a512c0ed626aa169688ec980730c0473b913.7020e403', - timestamp: '2023-05-26 15:12:11', - lpToken: { - amount: '1813689472', - token: - '0029cb7c88c7567b63d1a512c0ed626aa169688ec980730c0473b913.6c7020e403', - }, - }, - { - provider: 'wingriders', - fee: '0.35', - tokenA: { amount: '25476044027', token: '.' }, - tokenB: { - amount: '28844597339', - token: - 'dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53', - }, - price: 0.8832171837099813, - batcherFee: { amount: '2000000', token: '.' }, - depositFee: { amount: '2000000', token: '.' }, - deposit: 2000000, - utxo: '2120d4a9ece0add45461d5d3262f3708bd6cbdd8a473c7ceb32eb5e83f4c633e#0', - poolId: - '026a18d04a0c642759bb3d83b12e3344894e5c1c7b2aeb1a2113a570.84eaa43491009c3a9f4e257370352e84ea7246ee8a27d9d3d9e458141d9bcf97', - timestamp: '2023-05-26 15:10:51', - lpToken: null, - }, -]; - -const mockTokens = [ - { - info: { - supply: { total: '10000', circulating: null }, - status: 'verified', - address: { - policyId: '53fb41609e208f1cd3cae467c0b9abfc69f1a552bf9a90d51665a4d6', - name: 'f9c09e1913e29072938cad1fda7b70939090c0be1f7fa48150a2e63a2b9ad715', - }, - symbol: 'OBOND [VICIS]', - website: 'https://app.optim.finance/dashboard', - description: - 'A lender bond issued by Optim Finance corresponding to 100 ADA_TOKEN lent. Lending APY 5.33%. Max Duration 72 Epochs. Interest Buffer 6 epochs. Verify on token homepage to assess more details.', - decimalPlaces: 0, - categories: ['2'], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 1000.0, - askPrice: 0, - bidPrice: 1000.0, - priceChange: { '24h': 0, '7d': 0 }, - fromToken: '.', - toToken: - '53fb41609e208f1cd3cae467c0b9abfc69f1a552bf9a90d51665a4d6.f9c09e1913e29072938cad1fda7b70939090c0be1f7fa48150a2e63a2b9ad715', - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: '53fb41609e208f1cd3cae467c0b9abfc69f1a552bf9a90d51665a4d6', - name: 'f9c09e1913e29072938cad1fda7b70939090c0be1f7fa48150a2e63a2b9ad715', - }, - baseAddress: { policyId: '', name: '' }, - price10d: [], - }, - }, - { - info: { - supply: { total: '100000000000', circulating: null }, - status: 'unverified', - website: 'https://cardanofight.club', - symbol: 'CFC', - decimalPlaces: 0, - image: - 'https://tokens.muesliswap.com/static/img/tokens/71ccb467ef856b242753ca53ade36cd1f8d9abb33fdfa7d1ff89cda3.434643.png', - description: - 'The official token for Cardano Fight Club. The ultimate utility token for all CFC Products and Development.', - address: { - policyId: '71ccb467ef856b242753ca53ade36cd1f8d9abb33fdfa7d1ff89cda3', - name: '434643', - }, - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 14.5211676983, - askPrice: 0, - bidPrice: 1.112, - priceChange: { '24h': 0, '7d': 0 }, - fromToken: '.', - toToken: - '71ccb467ef856b242753ca53ade36cd1f8d9abb33fdfa7d1ff89cda3.434643', - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: '71ccb467ef856b242753ca53ade36cd1f8d9abb33fdfa7d1ff89cda3', - name: '434643', - }, - baseAddress: { policyId: '', name: '' }, - price10d: [], - }, - }, - { - info: { - supply: { total: '10000000', circulating: null }, - status: 'unverified', - website: 'https://www.cardanofly.io/', - symbol: 'cdfly', - decimalPlaces: 0, - image: 'ipfs://QmQaKcTjrBSVbmK5xZgaWjEa5yKzUSLRAbVyhyzbceXSMH', - description: '', - address: { - policyId: '5d5aadeebb07a5e48827ef6efe577c7b4db4a69b2db1b29279e0b514', - name: '6364666c79', - }, - imageIpfsHash: 'QmQaKcTjrBSVbmK5xZgaWjEa5yKzUSLRAbVyhyzbceXSMH', - minting: { - type: 'time-lock-policy', - blockchain: 'cardano', - mintedBeforeSlotNumber: 74916255, - }, - mediatype: 'image/png', - tokentype: 'token', - totalsupply: 10000000, - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { '24h': 0, '7d': 0 }, - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: '5d5aadeebb07a5e48827ef6efe577c7b4db4a69b2db1b29279e0b514', - name: '6364666c79', - }, - baseAddress: { policyId: '', name: '' }, - price10d: [], - }, - }, - { - info: { - supply: { total: '100000000000000', circulating: null }, - status: 'unverified', - website: 'https://linktr.ee/nutriemp.CRYPTO', - symbol: 'BUDZ', - decimalPlaces: 2, - image: 'ipfs://QmSESYYcMk9i3EDbQSWBmkfEWK4X7TokohJyvQThxpANgq', - description: 'NUTRIEMP.CRYPTO - GROWERS UNITE', - address: { - policyId: 'd2cb1f7a8ae3bb94117e30d241566c2dd5adbd0708c40a8a5ac9ae60', - name: '4255445a', - }, - imageIpfsHash: 'QmSESYYcMk9i3EDbQSWBmkfEWK4X7TokohJyvQThxpANgq', - minting: { - type: 'time-lock-policy', - blockchain: 'cardano', - mintedBeforeSlotNumber: 78641478, - }, - mediatype: 'image/png', - tokentype: 'token', - totalsupply: 100000000000000, - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { '24h': 0, '7d': 0 }, - quoteDecimalPlaces: 2, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: 'd2cb1f7a8ae3bb94117e30d241566c2dd5adbd0708c40a8a5ac9ae60', - name: '4255445a', - }, - baseAddress: { policyId: '', name: '' }, - price10d: [], - }, - }, - { - info: { - supply: { total: '10000000', circulating: null }, - status: 'unverified', - website: 'https://ratsonchain.com', - description: 'The official token of Rats On Chain', - image: 'ipfs://QmeJPgcLWDDTBKxqTzWdDj1ruE5huBf3cbJNPVetnqW7DH', - symbol: 'ROC', - decimalPlaces: 0, - address: { - policyId: '91ec8c9ae38d203c0fa1e0a31274f05bee7be4eb0133ed0beb837297', - name: '524f43', - }, - imageIpfsHash: 'QmeJPgcLWDDTBKxqTzWdDj1ruE5huBf3cbJNPVetnqW7DH', - ticker: 'ROC', - project: 'Rats On Chain', - mediatype: 'image/png', - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { '24h': 0, '7d': 0 }, - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: '91ec8c9ae38d203c0fa1e0a31274f05bee7be4eb0133ed0beb837297', - name: '524f43', - }, - baseAddress: { policyId: '', name: '' }, - price10d: [], - }, - }, - { - info: { - supply: { total: '39997832', circulating: null }, - status: 'unverified', - symbol: 'BURPZ', - decimalPlaces: 0, - image: 'ipfs://QmZcnb7DSFXdd9crvTMyTXnbQvUU5srktxK8aZxUNFvZDJ', - description: 'Official Fluffy Utility Token', - address: { - policyId: 'ef71f8d84c69bb45f60e00b1f595545238b339f00b137dd5643794bb', - name: '425552505a', - }, - imageIpfsHash: 'QmZcnb7DSFXdd9crvTMyTXnbQvUU5srktxK8aZxUNFvZDJ', - ticker: 'BURPZ', - twitter: '@FluffysNFT', - mediatype: 'image/png', - categories: [], - }, - price: { - volume: { base: '0', quote: '0' }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { '24h': '0', '7d': '0' }, - fromToken: 'lovelace', - toToken: - 'ef71f8d84c69bb45f60e00b1f595545238b339f00b137dd5643794bb.425552505a', - price10d: [], - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: 'ef71f8d84c69bb45f60e00b1f595545238b339f00b137dd5643794bb', - name: '425552505a', - }, - baseAddress: { policyId: '', name: '' }, - }, - }, - { - info: { - supply: { total: '76222812878', circulating: null }, - status: 'unverified', - address: { - policyId: 'e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86', - name: 'f86a805f257a14b127b9b54444e556ab1a066f690501d4474bbad4454324845b', - }, - decimalPlaces: 0, - symbol: - 'f86a805f257a14b127b9b54444e556ab1a066f690501d4474bbad4454324845b', - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { '24h': 0, '7d': 0 }, - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: 'e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86', - name: 'f86a805f257a14b127b9b54444e556ab1a066f690501d4474bbad4454324845b', - }, - baseAddress: { policyId: '', name: '' }, - price10d: [], - }, - }, -]; - -const ADA_TOKEN = { - policyId: '', - assetName: '', -}; - -const GENS_TOKEN = { - policyId: 'dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb', - assetName: '0014df1047454e53', -}; - -const SUNDAE_POOL = { - protocol: 'sundaeswap' as const, - batcherFee: '2500000', - lvlDeposit: '2000000', - tokenA: { - address: { - policyId: '', - assetName: '', - }, - amount: '1762028491', - }, - tokenB: { - address: { - policyId: 'dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb', - assetName: '0014df1047454e53', - }, - amount: '1904703890', - }, - poolFee: '0.05', - poolId: '0029cb7c88c7567b63d1a512c0ed626aa169688ec980730c0473b913.7020e403', -}; diff --git a/packages/swap/dex.ts b/packages/swap/dex.ts deleted file mode 100644 index 39590fc25f..0000000000 --- a/packages/swap/dex.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { - cancelOrder, - createOrder, - getOrders, - getPools, - getTokens, -} from './openswap'; -import type { Protocol, Token } from './openswap'; - -export type OrderDetails = { - protocol: Protocol; - poolId?: string; // only required for SundaeSwap trades. - send: TokenAmount; - receive: TokenAmount; - walletAddress: string; -}; - -export type OrderDatum = { - contractAddress: string; - datumHash: string; - datum: string; -}; - -export type TokenAddress = { - policyId: string; - assetName: string; -}; - -export type TokenAmount = { - address: TokenAddress; - amount: string; -}; - -export type TokenPair = { - send: TokenAmount; // token to swap from aka selling - receive: TokenAmount; // token to swap to aka buying -}; - -export type TokenPairPool = { - protocol: Protocol; - lpToken?: TokenAmount; - tokenA: TokenAmount; - tokenB: TokenAmount; - batcherFee: string; - lvlDeposit: string; - poolFee: string; - poolId: string; -}; - -export const getOpenOrders = async (stakeKeyHash: string) => - (await getOrders(stakeKeyHash)).map((order) => ({ - protocol: order.provider, - depposit: order.deposit, - utxo: order.utxo, - send: { - address: { - policyId: order.from.token.split('.')[0], - assetName: order.from.token.split('.')[1], - }, - amount: order.from.amount, - }, - receive: { - address: { - policyId: order.to.token.split('.')[0], - assetName: order.to.token.split('.')[1], - }, - amount: order.to.amount, - }, - })); - -export const getCancelOrderTx = async ( - orderUTxO: string, - collateralUTxOs: string, - walletAddress: string -) => await cancelOrder(orderUTxO, collateralUTxOs, walletAddress); - -export const getOrderDatum = async ( - order: OrderDetails -): Promise => { - const { address, hash, datum } = await createOrder({ - address: order.walletAddress, - protocol: order.protocol, - poolId: order.poolId, - sell: { - policyId: order.send.address.policyId, - assetName: order.send.address.assetName, - amount: order.send.amount, - }, - buy: { - policyId: order.receive.address.policyId, - assetName: order.receive.address.assetName, - amount: order.receive.amount, - }, - }); - - return { - contractAddress: address, - datumHash: hash, - datum, - }; -}; - -export const getSupportedTokens = async ( - baseToken?: TokenAddress -): Promise => getTokens(baseToken?.policyId, baseToken?.assetName); - -export const getTokenPairPools = async ( - sendToken: TokenAddress, - receiveToken: TokenAddress -): Promise => - (await getPools(sendToken, receiveToken)).map((pool) => ({ - protocol: pool.provider as Protocol, - lpToken: pool.lpToken && { - address: { - policyId: pool.lpToken.token.split('.')[0], - assetName: pool.lpToken.token.split('.')[1], - }, - amount: pool.lpToken.amount, - }, - batcherFee: pool.batcherFee.amount, - lvlDeposit: pool.deposit.toString(), - tokenA: { - address: { - policyId: pool.tokenA.token.split('.')[0], - assetName: pool.tokenA.token.split('.')[1], - }, - amount: pool.tokenA.amount, - }, - tokenB: { - address: { - policyId: pool.tokenB.token.split('.')[0], - assetName: pool.tokenB.token.split('.')[1], - }, - amount: pool.tokenB.amount, - }, - poolFee: pool.fee, - poolId: pool.poolId, - })); - -export const calculateAmountsGivenInput = ( - pool: TokenPairPool, - from: TokenAmount -): TokenPair => { - const poolA = BigInt(pool.tokenA.amount); - const poolB = BigInt(pool.tokenB.amount); - const poolsProduct = poolA * poolB; // fee is part of tokens sent -> this means the constant product increases after the swap! - - const fromAmount = BigInt(from.amount); - - const poolFee = ceilDivision( - BigInt(Number(pool.poolFee) * 1000) * fromAmount, - BigInt(100 * 1000) - ); - - const getReceiveAmount = (poolA_: bigint, poolB_: bigint) => { - const newPoolA = poolA_ + fromAmount - poolFee; - const newPoolB = ceilDivision(poolsProduct, newPoolA); - - return (poolB_ - newPoolB).toString(); - }; - - const receive = sameToken(from.address, pool.tokenA.address) - ? { amount: getReceiveAmount(poolA, poolB), address: pool.tokenB.address } - : { amount: getReceiveAmount(poolB, poolA), address: pool.tokenA.address }; - - return { send: from, receive }; -}; - -export const calculateAmountsGivenOutput = ( - pool: TokenPairPool, - to: TokenAmount -): TokenPair => { - const poolA = BigInt(pool.tokenA.amount); - const poolB = BigInt(pool.tokenB.amount); - const poolsProduct = poolA * poolB; // fee is part of tokens sent -> this means the constant product increases after the swap! - - const toAmount = BigInt(to.amount); - - const poolFee = BigInt(100 * 1000) - BigInt(Number(pool.poolFee) * 1000); - - const getSendAmount = (poolA_: bigint, poolB_: bigint) => { - const newPoolA = - poolA_ - (poolA_ > toAmount ? toAmount : poolA_ - BigInt(1)); - const newPoolB = ceilDivision(poolsProduct + newPoolA, newPoolA); - return ceilDivision( - (newPoolB - poolB_) * BigInt(100 * 1000), - poolFee - ).toString(); - }; - - const send = sameToken(to.address, pool.tokenA.address) - ? { amount: getSendAmount(poolA, poolB), address: pool.tokenB.address } - : { amount: getSendAmount(poolB, poolA), address: pool.tokenA.address }; - - return { send, receive: to }; -}; - -export const constructLimitOrder = ( - send: TokenAmount, - receive: TokenAmount, - pool: TokenPairPool, - slippage: number, - address: string -): OrderDetails => { - const receiveAmountWithSlippage = calculateAmountWithSlippage( - BigInt(receive.amount), - BigInt(slippage) - ); - - return { - protocol: pool.protocol, - poolId: pool.poolId, - send: send, - receive: { - address: receive.address, - amount: receiveAmountWithSlippage.toString(), - }, - walletAddress: address, - }; -}; - -export const constructMarketOrder = ( - send: TokenAmount, - receive: TokenAddress, - pools: TokenPairPool[], - slippage: number, - address: string -): OrderDetails | undefined => { - if (pools?.length === 0) { - return undefined; - } - - const findBestOrder = ( - order: OrderDetails | undefined, - pool: TokenPairPool - ): OrderDetails => { - const tokenPair = calculateAmountsGivenInput(pool, send); - - const receiveAmount = BigInt(tokenPair.receive.amount); - const receiveAmountWithSlippage = calculateAmountWithSlippage( - receiveAmount, - BigInt(slippage) - ); - - const newOrder: OrderDetails = { - protocol: pool.protocol, - poolId: pool.poolId, - send: tokenPair.send, - receive: { - address: receive, - amount: receiveAmountWithSlippage.toString(), - }, - walletAddress: address, - }; - - if ( - order === undefined || - BigInt(order.receive.amount) < BigInt(newOrder.receive.amount) - ) { - return newOrder; - } - - return order; - }; - - return pools.reduce(findBestOrder, undefined); -}; - -const calculateAmountWithSlippage = ( - amount: bigint, - slippage: bigint -): bigint => { - const slippageAmount = ceilDivision( - BigInt(1000) * slippage * amount, - BigInt(100 * 1000) - ); - - return amount - slippageAmount; -}; - -function ceilDivision(a: bigint, b: bigint): bigint { - return (a + b - BigInt(1)) / b; -} - -function sameToken(a: TokenAddress, b: TokenAddress): boolean { - return ( - a != null && - b != null && - a != undefined && - b != undefined && - a.policyId === b.policyId && - a.assetName === b.assetName - ); -} diff --git a/packages/swap/index.ts b/packages/swap/index.ts index ef81038d16..1c7b7a0582 100644 --- a/packages/swap/index.ts +++ b/packages/swap/index.ts @@ -1 +1 @@ -export * from './dex'; +export * from './api/index'; diff --git a/packages/swap/openswap/index.ts b/packages/swap/openswap/index.ts deleted file mode 100644 index 02d200648a..0000000000 --- a/packages/swap/openswap/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './orders'; -export * from './pools'; -export * from './tokens'; diff --git a/packages/swap/openswap/orders.ts b/packages/swap/openswap/orders.ts deleted file mode 100644 index 8bcb26ac6a..0000000000 --- a/packages/swap/openswap/orders.ts +++ /dev/null @@ -1,107 +0,0 @@ -import axios from 'axios'; - -export type Protocol = 'minswap' | 'sundaeswap' | 'wingriders' | 'muesliswap'; - -export type Order = { - address: string; - protocol: Protocol; - poolId?: string; // only required for SundaeSwap trades. - sell: { - policyId: string; - assetName: string; // hexadecimal representation of token, i.e. "" for lovelace, "4d494c4b" for MILK. - amount: string; - }; - buy: { - policyId: string; - assetName: string; // hexadecimal representation of token, i.e. "" for lovelace, "4d494c4b" for MILK. - amount: string; - }; -}; - -export type OpenOrder = { - provider: Protocol; - from: { - amount: string; - token: string; - }; - to: { - amount: string; - token: string; - }; - deposit: string; - utxo: string; -}; - -const client = axios.create({ - baseURL: 'https://aggregator.muesliswap.com', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, -}); - -/** - * @param orderUTxO order UTxO from the smart contract to cancel. e.g. "txhash#0" - * @param collateralUTxOs collateral UTxOs to use for canceling the order in cbor format. - * @param walletAddress address of the wallet that owns the order in cbor format. - * @returns an unsigned transaction to cancel the order. - */ -export const cancelOrder = async ( - orderUTxO: string, - collateralUTxOs: string, - walletAddress: string -) => { - const response = await client.get( - `/cancelSwapTransaction?wallet=${walletAddress}&utxo=${orderUTxO}&collateralUtxo=${collateralUTxOs}` - ); - - if (response.status !== 200) { - throw new Error('Failed to construct cancel swap transaction', { - cause: response.data, - }); - } - - return { - tx: response.data.cbor, - }; -}; - -/** - * @param order the order to construct the datum for and the address to send the order to. - * @returns the order datum, order datum hash, and address to send the order to. - */ -export const createOrder = async (order: Order) => { - const response = await client.get( - `/constructSwapDatum?walletAddr=${order.address}&protocol=${order.protocol}&poolId=${order.poolId}&sellTokenPolicyID=${order.sell.policyId}&sellTokenNameHex=${order.sell.assetName}&sellAmount=${order.sell.amount}&buyTokenPolicyID=${order.buy.policyId}&buyTokenNameHex=${order.buy.assetName}&buyAmount=${order.buy.amount}` - ); - - if (response.status !== 200) { - throw new Error('Failed to construct swap datum', { - cause: response.data, - }); - } - - return { - hash: response.data.hash, - datum: response.data.datum, - address: response.data.address, - }; -}; - -/** - * @param stakeKeyHash the stake key hash of the wallet to get orders for. - * @returns all unfufilled orders for the given stake key hash. - */ -export const getOrders = async (stakeKeyHash: string): Promise => { - const response = await client.get(`/all?stake-key-hash=${stakeKeyHash}`, { - baseURL: 'https://onchain2.muesliswap.com/orders', - }); - - if (response.status !== 200) { - throw new Error(`Failed to get orders for ${stakeKeyHash}`, { - cause: response.data, - }); - } - - return response.data; -}; diff --git a/packages/swap/openswap/pools.ts b/packages/swap/openswap/pools.ts deleted file mode 100644 index 127efa76a3..0000000000 --- a/packages/swap/openswap/pools.ts +++ /dev/null @@ -1,63 +0,0 @@ -import axios from 'axios'; - -export type Pool = { - provider: - | 'minswap' - | 'sundaeswap' - | 'wingriders' - | 'muesliswap_v1' - | 'muesliswap_v2' - | 'muesliswap_v3'; - fee: string; // % pool liquidity provider fee, usually 0.3. - tokenA: { - amount: string; // amount of tokenA in the pool, without decimals. - token: string; // hexadecimal representation of tokenA, i.e. "." for lovelace, "8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa.4d494c4b" for MILK. - }; - tokenB: { - amount: string; // amount of tokenB in the pool, without decimals. - token: string; // hexadecimal representation of tokenB, i.e. "." for lovelace, "8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa.4d494c4b" for MILK. - }; - price: number; // float, current price in tokenA / tokenB according to the pool, NOT SUITABLE for price calculations, just for display purposes, i.e. 0.9097362621640215. - batcherFee: { - amount: string; // amount of fee taken by protocol batchers, in lovelace. - token: '.'; - }; - deposit: number; // amount of deposit / minUTxO required by protocol, returned to user, in lovelace. - utxo: string; // txhash#txindex of latest transaction involving this pool. - poolId: string; // identifier of the pool across platforms. - timestamp: string; // latest update of this pool in UTC, i.e. 2023-05-23 06:13:26. - lpToken: { - amount: string; // amount of lpToken minted by the pool, without decimals. - token: string; // hexadecimal representation of lpToken, - }; -}; - -const client = axios.create({ - baseURL: 'https://onchain2.muesliswap.com/pools', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, -}); - -/** - * @param tokenA the token to swap from. - * @param tokenB the token to swap to. - * @returns a list of pools for the given token pair. - */ -export const getPools = async ( - tokenA: { policyId: string; assetName: string }, - tokenB: { policyId: string; assetName: string } -): Promise => { - const response = await client.get( - `/pair?policy-id1=${tokenA.policyId}&tokenname1=${tokenA.assetName}&policy-id2=${tokenB.policyId}&tokenname-hex2=${tokenB.assetName}` - ); - - if (response.status !== 200) { - throw new Error('Failed to fetch pools for token pair', { - cause: response.data, - }); - } - - return response.data; -}; diff --git a/packages/swap/openswap/tokens.ts b/packages/swap/openswap/tokens.ts deleted file mode 100644 index 8dbfdeec92..0000000000 --- a/packages/swap/openswap/tokens.ts +++ /dev/null @@ -1,69 +0,0 @@ -import axios from 'axios'; - -export type Token = { - info: { - supply: { - total: string; // total circulating supply of the token, without decimals. - circulating: string | null; // if set the circulating supply of the token, if null the amount in circulation is unknown. - }; - status: 'verified' | 'unverified' | 'scam' | 'outdated'; - address: { - policyId: string; // policy id of the token. - name: string; // hexadecimal representation of token name. - }; - symbol: string; // shorthand token symbol. - image?: string; // http link to the token image. - website: string; - description: string; - decimalPlaces: number; // number of decimal places of the token, i.e. 6 for ADA and 0 for MILK. - categories: string[]; // encoding categories as ids. - }; - price: { - volume: { - base: number; // float, trading volume 24h in base currency (e.g. ADA). - quote: number; // float, trading volume 24h in quote currency. - }; - volumeChange: { - base: number; // float, percent change of trading volume in comparison to previous 24h. - quote: number; // float, percent change of trading volume in comparison to previous 24h. - }; - price: number; // live trading price in base currency (e.g. ADA). - askPrice: number; // lowest ask price in base currency (e.g. ADA). - bidPrice: number; // highest bid price in base currency (e.g. ADA). - priceChange: { - '24h': number; // float, price change last 24 hours. - '7d': number; // float, price change last 7 days. - }; - quoteDecimalPlaces: number; // decimal places of quote token. - baseDecimalPlaces: number; // decimal places of base token. - price10d: number[]; //float, prices of this tokens averaged for the last 10 days, in chronological order i.e.oldest first. - }; -}; - -const client = axios.create({ - baseURL: 'https://api.muesliswap.com', - headers: { - Accept: 'application/json', - 'Content-Type': 'applicatio/json', - }, -}); - -/** - * @param policyId the policy id of the base token to calculate the price for in hexadecimal representation. - * @param assetName the asset name of the base token to calculate the price for in hexadecimal representation. - * @returns a list of tokens supported by MuesliSwap. - */ -export const getTokens = async ( - policyId = '', - assetName = '' -): Promise => { - const response = await client.get( - `/list?base-policy-id=${policyId}&base-tokenname=${assetName}` - ); - - if (response.status !== 200) { - throw new Error('Failed to fetch tokens', { cause: response.data }); - } - - return response.data; -}; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index e8005a7ebc..d1d4a5d9c7 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -10,15 +10,17 @@ import {MetricsStorage} from './metrics/storage' import {MetricsTrack} from './metrics/track' import { SwapFactoryOptions, - SwapOrderCreateData, SwapOrderType, SwapProtocol, SwapSlippageOptions, SwapOpenOrder, SwapNetwork, - SwapOrder, SwapPool, SwapTokenInfo, + SwapBaseTokenInfo, + SwapCreateOrderResponse, + SwapCreateOrderData, + SwapApi, } from './swap/module' import {SwapStorage} from './swap/storage' @@ -35,14 +37,16 @@ export namespace Swap { export type FactoryOptions = SwapFactoryOptions export type SlippageOptions = SwapSlippageOptions - export type CreateOrderData = SwapOrderCreateData + export type CreateOrderData = SwapCreateOrderData + export type CreateOrderResponse = SwapCreateOrderResponse export type OrderType = SwapOrderType export type Protocol = SwapProtocol export type Netowrk = SwapNetwork - export type Order = SwapOrder export type OpenOrder = SwapOpenOrder export type Pool = SwapPool export type TokenInfo = SwapTokenInfo + export type BaseTokenInfo = SwapBaseTokenInfo + export interface ISwapApi extends SwapApi {} export type Storage = SwapStorage } diff --git a/packages/types/src/swap/module.ts b/packages/types/src/swap/module.ts index 939c7565bb..d59751cdaa 100644 --- a/packages/types/src/swap/module.ts +++ b/packages/types/src/swap/module.ts @@ -8,7 +8,7 @@ export type SwapProtocol = | 'wingriders' | 'muesliswap' -export type SwapOrder = { +export type SwapCreateOrderData = { address: string protocol: SwapProtocol poolId?: string // Only required for SundaeSwap trades. @@ -24,6 +24,12 @@ export type SwapOrder = { } } +export type SwapCreateOrderResponse = Record< + 'datumHash' | 'datum' | 'contractAddress', + string +> + +// todo: add full type def. export type SwapOpenOrder = { provider: SwapProtocol from: { @@ -158,13 +164,33 @@ export type SwapTokenInfo = { } } +// todo: choose better name +export type SwapBaseTokenInfo = + | {policyId: string; assetName: string} + | {policyId: string; assetNameHex: string} + +export interface SwapApi { + createOrder(order: SwapCreateOrderData): Promise + cancelOrder( + orderUTxO: string, + collateralUTxO: string, + walletAddress: string, + ): Promise + getOrders(stakeKeyHash: string): Promise + getPools( + tokenA: SwapBaseTokenInfo, + tokenB: SwapBaseTokenInfo, + ): Promise + getTokens(policyId?: string, assetName?: string): Promise +} + export type SwapModule = { orders: { prepare: (order: SwapOrderCreateData) => Promise create: (order: SwapOrderCreateData) => Promise cancel: (order: SwapOrderCancelData) => Promise list: { - byStatusOpen: () => Promise> + byStatusOpen: () => Promise> } } pairs: {