diff --git a/.gitignore b/.gitignore index 2d8c7b6..fe92b2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ build/ -.idea/ \ No newline at end of file +.idea/ +.DS_Store \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f8cf5d1..cdf19da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ All notable changes to Dexter will be documented in this file. -## [UNRELEASED] +## [v5.4.0] +- SundaeSwap v3 integration + +## [v5.3.0] - Minswap v2 integration ## [v5.2.0] diff --git a/package-lock.json b/package-lock.json index 24c9299..a991657 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@indigo-labs/dexter", - "version": "5.0.3", + "version": "5.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@indigo-labs/dexter", - "version": "5.0.3", + "version": "5.4.0", "license": "MIT", "dependencies": { "@types/crypto-js": "^4.1.1", diff --git a/package.json b/package.json index ee04730..700c93d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@indigo-labs/dexter", - "version": "5.3.0", + "version": "5.4.0", "license": "MIT", "author": "Zachary Sluder", "keywords": [ diff --git a/src/constants.ts b/src/constants.ts index b746768..6ae00df 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,80 +1,86 @@ export enum MetadataKey { - Message = 674, + Message = 674, } export enum DatumParameterKey { - /** - * Generics. - */ - Action = 'Action', - TokenPolicyId = 'TokenPolicyId', - TokenAssetName = 'TokenAssetName', - ReserveA = 'ReserveA', - ReserveB = 'ReserveB', + /** + * Generics. + */ + Action = 'Action', + TokenPolicyId = 'TokenPolicyId', + TokenAssetName = 'TokenAssetName', + ReserveA = 'ReserveA', + ReserveB = 'ReserveB', + CancelDatum = 'CancelDatum', - /** - * Swap/wallet info. - */ - SenderPubKeyHash = 'SenderPubKeyHash', - SenderStakingKeyHash = 'SenderStakingKeyHash', - SenderKeyHashes = 'SenderKeyHashes', - ReceiverPubKeyHash = 'ReceiverPubKeyHash', - ReceiverStakingKeyHash = 'ReceiverStakingKeyHash', - SwapInAmount = 'SwapInAmount', - SwapInTokenPolicyId = 'SwapInTokenPolicyId', - SwapInTokenAssetName = 'SwapInTokenAssetName', - SwapOutTokenPolicyId = 'SwapOutTokenPolicyId', - SwapOutTokenAssetName = 'SwapOutTokenAssetName', - MinReceive = 'MinReceive', - Expiration = 'Expiration', - AllowPartialFill = 'AllowPartialFill', - Direction = 'Direction', + /** + * Swap/wallet info. + */ + SenderPubKeyHash = 'SenderPubKeyHash', + SenderStakingKeyHash = 'SenderStakingKeyHash', + SenderKeyHashes = 'SenderKeyHashes', + ReceiverPubKeyHash = 'ReceiverPubKeyHash', + ReceiverStakingKeyHash = 'ReceiverStakingKeyHash', + SwapInAmount = 'SwapInAmount', + SwapInTokenPolicyId = 'SwapInTokenPolicyId', + SwapInTokenAssetName = 'SwapInTokenAssetName', + SwapOutTokenPolicyId = 'SwapOutTokenPolicyId', + SwapOutTokenAssetName = 'SwapOutTokenAssetName', + MinReceive = 'MinReceive', + Expiration = 'Expiration', + AllowPartialFill = 'AllowPartialFill', + Direction = 'Direction', - /** - * Trading fees. - */ - TotalFees = 'TotalFees', - BatcherFee = 'BatcherFee', - DepositFee = 'DepositFee', - ScooperFee = 'ScooperFee', - BaseFee = 'BaseFee', - FeeSharingNumerator = 'FeeSharingNumerator', + /** + * Trading fees. + */ + TotalFees = 'TotalFees', + BatcherFee = 'BatcherFee', + DepositFee = 'DepositFee', + ScooperFee = 'ScooperFee', + BaseFee = 'BaseFee', + FeeSharingNumerator = 'FeeSharingNumerator', + OpeningFee = 'OpeningFee', + FinalFee = 'FinalFee', + FeesFinalized = 'FeesFinalized', + MarketOpen = 'MarketOpen', + ProtocolFee = 'ProtocolFee', - /** - * LP info. - */ - PoolIdentifier = 'PoolIdentifier', - TotalLpTokens = 'TotalLpTokens', - LpTokenPolicyId = 'LpTokenPolicyId', - LpTokenAssetName = 'LpTokenAssetName', - LpFee = 'LpFee', - LpFeeNumerator = 'LpFeeNumerator', - LpFeeDenominator = 'LpFeeDenominator', - PoolAssetAPolicyId = 'PoolAssetAPolicyId', - PoolAssetAAssetName = 'PoolAssetAAssetName', - PoolAssetATreasury = 'PoolAssetATreasury', - PoolAssetABarFee = 'PoolAssetABarFee', - PoolAssetBPolicyId = 'PoolAssetBPolicyId', - PoolAssetBAssetName = 'PoolAssetBAssetName', - PoolAssetBTreasury = 'PoolAssetBTreasury', - PoolAssetBBarFee = 'PoolAssetBBarFee', - RootKLast = 'RootKLast', - LastInteraction = 'LastInteraction', - RequestScriptHash = 'RequestScriptHash', - StakeAdminPolicy = 'StakeAdminPolicy', - LqBound = 'LqBound', + /** + * LP info. + */ + PoolIdentifier = 'PoolIdentifier', + TotalLpTokens = 'TotalLpTokens', + LpTokenPolicyId = 'LpTokenPolicyId', + LpTokenAssetName = 'LpTokenAssetName', + LpFee = 'LpFee', + LpFeeNumerator = 'LpFeeNumerator', + LpFeeDenominator = 'LpFeeDenominator', + PoolAssetAPolicyId = 'PoolAssetAPolicyId', + PoolAssetAAssetName = 'PoolAssetAAssetName', + PoolAssetATreasury = 'PoolAssetATreasury', + PoolAssetABarFee = 'PoolAssetABarFee', + PoolAssetBPolicyId = 'PoolAssetBPolicyId', + PoolAssetBAssetName = 'PoolAssetBAssetName', + PoolAssetBTreasury = 'PoolAssetBTreasury', + PoolAssetBBarFee = 'PoolAssetBBarFee', + RootKLast = 'RootKLast', + LastInteraction = 'LastInteraction', + RequestScriptHash = 'RequestScriptHash', + StakeAdminPolicy = 'StakeAdminPolicy', + LqBound = 'LqBound', } export enum TransactionStatus { - Building, - Signing, - Submitting, - Submitted, - Errored, + Building, + Signing, + Submitting, + Submitted, + Errored, } export enum AddressType { - Contract, - Base, - Enterprise, + Contract, + Base, + Enterprise, } diff --git a/src/definition-builder.ts b/src/definition-builder.ts index f31c379..9190528 100644 --- a/src/definition-builder.ts +++ b/src/definition-builder.ts @@ -1,171 +1,200 @@ import { DatumParameters, DefinitionConstr, DefinitionField } from './types'; -import { datumJsonToCbor } from 'lucid-cardano'; import { DatumParameterKey } from './constants'; import _ from 'lodash'; +import { datumJsonToCbor } from '@app/utils'; export class DefinitionBuilder { + private _definition: DefinitionConstr; + + /** + * Load a DEX definition file as a template for this builder. + */ + public async loadDefinition(definition: DefinitionConstr): Promise { + this._definition = _.cloneDeepWith(definition, (value: any) => { + if (value instanceof Function) { + return value; + } + }); + + return this; + } + + /** + * Push specified parameters to the definition template. + */ + public pushParameters(parameters: DatumParameters): DefinitionBuilder { + if (!this._definition) { + throw new Error(`Definition file must be loaded before applying parameters`); + } - private _definition: DefinitionConstr; + this._definition = this.applyParameters(this._definition, parameters); - /** - * Load a DEX definition file as a template for this builder. - */ - public async loadDefinition(definition: DefinitionConstr): Promise { - this._definition = _.cloneDeepWith(definition, (value: any) => { - if (value instanceof Function) { - return value; - } - }) + return this; + } - return this; + /** + * Pull parameters of a datum using a definition template. + */ + public pullParameters(definedDefinition: DefinitionConstr): DatumParameters { + if (!this._definition) { + throw new Error(`Definition file must be loaded before pulling parameters`); } - /** - * Push specified parameters to the definition template. - */ - public pushParameters(parameters: DatumParameters): DefinitionBuilder { - if (! this._definition) { - throw new Error(`Definition file must be loaded before applying parameters`); - } - - this._definition = this.applyParameters(this._definition, parameters); - - return this; + return this.extractParameters(definedDefinition, this._definition); + } + + /** + * Retrieve the CBOR for the builder. + */ + public getCbor(): string { + return datumJsonToCbor(JSON.parse(JSON.stringify(this._definition))); + } + + /** + * Recursively set specified parameters. + */ + private applyParameters(field: DefinitionField, mappedParameters: DatumParameters): DefinitionConstr { + if (field instanceof Function) { + return field(field, mappedParameters, false); } - /** - * Pull parameters of a datum using a definition template. - */ - public pullParameters(definedDefinition: DefinitionConstr): DatumParameters { - if (! this._definition) { - throw new Error(`Definition file must be loaded before pulling parameters`); + if ('fields' in field) { + if (typeof field.constructor === 'string') { + const parameterValue: any = mappedParameters[field.constructor as keyof typeof DatumParameterKey]; + + if (typeof parameterValue !== 'number') { + throw new Error(`Invalid parameter value '${parameterValue}' for constructor value`); } - return this.extractParameters(definedDefinition, this._definition); - } + field.constructor = parameterValue; + } - /** - * Retrieve the CBOR for the builder. - */ - public getCbor(): string { - return datumJsonToCbor( - JSON.parse( - JSON.stringify(this._definition) - ) - ); + field.fields = field.fields.map((fieldParameter: DefinitionField) => { + return this.applyParameters(fieldParameter, mappedParameters); + }); } - /** - * Recursively set specified parameters. - */ - private applyParameters(field: DefinitionField, mappedParameters: DatumParameters): DefinitionConstr { - if (field instanceof Function) { - return field(field, mappedParameters, false); - } + if ('list' in field) { + field.list = (field.list as DefinitionField[])?.map((fieldParameter: DefinitionField) => { + return this.applyParameters(fieldParameter, mappedParameters); + }); + } - if ('fields' in field) { - if (typeof field.constructor === 'string') { - const parameterValue: any = mappedParameters[field.constructor as keyof typeof DatumParameterKey]; + if ('int' in field) { + let parameterValue: any = mappedParameters[field.int as keyof typeof DatumParameterKey]; - if (typeof parameterValue !== 'number') { - throw new Error(`Invalid parameter value '${parameterValue}' for constructor value`); - } + if (typeof parameterValue === 'bigint') { + parameterValue = Number(parameterValue); + } + if (typeof parameterValue !== 'number') { + throw new Error(`Invalid parameter value '${parameterValue}' for type 'int'`); + } - field.constructor = parameterValue; - } + field.int = parameterValue; + } - field.fields = field.fields.map((fieldParameter: DefinitionField) => { - return this.applyParameters(fieldParameter, mappedParameters); - }); - } + if ('bytes' in field) { + const parameterValue: any = mappedParameters[field.bytes as keyof typeof DatumParameterKey] ?? ''; - if ('int' in field) { - let parameterValue: any = mappedParameters[field.int as keyof typeof DatumParameterKey]; + if (typeof parameterValue !== 'string') { + throw new Error(`Invalid parameter value '${parameterValue}' for type 'bytes'`); + } - if (typeof parameterValue === 'bigint') { - parameterValue = Number(parameterValue); - } - if (typeof parameterValue !== 'number') { - throw new Error(`Invalid parameter value '${parameterValue}' for type 'int'`); - } + field.bytes = parameterValue; + } - field.int = parameterValue; - } + if (Array.isArray(field) && field.every((item) => typeof item === 'object' && Object.keys(item).length === 1)) { + field.forEach((value) => { + return this.applyParameters(value, mappedParameters); + }); + } - if ('bytes' in field) { - const parameterValue: any = mappedParameters[field.bytes as keyof typeof DatumParameterKey] ?? ''; + return field as DefinitionConstr; + } - if (typeof parameterValue !== 'string') { - throw new Error(`Invalid parameter value '${parameterValue}' for type 'bytes'`); - } + /** + * Recursively pull parameters from datum using definition template. + */ + private extractParameters(definedDefinition: DefinitionField, templateDefinition: DefinitionField, foundParameters: DatumParameters = {}): DatumParameters { + if (! templateDefinition) return foundParameters; - field.bytes = parameterValue; - } + if (templateDefinition instanceof Function) { + templateDefinition(definedDefinition, foundParameters); - return field as DefinitionConstr; + return foundParameters; } - /** - * Recursively pull parameters from datum using definition template. - */ - private extractParameters(definedDefinition: DefinitionField, templateDefinition: DefinitionField, foundParameters: DatumParameters = {}): DatumParameters { - if (! templateDefinition) return foundParameters; + if (templateDefinition instanceof Array) { + templateDefinition + .map((fieldParameter: DefinitionField, index: number) => { + return this.extractParameters(fieldParameter, templateDefinition[index], foundParameters); + }) + .forEach((parameters: DatumParameters) => { + foundParameters = { ...foundParameters, ...parameters }; + }); + } - if (templateDefinition instanceof Function) { - templateDefinition(definedDefinition, foundParameters); + if ('fields' in definedDefinition) { + if (!('fields' in templateDefinition)) { + throw new Error("Template definition does not match with 'fields'"); + } - return foundParameters; - } + if (templateDefinition.constructor && typeof templateDefinition.constructor !== 'number') { + foundParameters[templateDefinition.constructor] = definedDefinition.constructor; + } else if (templateDefinition.constructor !== definedDefinition.constructor) { + throw new Error('Template definition does not match with constructor value'); + } - if (templateDefinition instanceof Array) { - templateDefinition.map((fieldParameter: DefinitionField, index: number) => { - return this.extractParameters(fieldParameter, templateDefinition[index], foundParameters); - }).forEach((parameters: DatumParameters) => { - foundParameters = {...foundParameters, ...parameters}; - }) - } - - if ('fields' in definedDefinition) { - if (! ('fields' in templateDefinition)) { - throw new Error("Template definition does not match with 'fields'"); - } - - if (typeof templateDefinition.constructor !== 'number') { - foundParameters[templateDefinition.constructor] = definedDefinition.constructor; - } else if (templateDefinition.constructor !== definedDefinition.constructor) { - throw new Error("Template definition does not match with constructor value"); - } - - definedDefinition.fields.map((fieldParameter: DefinitionField, index: number) => { - return this.extractParameters(fieldParameter, templateDefinition.fields[index], foundParameters); - }).forEach((parameters: DatumParameters) => { - foundParameters = {...foundParameters, ...parameters}; - }); - } + definedDefinition.fields + .map((fieldParameter: DefinitionField, index: number) => { + return this.extractParameters(fieldParameter, templateDefinition.fields[index], foundParameters); + }) + .forEach((parameters: DatumParameters) => { + foundParameters = { ...foundParameters, ...parameters }; + }); + } - if ('int' in definedDefinition) { - if (! ('int' in templateDefinition)) { - throw new Error("Template definition does not match with 'int'"); - } + if ('list' in definedDefinition) { + if (!('list' in templateDefinition) || !Array.isArray(definedDefinition.list)) { + throw new Error("Template definition does not match or 'list' is not an array"); + } + + (definedDefinition.list as DefinitionField[]) + .map((fieldParameter: DefinitionField, index: number) => { + // Ensure the template list at index exists before attempting to access it + if (templateDefinition.list as DefinitionField[]) { + return this.extractParameters(fieldParameter, (templateDefinition.list as DefinitionField[])[index], foundParameters); + } else { + throw new Error(`Template definition at index ${index} is undefined`); + } + }) + .forEach((parameters: any) => { + foundParameters = { ...foundParameters, ...parameters }; + }); + } - if (typeof templateDefinition.int !== 'number') { - foundParameters[templateDefinition.int] = definedDefinition.int; - } - } + if ('int' in definedDefinition) { + if (!('int' in templateDefinition)) { + throw new Error("Template definition does not match with 'int'"); + } - if ('bytes' in definedDefinition) { - if (! ('bytes' in templateDefinition)) { - throw new Error("Template definition does not match with 'bytes'"); - } + if (typeof templateDefinition.int !== 'number') { + foundParameters[templateDefinition.int] = definedDefinition.int; + } + } - const datumKeys: string[] = Object.values(DatumParameterKey); + if ('bytes' in definedDefinition) { + if (!('bytes' in templateDefinition)) { + throw new Error("Template definition does not match with 'bytes'"); + } - if (datumKeys.includes(templateDefinition.bytes)) { - foundParameters[templateDefinition.bytes] = definedDefinition.bytes; - } - } + const datumKeys: string[] = Object.values(DatumParameterKey); - return foundParameters; + if (datumKeys.includes(templateDefinition.bytes)) { + foundParameters[templateDefinition.bytes] = definedDefinition.bytes; + } } + return foundParameters; + } } diff --git a/src/dex/api/sundaeswap-api.ts b/src/dex/api/sundaeswap-v1-api.ts similarity index 94% rename from src/dex/api/sundaeswap-api.ts rename to src/dex/api/sundaeswap-v1-api.ts index f684934..1e5e9a5 100644 --- a/src/dex/api/sundaeswap-api.ts +++ b/src/dex/api/sundaeswap-v1-api.ts @@ -2,16 +2,16 @@ import { BaseApi } from './base-api'; import { Asset, Token } from '../models/asset'; import { LiquidityPool } from '../models/liquidity-pool'; import axios, { AxiosInstance } from 'axios'; -import { SundaeSwap } from '../sundaeswap'; +import { SundaeSwapV1 } from '../sundaeswap-v1'; import { RequestConfig } from '@app/types'; import { appendSlash } from '@app/utils'; -export class SundaeSwapApi extends BaseApi { +export class SundaeSwapV1Api extends BaseApi { protected readonly api: AxiosInstance; - protected readonly dex: SundaeSwap; + protected readonly dex: SundaeSwapV1; - constructor(dex: SundaeSwap, requestConfig: RequestConfig) { + constructor(dex: SundaeSwapV1, requestConfig: RequestConfig) { super(); this.dex = dex; @@ -75,7 +75,7 @@ export class SundaeSwapApi extends BaseApi { const pools = response.data.data.pools; const liquidityPools = pools.map((pool: any) => { let liquidityPool: LiquidityPool = new LiquidityPool( - SundaeSwap.identifier, + SundaeSwapV1.identifier, pool.assetA.assetId ? Asset.fromIdentifier(pool.assetA.assetId, pool.assetA.decimals) : 'lovelace', diff --git a/src/dex/api/sundaeswap-v3-api.ts b/src/dex/api/sundaeswap-v3-api.ts new file mode 100644 index 0000000..4e02073 --- /dev/null +++ b/src/dex/api/sundaeswap-v3-api.ts @@ -0,0 +1,75 @@ +import { BaseApi } from './base-api'; +import { Asset, Token } from '../models/asset'; +import { LiquidityPool } from '../models/liquidity-pool'; +import axios, { AxiosInstance } from 'axios'; +import { RequestConfig } from '@app/types'; +import { appendSlash } from '@app/utils'; +import { SundaeSwapV3 } from '@dex/sundaeswap-v3'; + +export class SundaeSwapV3Api extends BaseApi { + + protected readonly api: AxiosInstance; + protected readonly dex: SundaeSwapV3; + + constructor(dex: SundaeSwapV3, requestConfig: RequestConfig) { + super(); + + this.dex = dex; + this.api = axios.create({ + timeout: requestConfig.timeout, + baseURL: `${appendSlash(requestConfig.proxyUrl)}https://api.sundae.fi/graphql`, + headers: { + 'Content-Type': 'application/json', + } + }); + } + + async liquidityPools(assetA: Token, assetB?: Token): Promise { + const assetAId: string = (assetA === 'lovelace') + ? 'ada.lovelace' + : assetA.identifier('.'); + const assetBId: string = (assetB && assetB !== 'lovelace') + ? assetB.identifier('.') + : 'ada.lovelace'; + const assets: string[] = [assetAId, assetBId]; + + return await this.api.post('', { + operationName: 'fetchPoolsByPair', + 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}`, + variables: { + assetA: assets[0], + assetB: assets[1], + }, + }).then((response: any) => { + const pools = response.data.data.pools.byPair; + + return pools + .filter((pool: any) => pool.version === 'V3') + .map((pool: any) => { + let liquidityPool: LiquidityPool = new LiquidityPool( + SundaeSwapV3.identifier, + pool.assetA.id === 'ada.lovelace' + ? 'lovelace' + : Asset.fromIdentifier(pool.assetA.id, pool.assetA.decimals), + pool.assetB.id === 'ada.lovelace' + ? 'lovelace' + : Asset.fromIdentifier(pool.assetB.id, pool.assetB.decimals), + BigInt(pool.current.quantityA.quantity), + BigInt(pool.current.quantityB.quantity), + this.dex.poolAddress, + '', + '', + ); + + liquidityPool.identifier = pool.id; + liquidityPool.lpToken = Asset.fromIdentifier(pool.assetLP.id); + liquidityPool.poolFeePercent = Number((pool.bidFee[0] / pool.bidFee[1]) * 100); + liquidityPool.totalLpTokens = BigInt(pool.current.quantityLP.quantity); + + return liquidityPool; + }); + }); + + } + +} diff --git a/src/dex/base-dex.ts b/src/dex/base-dex.ts index c7366ec..955410d 100644 --- a/src/dex/base-dex.ts +++ b/src/dex/base-dex.ts @@ -5,6 +5,7 @@ import { AssetBalance, DatumParameters, PayToAddress, SpendUTxO, SwapFee, UTxO } import { DatumParameterKey } from '@app/constants'; import { tokensMatch } from '@app/utils'; import { BaseApi } from '@dex/api/base-api'; +import { BaseWalletProvider } from '@providers/wallet/base-wallet-provider'; export abstract class BaseDex { @@ -16,17 +17,17 @@ export abstract class BaseDex { /** * Fetch addresses mapped to a liquidity pool. */ - abstract liquidityPoolAddresses(provider: BaseDataProvider): Promise; + abstract liquidityPoolAddresses(provider?: BaseDataProvider): Promise; /** * Fetch all liquidity pools. */ - abstract liquidityPools(provider: BaseDataProvider): Promise; + abstract liquidityPools(provider: BaseDataProvider, wallet?: BaseWalletProvider): Promise; /** * Craft liquidity pool state from a valid UTxO. */ - abstract liquidityPoolFromUtxo(provider: BaseDataProvider, utxo: UTxO): Promise; + abstract liquidityPoolFromUtxo(provider: BaseDataProvider, utxo: UTxO, wallet?: BaseWalletProvider): Promise; /** * Estimated swap in amount given for a swap out token & amount on a liquidity pool. @@ -51,12 +52,12 @@ export abstract class BaseDex { /** * Craft a swap order cancellation for this DEX. */ - abstract buildCancelSwapOrder(txOutputs: UTxO[], returnAddress: string): Promise; + abstract buildCancelSwapOrder(txOutputs: UTxO[], returnAddress: string, wallet?: BaseWalletProvider): Promise; /** * Fees associated with submitting a swap order. */ - abstract swapOrderFees(): SwapFee[]; + abstract swapOrderFees(liquidityPool?: LiquidityPool, swapInToken?: Token, swapInAmount?: bigint): SwapFee[]; /** * Adjust the payment for the DEX order address to include the swap in amount. diff --git a/src/dex/definitions/sundaeswap/order.ts b/src/dex/definitions/sundaeswap-v1/order.ts similarity index 100% rename from src/dex/definitions/sundaeswap/order.ts rename to src/dex/definitions/sundaeswap-v1/order.ts diff --git a/src/dex/definitions/sundaeswap/pool.ts b/src/dex/definitions/sundaeswap-v1/pool.ts similarity index 100% rename from src/dex/definitions/sundaeswap/pool.ts rename to src/dex/definitions/sundaeswap-v1/pool.ts diff --git a/src/dex/definitions/sundaeswap-v3/order.ts b/src/dex/definitions/sundaeswap-v3/order.ts new file mode 100644 index 0000000..a5813d1 --- /dev/null +++ b/src/dex/definitions/sundaeswap-v3/order.ts @@ -0,0 +1,96 @@ +import { DatumParameterKey } from '@app/constants'; + +export default { + constructor: 0, + fields: [ + { + constructor: 0, + fields: [ + { + bytes: DatumParameterKey.PoolIdentifier, + }, + ], + }, + { + constructor: 0, + fields: [ + { + bytes: DatumParameterKey.SenderStakingKeyHash, + }, + ], + }, + { + int: DatumParameterKey.ProtocolFee, + }, + { + constructor: 0, + fields: [ + { + constructor: 0, + fields: [ + { + constructor: 0, + fields: [ + { + bytes: DatumParameterKey.SenderPubKeyHash, + }, + ], + }, + { + constructor: 0, + fields: [ + { + constructor: 0, + fields: [ + { + constructor: 0, + fields: [ + { + bytes: DatumParameterKey.SenderStakingKeyHash, + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + constructor: 0, + fields: [], + }, + ], + }, + { + constructor: 1, + fields: [ + [ + { + bytes: DatumParameterKey.SwapInTokenPolicyId, + }, + { + bytes: DatumParameterKey.SwapInTokenAssetName, + }, + { + int: DatumParameterKey.SwapInAmount, + }, + ], + [ + { + bytes: DatumParameterKey.SwapOutTokenPolicyId, + }, + { + bytes: DatumParameterKey.SwapOutTokenAssetName, + }, + { + int: DatumParameterKey.MinReceive, + }, + ], + ], + }, + { + bytes: DatumParameterKey.CancelDatum, + }, + ], +}; diff --git a/src/dex/definitions/sundaeswap-v3/pool.ts b/src/dex/definitions/sundaeswap-v3/pool.ts new file mode 100644 index 0000000..150c3d6 --- /dev/null +++ b/src/dex/definitions/sundaeswap-v3/pool.ts @@ -0,0 +1,47 @@ +import { DatumParameterKey } from '@app/constants'; +import { DatumParameters, DefinitionField } from '@app/types'; + +export default { + constructor: 0, + fields: [ + { + bytes: DatumParameterKey.PoolIdentifier, + }, + { + list: [ + { + list: [ + { + bytes: DatumParameterKey.PoolAssetAPolicyId + }, + { + bytes: DatumParameterKey.PoolAssetAAssetName + } + ], + }, + { + list: [ + { + bytes: DatumParameterKey.PoolAssetBPolicyId + }, + { + bytes: DatumParameterKey.PoolAssetBAssetName + } + ], + }, + ], + }, + { + int: DatumParameterKey.TotalLpTokens + }, + { + int: DatumParameterKey.OpeningFee + }, + { + int: DatumParameterKey.FinalFee + }, + (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => { + return; + }, + ], +}; diff --git a/src/dex/sundaeswap.ts b/src/dex/sundaeswap-v1.ts similarity index 96% rename from src/dex/sundaeswap.ts rename to src/dex/sundaeswap-v1.ts index 2159937..381d648 100644 --- a/src/dex/sundaeswap.ts +++ b/src/dex/sundaeswap-v1.ts @@ -15,15 +15,15 @@ import { import { DefinitionBuilder } from '@app/definition-builder'; import { correspondingReserves, tokensMatch } from '@app/utils'; import { AddressType, DatumParameterKey } from '@app/constants'; -import pool from '@dex/definitions/sundaeswap/pool'; -import order from '@dex/definitions/sundaeswap/order'; +import pool from '@dex/definitions/sundaeswap-v1/pool'; +import order from '@dex/definitions/sundaeswap-v1/order'; import { BaseApi } from '@dex/api/base-api'; -import { SundaeSwapApi } from '@dex/api/sundaeswap-api'; +import { SundaeSwapV1Api } from '@dex/api/sundaeswap-v1-api'; import { Script } from 'lucid-cardano'; -export class SundaeSwap extends BaseDex { +export class SundaeSwapV1 extends BaseDex { - public static readonly identifier: string = 'SundaeSwap'; + public static readonly identifier: string = 'SundaeSwapV1'; public readonly api: BaseApi; /** @@ -41,7 +41,7 @@ export class SundaeSwap extends BaseDex { constructor(requestConfig: RequestConfig = {}) { super(); - this.api = new SundaeSwapApi(this, requestConfig); + this.api = new SundaeSwapV1Api(this, requestConfig); } public async liquidityPoolAddresses(provider: BaseDataProvider): Promise { @@ -84,7 +84,7 @@ export class SundaeSwap extends BaseDex { const assetBIndex: number = relevantAssets.length === 2 ? 1 : 2; const liquidityPool: LiquidityPool = new LiquidityPool( - SundaeSwap.identifier, + SundaeSwapV1.identifier, relevantAssets[assetAIndex].asset, relevantAssets[assetBIndex].asset, relevantAssets[assetAIndex].quantity, @@ -111,6 +111,7 @@ export class SundaeSwap extends BaseDex { const datum: DefinitionField = await provider.datumValue(utxo.datumHash); const parameters: DatumParameters = builder.pullParameters(datum as DefinitionConstr); + liquidityPool.lpToken = lpToken; liquidityPool.identifier = typeof parameters.PoolIdentifier === 'string' ? parameters.PoolIdentifier : ''; diff --git a/src/dex/sundaeswap-v3.ts b/src/dex/sundaeswap-v3.ts new file mode 100644 index 0000000..3913599 --- /dev/null +++ b/src/dex/sundaeswap-v3.ts @@ -0,0 +1,244 @@ +import { LiquidityPool } from './models/liquidity-pool'; +import { BaseDataProvider } from '@providers/data/base-data-provider'; +import { Asset, Token } from './models/asset'; +import { BaseDex } from './base-dex'; +import { AssetBalance, DatumParameters, DefinitionConstr, DefinitionField, PayToAddress, RequestConfig, SpendUTxO, SwapFee, UTxO } from '@app/types'; +import { DefinitionBuilder } from '@app/definition-builder'; +import { correspondingReserves, lucidUtils, tokensMatch } from '@app/utils'; +import { AddressType, DatumParameterKey } from '@app/constants'; +import pool from '@dex/definitions/sundaeswap-v3/pool'; +import order from '@dex/definitions/sundaeswap-v3/order'; +import { BaseApi } from '@dex/api/base-api'; +import { AddressDetails, Lucid, Script, Utils } from 'lucid-cardano'; +import { SundaeSwapV3Api } from '@dex/api/sundaeswap-v3-api'; +import { BaseWalletProvider } from '@providers/wallet/base-wallet-provider'; + +export class SundaeSwapV3 extends BaseDex { + + public static readonly identifier: string = 'SundaeSwapV3'; + public readonly api: BaseApi; + + /** + * On-Chain constants. + */ + public readonly poolAddress: string = 'addr1x8srqftqemf0mjlukfszd97ljuxdp44r372txfcr75wrz26rnxqnmtv3hdu2t6chcfhl2zzjh36a87nmd6dwsu3jenqsslnz7e'; + public readonly lpTokenPolicyId: string = 'e0302560ced2fdcbfcb2602697df970cd0d6a38f94b32703f51c312b'; + public readonly cancelDatum: string = 'd87a80'; + public readonly orderScriptHash: string = 'fa6a58bbe2d0ff05534431c8e2f0ef2cbdc1602a8456e4b13c8f3077'; + public readonly orderScript: Script = { + type: 'PlutusV2', + script: '593ce301000033232323232323232322322253330073370e900018030008992999804191919191919299980719b87480080184c8c8c8c8c8c8c94ccc054cdc3a400060280022646464646464a66603666e1d2000301a0011323232533301e3370e9002180e80089919191919191919299981319b8748000c0940044c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc0d4cdc49bad303a303b303b303b303b303b303b303b303b303b303b3033004375a6002606602c26464a66606e66e1d20003036001132323232323232533303e3370e9000181e80089919299982019baf3036303e0013035303e00f153330403370e66606e6eacc0d4c0f80040a80552002132533304101d1533304100615333041005100114a02940528299982019b8f375c606c607c04202e2a66608066ebcdd3198221919bb03046001304630470013758606a607c04297adef6c60374c6608866ec0dd399822182080f99822182100fa5eb80dd399822182080f19822182100f25eb812f5bded8c02a66608066e1cdd69814981f01080addr1v92m7sgckq0pc72xgldexa0lepe7gdwhxuq8k2kmcjxdh2skxa0m44099b89375a600c607c0426eb4c114c118c118c118c118c118c0f80845280a5014a02c2c608800260780022c660486eb0c098c0ecc0ccc0ec0dc0a94ccc0f0cdc4a400400e2a66607866e2520020091533303c33712664600200244a66608200229000099b8048008cc008008c110004c8c8cc004004008894ccc10800452f5c0264666444646600200200644a6660900022006264660946e9ccc128dd4803198251ba9375c608e002660946ea0dd69824000a5eb80cc00c00cc130008c128004dd718208009bab304200133003003304600230440013756606260740409003099b8733303337566062607404004c01e90010a5014a02940cdd79ba632323300100100222533304100114bd6f7b630099191919299982119b8f4881000021003133046337606ea4008dd3000998030030019bab3043003375c6082004608a00460860026eacc004c0e4c0c4c0e40d4dd300111820182098209820982080099191919800998008011801998201ba902633040375201e660809810101004bd701801998201ba902633040375201a660806ea00112f5c04464666002002006004444a6660860042002264666008008608e0066644646600200200a44a66609000226609266ec0dd48021ba60034bd6f7b630099191919299982499baf330260080024c0103d879800013304d337606ea4020dd30038028a99982499b8f00800213232533304b3370e900000089982799bb0375201460a0609200400a200a609200264a666094a66609a00229445280a60103d87a800013374a9000198271ba60014bd70191998008008040011112999827801080089919980200218298019991191980080080291299982a00089982a99bb037520086ea000d2f5bded8c0264646464a6660aa66ebccc0c8020009300103d8798000133059337606ea4020dd40038028a99982a99b8f0080021323253330573370e900000089982d99bb0375201460b860aa00400a200a60aa00264a6660ac66e1c005200014c103d87a800013374a90001982d1ba80014bd7019b80007001133059337606ea4008dd4000998030030019bad3056003375c60a800460b000460ac0026eb8c138004dd69827800982880109982699bb037520046e98004cc01801800cdd598250019bae3048002304c002304a001375c60840026eacc10c004c114008c004cc0f8dd48121981f1ba900f3303e4c10101004bd701199911299981e99b870014800052f5bded8c0264646600200297adef6c60225333043001133044337606ea4018dd3001a5eb7bdb1804c8c8c8c94ccc110cdd799810805001260103d8798000133048337606ea4028dd30038028a99982219b8f00a002133048337606ea4028dd300380189982419bb037520046e98004cc01801800cdd598228019bae30430023047002304500132330010014bd6f7b63011299982100089982199bb037520086ea000d2f5bded8c0264646464a66608666ebccc08002000930103d8798000133047337606ea4020dd40038028a99982199b8f008002133047337606ea4020dd400380189982399bb037520046ea0004cc01801800cdd698220019bae304200230460023044001375c60600026eb8c0bc004dd698118009bad303d0013035001163253330373371000290000a60103d87a800015333037337120029001099ba548000cc0ecdd4000a5eb804cdd2a4000660766ea0c8c8ccc00400400c0088894ccc0eccdc48010008801099980180180099b833370000266e0c01400520043370666e000052002480112f5c066e0800400d4ccc0d4cdc39b8d375c606c0289000099b81003375a6002606602c20062c460746076607660766076607660760026660546eacc0a0c0c405cdd7181a0089bae30350113330293756604e606002c6eb8c0cc044dd7181a008991919191919191919299981e982000109919299981e19b8f0020381533303c3375e6e9800530010ba14873657474696e67730100132533303d3370e9002181e000899191900119299982019b87480000044c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc16cc1780084c8c8c8c8c8c8c9263302c0112302e00132533305e3370e9000000899192999831983300109924c6605c00246eb800458dd61832000982e0098a99982f19b874800800454ccc184c17004c5261616305c01232375a60be0286eb4c17404cc0a0050c094054c098058c08c05c58c170004c170008dd6982d000982d0011bad30580013058002375a60ac00260ac0046eb4c150004c150008dd618290009829001182800098280011919bb0304f001304f30500013758609c002609c004609800260980046094002609400460900026090004608c002607c0042c607c002608600260760022c604a607400c2c2c6eacc0f4008dd7181d8008b181f000981f0009bab302c3035001302b3034303b0063001001223253330373370e900000089919299981e181f8010a4c2c6eb8c0f4004c0d400854ccc0dccdc3a400400226464a666078607e00426493198038009198030030008b1bac303d0013035002153330373370e900200089919299981e181f80109924c6600e00246600c00c0022c6eb0c0f4004c0d400854ccc0dccdc3a400c002264646464a66607c608200426493198048009198040040008b1bac303f001303f002375a607a002606a0042a66606e66e1d200800113232533303c303f002149858dd6981e800981a8010a99981b99b87480280044c8c94ccc0f0c0fc00852616375a607a002606a0042c606a002464a66606a66e1d2000001132323232533303c303f00213232498c94ccc0eccdc3a400000226464a66608060860042649319299981f19b87480000044c8c94ccc10cc1180084c926300f001163044001303c0021533303e3370e90010008991919191919299982398250010a4c2c6eb4c120004c120008dd6982300098230011bad3044001303c00216303c00116304100130390031533303b3370e90010008a99981f181c8018a4c2c2c607200460100062c607a002607a004607600260660042c606600244646600200200644a66607200229309919801801981e8011801981d800919299981999b87480000044c8c94ccc0e0c0ec00852616375c607200260620042a66606666e1d2002001132325333038303b002149858dd7181c80098188010b18188009bac3025302e3026302e02a375c606800260680046eb8c0c8004c0c8008dd71818000998171ba93301e48904000643b0000013302e3752603a0026605c6ea4cc079221040014df10000014bd7019191919199b8c48020cdc09b8d00148020004dca0009980f8008019980f000a45012300375c603e604e603e604e603e604e00464a66605266e200052080041337160029110016375a603a604c603c604c002605800260480022c660186eb0c06cc08cc06cc08c07d20003375e646464646464a66605466e1d200000114c0103d87980001533302a3370e90010008998038028018a6103d87b8000302800133005004002375c605200a6eb8c09c010dd718138021bae30250034c103d879800022533302533720004002298103d8798000153330253371e0040022980103d87a800014c103d87b800032337606050002605060520026eb0c09004cc8cdd81813800981398140009bac30220123253330213370e9000000899191919191919191919191919192999819181a8010991924c646eb4c0cc024dd69818804191919191bae3035003375c6066004646eb8c0d000cdd718190011919bb0303600130363037001375860640186466ec0c0d4004c0d4c0d8004dd618180058b1bad30330013033002375a606200260620046eb4c0bc004c0bc008c8cdd81817000981718178009bac302d001302d002375a605600260560046466ec0c0a8004c0a8c0ac004dd6181480098148011bae3027001301f00216301f0013024001301c001163006301b001302100130190011633001375860066030602060300280126002002444a66603c0042980103d87a800013232533301d3370e0069000099ba548000cc0840092f5c0266600a00a00266e0400d2002302200330200022301d301e301e001375c603600260260022c6012602401c6eb4c060004c060008dd6980b000980b0011919bb03015001301530160013758602800260180122646464a66602266e1d20003010001132323232323300100100222533301a00114a026464a66603266e1cccc040dd59807180b9807180b8010038032400429444cc010010004c078008c070004dd6180518091805180900718030021bae3017001300f001163005300e00a375c60280026018012466004910104000de14000001223371400400246022602400246020002444646464a66601e66e1d20020011480004dd6980a1806801180680099299980719b8748008004530103d87a8000132323300100100222533301400114c103d87a800013232323253330153371e014004266e95200033019375000297ae0133006006003375a602c0066eb8c050008c060008c058004dd598099806001180600099198008008021129998088008a6103d87a800013232323253330123371e010004266e95200033016374c00297ae0133006006003375660260066eb8c044008c054008c04c004c01c00c526136563253330083370e900000089919299980698080010a4c2c6eb8c038004c01800c54ccc020cdc3a40040022646464646464a666022602800426493191919191bae3014003375c6024004646eb8c04c00cdd718088011919bb03015001301530160013758602200c6466ec0c050004c050c054004dd618078028b1bad30120013012002375a602000260200046466ec0c03c004c03cc040004dd6180700098030018b18030010991191919191929998071919191919191919191919191919191919191919191919299981299b8748008c0900044c8c8c94ccc0a0cdc3a4000604e002264646464a66605866e1d2004302b001132323232323232323232323232323232323232323232323232323232323232533304b3370e900000689919191919191919191919191919191919191919191919299983119b89480000044c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc1d0cdc399b8000700501413253330753370e07a66e0003000454ccc1d4c94ccc1d8cdc39bad304c3074071375a60986eb0c130c1d002c4c0b80044cdd79ba6001374c660f466ec0dd498099bae30683074071375066e04dd698261bac304c307400b375a609860e80e297adef6c6032323253330783370e90010008a5eb7bdb1804dd5983e983b001183b0009981a800828191980080082f11299983d0008a5eb7bdb1804c8c8c8c94ccc1eccdc7a4500002100313307f337606ea4008dd3000998030030019bab307c003375c60f400460fc00460f80022a6660ea64a6660ec66e1d200000113232323232533307b3370e9000183d0008991919299983f19b8748000c1f40044cdc78011bae308301307c001163300400602f375c61020200260f20022c660020b805c6002002444a6660fc0042980103d87a800013232533307d3370e0069000099ba548000cc20404dd480125eb804ccc014014004cdc0801a40046104020066eb8c20004008dd6183e000983a00f8a51307401e1533307532323253330783370e900100089919299983d19b8748008c1ecdd5182c183c1836183c02f099b8800500113371200a0026eb4c1f8004c1d8008528183b0009834183a1834183a02d1bad30353073070153330753370e6eb4c12cc1cc120dd698259bac304b307300a153330753332322232323232323232323232323232325333087013371e0149110015333087013301200f4801854ccc21c04cdc3982200799b8004f00615333087013370e66608a01e00a006002266e1cccc11403c184c044041200214a029405280a9998438099809007a40102a66610e0266e1cc11003c13c54ccc21c04cdc39998228078050040030a9998438099b8733304500f00500300113370e66608a01e0c2602202090010a5014a029405281bad308b01001308b01002375c61120200261120200e6eb8c21c04018dd69843008009843008011bae308401001308401004375c6104020066eb0c20404004c20404008dd6183f800983b80711980aa4504000de14000001223370e002646660020020069000111299983e801080089998018019840008011919b803232333001001002480008894ccc2080400840044ccc00c00cc21404008c8cdc0001240046108020046eacc1f4004008c1fc008dd7183398398241bab3053307304c153330753370e0846eb4c12c00854ccc1d4cdc381e99b8000c001153330753371e6eb8c19cc1cc1c0dd7183398398240a99983a99baf374c660f26466ec0c1ec004c1ecc1f0004dd61829983983825eb7bdb180dd31983c9919bb0307b001307b307c001375860a660e609097adef6c60153330753375e6e98cc1e4c8cdd8183d800983d983e0009bac303630730704bd6f7b6301ba633079323376060f600260f660f80026eb0c0d8c1cc1212f5bded8c02a6660ea66e1cdd6981a98398381bad3035307304813375e60a660e660ce60e60a460a660e660ce60e609829405280a5014a02c2c2c2c2c2c2c2c66e00cdc019b820130143370400e03066e0801405858dd6183c000983c000983b80098370029bad30740013074002375a60e400260e400460e000266464646464646464646446466666666600200202204c900003280124000900024000444444444a6661020200e2661040201066104026ea0008cc20804dd4000a5eb804c8c8c8c8c8c8c8c8c8c8c8c8c94ccc22c04c0b0c1f4c22404c1f4c224040044c94ccc23004cdc3a40006116020022646464646464646464646464a6661300264a6661320266e1d200000113371e6eb8c27804c25c040241a4528984b80804099191919199999999813813984e8080080e99b8001c48008058dd6184f0080080c8018011919199980080080d80302091111929998508099b87480000044c8c94ccc28c04cdc3a400061440200c2666600e00e98103d87a8000323232323232323232323253330ae013375e04c00a2a66615c0264646464a6661640200420022940c8c94ccc2cc04cdc3a400400226464a66616a0266e1d200230b6013754612602616602612602616602014266e240180044cdc40030009bad30b90100130b10100214a2616202002614602615e02611e02615e0200c6464a6661640266e1d20020011323253330b4013370e9001185a809baa30920130b20130a60130b20100913371200200c266e20004018dd6985c008009858008010a5130b00100130a20130ae0130a20130ae01005323253330b1013370e90010008991929998598099b8748008c2d004dd518488098588098488098588084b808800899b8100148008dd6985b808009857808010a41fd3f6f0fd20a615e02002614202615a02611a02615a02126026464a6661600266e1d20020011323253330b2013370e90011859809baa30900130b00130a40130b00109601100113370000290011bad30b60100130ae01002148000c2b804004c28004c2b004c28004c2b004248044c94ccc2bc04ccdca80680080408010b1bb3001161630b20100130b20100230b00100130b00100230ae0100130a601003375c61580200261580200461540200261440200261500261420200c08a2c6eb8c29c04004c27c0400c54ccc28404cdc3a4004002264646464646464a6661500266e240040544cdd8191919191919191919191919191919191919191919299985e80a99985e8099b8f01500a13371e026010294054ccc2f404cdc79bae30af01019005153330bd013371e6eb8c26c0406400c4c8c8c94ccc30004cdd7985900985f0080d985900985f008158a9998600099baf30960130be0101b309e0130be0102b153330c0013375e613c02617c020366e9800454ccc30004cdc49bad30960101c00213374a900019862009ba7330c401375201a66188026ea402ccc31004dd419b800090144bd7019862009ba7330c401375201066188026ea4018cc31004dd419b810040024bd7019862009ba700e4bd700b0b0b0b1bab30c101002375a617e02002666666666609c02a02600a00600c0020ac02c0220442c2c2a66617a02a66617a0266e3c0540144cdc78098018a50153330bd013371e6eb8c2bc0406402854ccc2f404cdc79bae309b01019008132323253330c0013375e616402617c02036616402617c020562a6661800266ebcc25804c2f80406cc27804c2f8040ac54ccc30004cdd7984f00985f0080d9ba6001153330c001337126eb4c258040700084cdd2a400066188026e9ccc31004dd480699862009ba900b330c401375066e040240092f5c066188026e9ccc31004dd480419862009ba9006330c401375066e000100512f5c066188026e9c0392f5c02c2c2c2c6eacc30404008dd6985f80800999999999982700a80980500400080302b00b0088110b0b0b1bad30c10100130c101002375c617e02002617e020126eb8c2f404020dd6985e00800985e008011bae30ba0100130ba01006375c61700200a6eb0c2dc04004c2dc04008dd6185a80800985a808011bac30b30100130ab01037375a6162020026162020046eb8c2bc04004c2bc0401cdd71856808031ba7002163370008c0106158020586154020566eb0c2a404004c2a404008dd6185380800984f808018a9998508099b87480100044c8c8c8c8c94ccc29804cdc4800809899bb0323253330a8013371e6eb8c26804008dd7184d009bac309a0130a601032153330a8013371e6eb8c21804008dd71843009bac309a0130a601032153330a8013371e6eb8c26804004dd7184d009bac30860130a601032153330a8013371e6eb8c21804004dd71843009bac30860130a60103213253330a90133710900000089919191919191929998580099baf30a20130ae0100d30a20130ae0101b153330b0013375e610c02615c0201a611c02615c020362a6661600266ebcc23804c2b804034dd3000899ba548000cc2d004dd39985a009851009bac30a20130ae0103a330b401308e013758614402615c0207466168026ea0cdc01bad3086013758614402615c0207400897ae0330b401374e66168026144026eb0c23804c2b8040e8cc2d004c23804dd618470098570081d1985a009ba8337006eb4c21804dd618470098570081d001a5eb80cc2d004dd39985a009851009bac30860130ae0103a330b401308e013758610c02615c0207466168026ea0cdc01bad3086013758610c02615c0207400497ae04bd700b0b0b199982299998229999822999982280a1bae30a101009375c611a0201266e052000003375c6142020106eb8c23404020cdc0a40000049101004881003370290000051bae30a1013758610a02615a020726eb8c23404dd618428098568081c80099b83337040046eb4c21004dd618420098560081c1bad30840137586140026158020706eb4c2bc04008dd6985680800a9998558099b880030011337606ea000cdd419b810023370666e08dd69840809bac30890130a901035337020020066eb4c20404dd6184e8098548081a899bb0375066e0400ccdc08018009ba80023370666e08004dd69840009bac309c0130a801034375a6100026eb0c22004c2a0040d0cc0ecdd6983f8011998338071bae309b01002375c610e020042c660746eb4c1f8008cdc09998330069bae309a01002375c610c02004a6661500266e3cdd7184d008012441001337009040497a008018a40002c2c2c2c6eb0c2a404014dd61853808021ba7002163370008800c6154020546150020526466ec0c2a004004c2a004c2a404004dd6185380800984f808018a9998508099b87480180044c8c8c8c8c94ccc29804cdc4800809899bb0323232323253330ab013371e00a6eb8c27404dd618408098548081a8a9998558099b8f003375c6112026eb0c20404c2a4040d44c8c8c8c94ccc2bc04cdd798508098568080618508098568080d0a9998578099baf30850130ad0100c308d0130ad0101a153330af013375e611a02615a020186e980044cdd2a400066166026e9ccc2cc04c28404dd618508098568081c99859809846809bac30a10130ad01039330b301375066e04dd69842809bac30a10130ad010390034bd7019859809ba7330b30130a1013758611a02615a020726616602611a026eb0c23404c2b4040e4cc2cc04dd419b81375a610a026eb0c23404c2b4040e40092f5c066166026e9ccc2cc04c28404dd618428098568081c99859809846809bac30850130ad01039330b301375066e04dd69842809bac30850130ad010390044bd7025eb80585858cccc110cccc110cccc110cccc11004c020018cdc0a4000006911004881003370290000049bae30a00137586140026158020706eb8c23004dd618500098560081c0011bae30a00137586118026158020706eb8c23004dd618460098560081c00099b83337040046eb4c20c04dd618458098558081b9bad308301375861060261560206e66e0ccdc10009bad3082013758613c0261540206c6eb4c20804dd618410098550081b1981e8009998348080028018b0b1bad30af0100130af01002375c615a02002615a0200a6eb8c2ac04010dd38010b19b8004400630aa0102a30a8010293758614e02002613e0200626464646464a66614c0266e2400404c4c8c8c94ccc2a4040044cdd80011ba70051337600046e9c0bccdc3a40046152026ea8c2a804008c2a0040054ccc29804cdc79bae3098013758614e020086eb8c26004dd6184c009852008180a9998530099b8f375c6108026eb0c29c04010dd71842009bac30980130a401030153330a6013371e6eb8c26004dd61854008021bae30980137586108026148020602a66614c0266e3cdd71842009bac30a801004375c6108026eb0c21004c290040c04c8c94ccc2a0054ccc2a004ccc2a0040052825114a22a6661500266ebcc26804c29804014c26804c2980404c54ccc2a004cdd7983f185300802984300985300809899baf30860130a601005374c0042940528099bb03374a900019856009ba7330ac01309a013758613402614c020646615802610c026eb0c26804c298040c8cc2b004dd419b80375a60fc6eb0c26804c298040c8dd6983f1bac30a9010064bd7019856009ba7330ac01309a013758610c02614c020646615802610c026eb0c21804c298040c8cc2b004dd419b80375a60fc6eb0c21804c298040c8dd6983f1bac30aa010064bd701985600983f18530081925eb80ccc2a00400530103d87a80004c0103d8798000163330a7013375e6e98005300101a0004a0944cc174c8c8cc004004008894ccc2b00400452f5bded8c02646464661600266ec0dd48009ba63232330010010022253330b20100114bd6f7b6300991985a0099bb037526eb8c2c404004dd419b8148000dd698590080099801801985b00801185a008009bab30ae0100333005005002375c615802004616002004615c020026666078660ba60726eb0c29c04010c0e4dd61854008022450048810000100b16161616163370008800c6154020546150020526466ec0c2a004004c2a004c2a404004dd6185380800984f80801984f808011bab3079309901011375a6138020046eb4c26804004c94ccc26404cdc3a4000002266ec0dd400e9ba8337000389001099bb0375066e0007520023750038612e020022c6138020026138020046134020026134020046eb4c26004004c26004004c25c04008c25404004c23404008c94ccc23c04cdc3a40000022646464646464646464646464a66613c026142020042646464649318490080319299984f0099b87480000044c8c8c8c94ccc29404c2a0040084c8c9263253330a4013370e90000008a999853809851008018a4c2c2a6661480266e1d20020011323253330a90130ac01002149858dd71855008009851008018a9998520099b87480100044c8c94ccc2a404c2b0040085261630aa0100130a2010031630a20100230660031630a60100130a60100230a401001309c0100816309c01007306200a32533309c013370e9000000899192999850809852008010a4c2c6eb8c28804004c2680403054ccc27004cdc3a40040022a66613e026134020182930b0b184d008058b184f80800984f80801184e80800984e80801184d80800984d808011bad309901001309901002309701001309701002309501001308d0100216308d01001309201001308a01001163232533308d013370e90000008a60103d87a80001533308d013370e90010008991919800800837112999849808008a6103d87a80001323232325333094013371e00e004266e95200033098010014bd70099803003001984a808019bae309301002309701002309501001375c612402611602004266e9520003309101309201308b010024bd7018458080098309844808008b184780800984780801184680800984280801184600801184500800a9998420099b8900c00413301100b337020080182660220e6008a6661060266e25200000313232325333086013371066e1802c008cdc300080108008b19b8000a002337049002000a9998418099b88003480404cdc7245080102040810204080000031533308301337100069010099b824820010cdc72441080102040810204080003370200690080a9998418099b88003480c04cdc124101001066e392201080102040810204080003370200690100a9998418099b88003481004cdc12410101002066e392201080102040810204080003370200690180a9998418099b88003481404cdc1241010101004066e392201080102040810204080003370200690200980b8018b1843808009843808019bad308501002308501008375861060200e6002002444a6660ea66e25201e0011333003003307b307b307b307b307b307b307b307b307b307b307b307b307b307b307b00233702002900f0a99983a99b89480380044ccc00c00cc1ecc1ecc1ecc1ecc1ecc1ecc1ec008cdc0800a401c2660080040026002002444a6660e666e1c00520001002133300300330790023370200290011111111111191919bb037500026e98cccc044cccc044cccc04400c03002ccdc0a40000089110048810033702900000280500480099b833370466e0801800c004cdc019b82007482827004cdc100180099b81482827004010894ccc1c0cdc40008010800880111998121bae3060001375c60980026eb4c110004c004004894ccc1b4cdc4000a40202a6660da66e2000520001480004cdc7245080102040810204080000011533306d337100029020099b824820010cc008008cdc0800a4020266e09208080808020330020023370200290201111299983719b870014800040104c8ccccc0a8014010004888c94ccc1ccc0ac0045300103d87a800013374a90001983b9ba60014bd701999981600080300291119299983b19b87001480005300103d87a800013374a90001983d1ba80014bd7019b8000100202b0283232002323300100100222533307400114984c94ccc1d400452613232323232323253330793370e90000008998050051983e8030028b183b800998180010009bae3077003375c60ec00660f400660f000460ee00460ee002660e466ec0dd48011ba80014bd6f7b63019991800800911299983699b8900148000400854ccc1c000852f5c0266600600660e600466e04005200205548008dd6983700098370011bad306c001306c002306a00132323232323232323232323232323232323232323232323307f3374a90001983f9ba73307f375200c660fe6ea4014cc1fcdd419b81333039375660b260f20b000c00a00497ae03307f374e660fe6ea4010cc1fcdd48019983f9ba8333039375660b260f20b000800697ae03307f374e660fe6ea4154cc1fcdd4980c00a9983f9ba80114bd7025eb80cc1fcdd4299983d99b8800b0011375a60fa01e264a6660f866e1c00520001375a60fc020266e00dd6983e80819b833370466e04008038cdc09bad307e010375a60fa02000266e0402c034cc1fcdd4004a5eb80c8c94ccc1f0cdc3a4000002201c2a6660f866e1d2002001132533307d3371001e0022002201e6eb4c20404c1e800858c1e8004c1b0c1e00594ccc1e4cdc7802245001007148000dd7183d0021bae3078003375c60f00066eb8c1d8008c8cdd8183d000983d183d8009bac307600c323376060f200260f260f40026eb0c1d002cdd6983b800983b8011bad30750013075002375a60e600260e60046466ec0c1c8004c1c8c1cc004dd6183880098388011bad306f001306f002323376060dc00260dc60de0026eb0c1b4004c1b4008dd718358009831830182b18310241198012441040014df100000122337140040022c66e0ccdc099b8000700148008004ccc07013d20012233700002a6660c4600660a860c060a860c0608060c0004290010a4000464a6660c266e1d200200114a22940c17c004dd6983180098318011bad30610013061002375a60be00260be00260bc00460b800260b800260b600260b400260b200260b0002609e0286eb0c154004c154008dd6982980098298011bad305100130490441323232323232323232323232533305733712016002264a6660b066ebc0ac0044c94ccc164cdd7981b982b8181ba6001132533305a3232330010010022232533305e3370e900000089919198008008211129998320008a501323253330633371e00400a29444cc010010004c1a0008dd718330009bae3063305c0021533305e3370e90010008991919198008008011129998328008a511323253330643300900900213300400400114a060d200460ce0026eb0c190004c17000854ccc178cdc3a4008002264646600e00246600c00c0026eb0c190004c17000854ccc178cdc3a400c00226464646466e2400cccc078005200022533306433009009002133700002900108009bac30660013066002375a60c800260b80042a6660bc66e1d200800113232323253330623370e900100089919299983219b8748008c194dd51821183118211831024099b8900100513371000200a6eb4c1a0004c18000852818300009829182f181f182f0221bad3064001305c00213232323253330623370e900100089919299983219b8748008c194dd518211831182b1831024099b8900500113371000a0026eb4c1a0004c18000852818300009829182f1829182f0221bad3064001305c002305c0013030305801d1323232533305d3370e9000182e00089919299982f99baf3051305d0013020305d0221533305f3375e606a60ba002980107d87b9fd87980ff001533305f3375e60a260ba60a260ba06c60a260ba60a260ba0782a6660be6600c6eb0c190c194c194c194c194c194c194c1740888cdd7981f182f1829182f01b99ba548000cc190cdd2a4000660c800297ae04bd700a99982f99b89004301c3756607a60ba00229445858585858c18c004c16c00458ccc8c0040048894ccc188008530103d87a80001323253330613370e0069000099ba548000cc1940092f5c0266600a00a00266e0400d20023066003306400204700e3370201e00266e0ccdc10071bad305b323376060c000260c060c20026eb0c068c160074dd6982e1919bb03060001306030610013758603460b003a2c44646600200200644a6660c000229404c8c94ccc17cc014008528899802002000983200118310008b198079bab3036305603533300e4881004881003370290000060b19ba548000cc16cc124c154148cc16cc8dd39982e182c8009982e182d000a5eb80c8cdd8182e800982e982f0009bac303530550523305b302d30550523305b32374e660b860b2002660b860b400297ae0323376060ba00260ba60bc0026eb0c060c154148cc16cc05cc154148cc16cc170c174c174c174c174c174c154148cc16cdd419b8100100b4bd700b1bad305b001305b001305a0013059001305800130570013056001304d04a375a60a600260a60046eb4c144004c1241108894ccc134cdc3800a4000297adef6c6013232330010014bd6f7b63011299982980089982a19bb0375200c6e9800d2f5bded8c0264646464a6660a866ebccc034028009300103d8798000133058337606ea4028dd30038028a99982a19b8f00a002133058337606ea4028dd300380189982c19bb037520046e98004cc01801800cdd5982a8019bae30530023057002305500132330010014bd6f7b63011299982900089982999bb037520086ea000d2f5bded8c0264646464a6660a666ebccc03002000930103d8798000133057337606ea4020dd40038028a99982999b8f008002133057337606ea4020dd400380189982b99bb037520046ea0004cc01801800cdd6982a0019bae30520023056002305400122323330010010030022225333051002100113233300400430550033333300a002375c60a00026eacc144004888c94ccc14cc02c0045300103d87a800013374a90001982b9ba60014bd7019199800800801801111299982c0010800899199802002182e001999998080011bae3057001375a60b000244464a6660b466e1c005200014c0103d87a800013374a90001982f1ba80014bd7019b8000200100f305a00200830530022533304c00114a22940894ccc124cdc80010008a6103d8798000153330493371e0040022980103d87a800014c103d87b8000222223233001001006225333050001133051337606ea4018dd4002a5eb7bdb1804c8c8c8c94ccc144cdd799803805001260103d8798000133055337606ea4028dd40048028a99982899b8f00a0021323253330533370e900000089982b99bb0375201860b060a200400a200a60a20026660100140120022660aa66ec0dd48011ba800133006006003375a60a40066eb8c140008c150008c14800488888c8cc004004018894ccc13c0044cc140cdd81ba9006374c00a97adef6c6013232323253330503375e6600e01400498103d8798000133054337606ea4028dd30048028a99982819b8f00a0021323253330523370e900000089982b19bb0375201860ae60a000400a200a60a00026660100140120022660a866ec0dd48011ba600133006006003375660a20066eb8c13c008c14c008c144004888c8ccc00400401000c8894ccc13400840044ccc00c00cc140008cc010c13c0080048ccc00800522010048810022232323253330483370e90010008a400026eb4c134c118008c118004c94ccc11ccdc3a4004002298103d87a8000132323300100100222533304d00114c103d87a8000132323232533304e3371e014004266e95200033052375000297ae0133006006003375a609e0066eb8c134008c144008c13c004dd5982618228011822800998020018011119198008008019129998240008a60103d87a800013232323253330493371e00e004266e9520003304d374c00297ae0133006006003375660940066eb8c120008c130008c1280048c118c11cc11cc11cc11c0048c114c118c118c118004c0f40dcc8c8c8c94ccc110c11c0084c8c94ccc10ccdc780101f8a99982199baf374c0029810ba14873657474696e6773010013253330443370e90021821800899191900119299982399b87480000044c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc188c1940084c8c8c8c8c8c8c9263305a0112302b0013253330653370e9000000899192999835183680109924c660b800246eb800458dd6183580098318098a99983299b874800800454ccc1a0c18c04c5261616306301232375a60cc0286eb4c19004cc098050c09c054c090058c09405c58c18c004c18c008dd6983080098308011bad305f001305f002375a60ba00260ba0046eb4c16c004c16c008dd6182c800982c801182b800982b8011919bb0305600130563057001375860aa00260aa00460a600260a600460a200260a2004609e002609e004609a002608a0042c608a002609400260840022c6032608200c2c2c6eacc110008dd718210008b182280098228009bab301c303c001301b303b304202a232533303e3370e9000000899191919299982298240010991924c64a66608866e1d2000001132325333049304c002132498c94ccc11ccdc3a400000226464a666098609e0042649318070008b182680098228010a99982399b87480080044c8c8c8c8c8c94ccc140c14c00852616375a60a200260a20046eb4c13c004c13c008dd6982680098228010b18228008b182500098210018a99982219b874800800454ccc11cc10800c52616163042002300700316304600130460023044001303c00216303c001232533303d3370e900000089919299982118228010a4c2c6eb8c10c004c0ec00854ccc0f4cdc3a400400226464a666084608a0042930b1bae3043001303b00216303b00130010012232533303c3370e900000089919299982098220010a4c2c6eb8c108004c0e800854ccc0f0cdc3a400400226464a666082608800426493198198009198030030008b1bac3042001303a0021533303c3370e9002000899192999820982200109924c6606600246600c00c0022c6eb0c108004c0e800854ccc0f0cdc3a400c002264646464a666086608c004264931981a8009198040040008b1bac30440013044002375a608400260740042a66607866e1d20080011323253330413044002149858dd69821000981d0010a99981e19b87480280044c8c94ccc104c11000852616375a608400260740042c60740026eb4c0f4004c0f4004c0f0004c0ec004c0e8008dd6981c000981c000981b8011bae3035001302d00230250013032001302a00116300130290022303030313031001302e001302600116533302a01214c103d87a800013374a900019815981600925eb80dd7181580098118008b180b1811180b181100099299981219b8748008c08c0044c8c8c94ccc09ccdc3a4000604c0022600a604a6058604a0022c64646600200200444a6660580022980103d87a800013232533302b3375e603a605200400c266e9520003302f0024bd70099802002000981800118170009bac301830243018302401e302a0013022001163001302101b2302830290013756604c002604c002604a0046eb0c08c004c08c008c084004c084004c080004c07c008dd5980e800980e800980e0011bac301a001301a0023758603000260300046eb0c058004c038004c004c03401c8c050004526136563232533300f3370e90000008991919191919299980c180d80109924c660140024646464646464a66603e60440042649319299980e99b87480000044c8c94ccc088c0940084c9263253330203370e90000008991919192999813981500109924c64a66604a66e1d20000011323232323232533302e30310021323232498c08400cc94ccc0b4cdc3a4000002264646464a666068606e0042646493181280118120018b181a800981a801181980098158028b181580219299981619b87480000044c8c8c8c94ccc0ccc0d80084c9263253330313370e900000089919299981b181c8010a4c2c6eb8c0dc004c0bc01058c0bc00c58dd6981a000981a001181900098150030b18150028b1817800981780118168009816801181580098118020b18118018b1bae302800130280023026001301e00216301e001163023001301b0041533301d3370e90010008a999810180d8020a4c2c2c60360062c6eb4c080004c080008c078004c078008dd6980e0009bac001163758603200260320046eb4c05c004c05c008dd6980a80098068040a99980799b87480080044c8c8c8c94ccc058c06400852616375a602e002602e0046eb4c054004c03402058c03401c8c94ccc03ccdc3a4000002264646464a66602c60320042649319299980a19b874800000454ccc05cc04801052616153330143370e900100089919299980c980e0010a4c2c6eb4c068004c04801054ccc050cdc3a40080022a66602e60240082930b0b18090018b19b8748008c04cdd5180b800980b801180a80098068010b1806800919299980719b87480000044c8c94ccc04cc05800852616375c602800260180042a66601c66e1d20020011323232325333015301800213232498c8c8c8c8c94ccc06cc07800852616375a603800260380046eb8c068004c06800cdd7180c0011919191919299980d180e8010a4c2c6eb4c06c004c06c008dd7180c800980c8021bae3017003163758602c002602c0046eb0c050004c03000854ccc038cdc3a400800226464a666026602c004264931919191919191919299980d980f0010a4c2c6eb4c070004c070008dd7180d000980d0019bae30180023232323232533301a301d002149858dd6980d800980d8011bae30190013019003375c602e0046eb0c04c008dd618088008b1919bb03015001301530160013758602800260180042a66601c66e1d20060011323253330133016002132498c8c8c8c8c94ccc060c06c00852616375a603200260320046eb8c05c004c05c008dd7180a8008b1bac3014001300c0021533300e3370e9004000899192999809980b00109924c6464646464646464a666036603c0042930b1bad301c001301c002375c603400260340066eb8c060008c8c8c8c8c94ccc068c07400852616375a603600260360046eb8c064004c06400cdd7180b8011bac3013002375860220022c6466ec0c054004c054c058004dd6180a00098060010b18060009119198008008019129998090008a4c26466006006602c00460066028002600200a464a66601666e1d200000113232323232323232323232323232533301c301f00213232498c8dd6980e8049bad301b00832323232375c603e0066eb8c074008c8dd7180f0019bae301c00232337606040002604060420026eb0c070030c8cdd8180f800980f98100009bac301a00b16375a603a002603a0046eb4c06c004c06c008dd6980c800980c8011919bb03018001301830190013758602e002602e0046eb4c054004c054008c8cdd8180a000980a180a8009bac30130013013002375c602200260120042c60120026018600a0026eb80048c014dd5000918019baa0015734aae7555cf2ab9f5740ae855d126011e581cf38e223a1739704ce782214d86c2dcca74573e4b9d8dcbe5a316b85e0001', + }; + + private readonly protocolFeeDefault: bigint = 1_000000n; + + constructor(requestConfig: RequestConfig = {}) { + super(); + + this.api = new SundaeSwapV3Api(this, requestConfig); + } + + public async liquidityPoolAddresses(): Promise { + return Promise.resolve([this.poolAddress]); + } + + async liquidityPools(provider: BaseDataProvider, wallet?: BaseWalletProvider): Promise { + const utxos: UTxO[] = await provider.utxos(this.poolAddress); + + return await Promise.all( + utxos.map(async (utxo: UTxO) => { + return await this.liquidityPoolFromUtxo(provider, utxo, wallet); + }) + ).then((liquidityPools: (LiquidityPool | undefined)[]) => { + return liquidityPools.filter((liquidityPool?: LiquidityPool) => { + return liquidityPool !== undefined; + }) as LiquidityPool[]; + }); + } + + public async liquidityPoolFromUtxo(provider: BaseDataProvider, utxo: UTxO, wallet?: BaseWalletProvider): Promise { + if (! utxo.datumHash) { + return Promise.resolve(undefined); + } + + const relevantAssets: AssetBalance[] = utxo.assetBalances.filter((assetBalance: AssetBalance) => { + const assetBalanceId: string = assetBalance.asset === 'lovelace' ? 'lovelace' : assetBalance.asset.identifier(); + + return !assetBalanceId.startsWith(this.lpTokenPolicyId); + }); + + // Irrelevant UTxO + if (![2, 3].includes(relevantAssets.length)) { + return Promise.resolve(undefined); + } + + // Could be ADA/X or X/X pool + const assetAIndex: number = relevantAssets.length === 2 ? 0 : 1; + const assetBIndex: number = relevantAssets.length === 2 ? 1 : 2; + + try { + const builder: DefinitionBuilder = await new DefinitionBuilder().loadDefinition(pool); + const datum: DefinitionField = await provider.datumValue(utxo.datumHash); + const parameters: DatumParameters = builder.pullParameters(datum as DefinitionConstr); + + const reservesA: bigint = relevantAssets[assetAIndex].asset === 'lovelace' + ? relevantAssets[assetAIndex].quantity - BigInt((parameters.ProtocolFee ?? 0) as number) + : relevantAssets[assetAIndex].quantity; + const reservesB: bigint = relevantAssets[assetBIndex].asset === 'lovelace' + ? relevantAssets[assetBIndex].quantity - BigInt((parameters.ProtocolFee ?? 0) as number) + : relevantAssets[assetBIndex].quantity; + + const liquidityPool: LiquidityPool = new LiquidityPool( + SundaeSwapV3.identifier, + relevantAssets[assetAIndex].asset, + relevantAssets[assetBIndex].asset, + reservesA, + reservesB, + utxo.address, + '', + '' + ); + + const lpToken: Asset = utxo.assetBalances.find((assetBalance) => { + return assetBalance.asset !== 'lovelace' && assetBalance.asset.policyId === this.lpTokenPolicyId; + })?.asset as Asset; + + if (lpToken) { + lpToken.nameHex = '0014df1' + lpToken.nameHex.substr(7); + liquidityPool.lpToken = lpToken; + liquidityPool.identifier = lpToken.identifier(); + } + + liquidityPool.lpToken = lpToken; + liquidityPool.identifier = typeof parameters.PoolIdentifier === 'string' ? parameters.PoolIdentifier : ''; + liquidityPool.poolFeePercent = typeof parameters.OpeningFee === 'number' ? (parameters.OpeningFee / 10_000) * 100 : 0; + liquidityPool.totalLpTokens = typeof parameters.TotalLpTokens === 'number' ? BigInt(parameters.TotalLpTokens) : 0n; + liquidityPool.extra.protocolFee = typeof parameters.ProtocolFee === 'number' ? parameters.ProtocolFee : this.protocolFeeDefault; + + return liquidityPool; + } catch (e) { + return undefined; + } + } + + estimatedGive(liquidityPool: LiquidityPool, swapOutToken: Token, swapOutAmount: bigint): bigint { + const [reserveOut, reserveIn]: bigint[] = correspondingReserves(liquidityPool, swapOutToken); + + const receive: bigint = (reserveIn * reserveOut) / (reserveOut - swapOutAmount) - reserveIn; + const swapFee: bigint = (receive * BigInt(Math.floor(liquidityPool.poolFeePercent * 100)) + BigInt(10000) - 1n) / 10000n; + + return receive + swapFee; + } + + estimatedReceive(liquidityPool: LiquidityPool, swapInToken: Token, swapInAmount: bigint): bigint { + const [reserveIn, reserveOut]: bigint[] = correspondingReserves(liquidityPool, swapInToken); + + const swapFee: bigint = (swapInAmount * BigInt(Math.floor(liquidityPool.poolFeePercent * 100)) + BigInt(10000) - 1n) / 10000n; + + return reserveOut - (reserveIn * reserveOut) / (reserveIn + swapInAmount - swapFee); + } + + priceImpactPercent(liquidityPool: LiquidityPool, swapInToken: Token, swapInAmount: bigint): number { + const reserveIn: bigint = tokensMatch(swapInToken, liquidityPool.assetA) ? liquidityPool.reserveA : liquidityPool.reserveB; + + return (1 - Number(reserveIn) / Number(reserveIn + swapInAmount)) * 100; + } + + public async buildSwapOrder(liquidityPool: LiquidityPool, swapParameters: DatumParameters, spendUtxos: SpendUTxO[] = []): Promise { + const protocolFee: SwapFee | undefined = this.swapOrderFees().find((fee: SwapFee) => fee.id === 'protocolFee'); + const deposit: SwapFee | undefined = this.swapOrderFees().find((fee: SwapFee) => fee.id === 'deposit'); + + if (!protocolFee || !deposit) { + return Promise.reject('Parameters for datum are not set.'); + } + + swapParameters = { + ...swapParameters, + [DatumParameterKey.ProtocolFee]: protocolFee.value, + [DatumParameterKey.CancelDatum]: this.cancelDatum, + }; + + const datumBuilder: DefinitionBuilder = new DefinitionBuilder(); + await datumBuilder.loadDefinition(order).then((builder: DefinitionBuilder) => { + builder.pushParameters(swapParameters); + }); + + return [ + this.buildSwapOrderPayment(swapParameters, { + address: lucidUtils.credentialToAddress( + { + type: 'Script', + hash: this.orderScriptHash, + }, + { + type: 'Key', + hash: swapParameters.SenderStakingKeyHash as string, + }, + ), + addressType: AddressType.Contract, + assetBalances: [ + { + asset: 'lovelace', + quantity: this.protocolFeeDefault + deposit.value, + }, + ], + datum: datumBuilder.getCbor(), + isInlineDatum: true, + spendUtxos: spendUtxos, + }), + ]; + } + + public async buildCancelSwapOrder(txOutputs: UTxO[], returnAddress: string, wallet?: BaseWalletProvider): Promise { + if (!wallet) { + return Promise.reject('Need wallet connected in order to generate cancel order address.'); + } + + const relevantUtxo: UTxO | undefined = txOutputs.find((utxo: UTxO) => { + const addressDetails: AddressDetails | undefined = lucidUtils.getAddressDetails(utxo.address); + + return (addressDetails.paymentCredential?.hash ?? '') === this.orderScriptHash; + }); + + if (!relevantUtxo) { + return Promise.reject('Unable to find relevant UTxO for cancelling the swap order.'); + } + + return [ + { + address: returnAddress, + addressType: AddressType.Base, + assetBalances: relevantUtxo.assetBalances, + isInlineDatum: true, + spendUtxos: [ + { + utxo: relevantUtxo, + redeemer: this.cancelDatum, + validator: this.orderScript, + signer: returnAddress, + }, + ], + }, + ]; + } + + public swapOrderFees(): SwapFee[] { + return [ + { + id: 'protocolFee', + title: 'Sundae Protocol Fee', + description: 'Sundae Protocol Fee', + value: this.protocolFeeDefault, + isReturned: false, + }, + { + id: 'deposit', + title: 'Deposit', + description: 'A small ADA deposit that you will get back when your order is processed or cancelled.', + value: 2_000000n, + isReturned: true, + }, + ]; + } + +} diff --git a/src/dexter.ts b/src/dexter.ts index cb5b856..aac5feb 100644 --- a/src/dexter.ts +++ b/src/dexter.ts @@ -1,7 +1,7 @@ import { BaseDataProvider } from '@providers/data/base-data-provider'; import { AvailableDexs, DexterConfig, RequestConfig } from '@app/types'; import { Minswap } from '@dex/minswap'; -import { SundaeSwap } from '@dex/sundaeswap'; +import { SundaeSwapV1 } from '@dex/sundaeswap-v1'; import { MuesliSwap } from '@dex/muesliswap'; import { WingRiders } from '@dex/wingriders'; import { SwapRequest } from '@requests/swap-request'; @@ -12,12 +12,13 @@ import { BaseMetadataProvider } from '@providers/asset-metadata/base-metadata-pr import { TokenRegistryProvider } from '@providers/asset-metadata/token-registry-provider'; import { CancelSwapRequest } from '@requests/cancel-swap-request'; import { FetchRequest } from '@requests/fetch-request'; -import axios from "axios"; -import axiosRetry from "axios-retry"; +import axios from 'axios'; +import axiosRetry from 'axios-retry'; import { SplitSwapRequest } from '@requests/split-swap-request'; import { TeddySwap } from '@dex/teddyswap'; import { Spectrum } from '@dex/spectrum'; import { SplitCancelSwapRequest } from '@requests/split-cancel-swap-request'; +import { SundaeSwapV3 } from '@dex/sundaeswap-v3'; import { MinswapV2 } from '@dex/minswap-v2'; export class Dexter { @@ -59,8 +60,9 @@ export class Dexter { this.metadataProvider = new TokenRegistryProvider(this.requestConfig); this.availableDexs = { [Minswap.identifier]: new Minswap(this.requestConfig), + [SundaeSwapV1.identifier]: new SundaeSwapV1(this.requestConfig), + [SundaeSwapV3.identifier]: new SundaeSwapV3(this.requestConfig), [MinswapV2.identifier]: new MinswapV2(this.requestConfig), - [SundaeSwap.identifier]: new SundaeSwap(this.requestConfig), [MuesliSwap.identifier]: new MuesliSwap(this.requestConfig), [WingRiders.identifier]: new WingRiders(this.requestConfig), [VyFinance.identifier]: new VyFinance(this.requestConfig), diff --git a/src/index.ts b/src/index.ts index 3be17d1..e9b7b50 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,7 +41,8 @@ export * from './dex/models/dex-transaction'; export * from './dex/base-dex'; export * from './dex/minswap'; export * from './dex/minswap-v2'; -export * from './dex/sundaeswap'; +export * from './dex/sundaeswap-v1'; +export * from './dex/sundaeswap-v3'; export * from './dex/muesliswap'; export * from './dex/wingriders'; export * from './dex/vyfinance'; diff --git a/src/types.ts b/src/types.ts index 04568c2..2c3f67a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,154 +5,170 @@ import { LiquidityPool } from '@dex/models/liquidity-pool'; import { Script } from 'lucid-cardano'; export interface DexterConfig { - shouldFetchMetadata?: boolean, - shouldFallbackToApi?: boolean, - shouldSubmitOrders?: boolean, - metadataMsgBranding?: string, + shouldFetchMetadata?: boolean; + shouldFallbackToApi?: boolean; + shouldSubmitOrders?: boolean; + metadataMsgBranding?: string; } export interface RequestConfig { - timeout?: number, - proxyUrl?: string, - retries?: number, + timeout?: number; + proxyUrl?: string; + retries?: number; } export interface BlockfrostConfig { - url: string, - projectId: string, + url: string; + projectId: string; } export interface KupoConfig { - url: string, + url: string; } export interface KupmiosConfig { - kupoUrl: string, - ogmiosUrl: string, + kupoUrl: string; + ogmiosUrl: string; } export type AvailableDexs = { - [dex: string]: BaseDex, -} + [dex: string]: BaseDex; +}; export type DatumParameters = { - [key in DatumParameterKey | string]?: string | number | bigint -} + [key in DatumParameterKey | string]?: string | number | bigint; +}; export type AssetBalance = { - asset: Token, - quantity: bigint, -} + asset: Token; + quantity: bigint; +}; export type UTxO = { - txHash: string, - address: string, - datumHash: string, - datum?: string, - outputIndex: number, - assetBalances: AssetBalance[], + txHash: string; + address: string; + datumHash: string; + datum?: string; + outputIndex: number; + assetBalances: AssetBalance[]; }; export type Transaction = { - hash: string, - inputs: UTxO[], - outputs: UTxO[], + hash: string; + inputs: UTxO[]; + outputs: UTxO[]; }; export type AssetAddress = { - address: string, - quantity: bigint, -} + address: string; + quantity: bigint; +}; export type DefinitionBytes = { - bytes: string | DatumParameterKey, -} + bytes: string | DatumParameterKey; +}; export type DefinitionInt = { - int: number | DatumParameterKey, -} + int: number | DatumParameterKey; +}; -export type DefinitionField = DefinitionConstr | DefinitionBytes | DefinitionInt | DefinitionField[] | Function +export type DefinitionList = { + list: DefinitionField[] | DefinitionList[]; +}; + +export type DefinitionField = DefinitionConstr | DefinitionBytes | DefinitionInt | DefinitionList| Function | DefinitionField[]; export type DefinitionConstr = { - constructor: number | DatumParameterKey, - fields: DefinitionField[], -} + constructor: number | DatumParameterKey; + fields: DefinitionField[]; +}; export type WalletOptions = { - addressType?: AddressType, - accountIndex?: number, -} + addressType?: AddressType; + accountIndex?: number; +}; export type SpendUTxO = { - utxo: UTxO, - redeemer?: string, - validator?: Script, - signer?: string, + utxo: UTxO; + redeemer?: string; + validator?: Script; + signer?: string; }; export type PayToAddress = { - address: string, - addressType: AddressType, - assetBalances: AssetBalance[], - spendUtxos?: SpendUTxO[], - datum?: string, - isInlineDatum: boolean, + address: string; + addressType: AddressType; + assetBalances: AssetBalance[]; + spendUtxos?: SpendUTxO[]; + datum?: string; + isInlineDatum: boolean; }; export type SwapFee = { - id: string, - title: string, - description: string, - value: bigint, - isReturned: boolean, + id: string; + title: string; + description: string; + value: bigint; + isReturned: boolean; }; export type SwapInAmountMapping = { - swapInAmount: bigint, - liquidityPool: LiquidityPool, -} + swapInAmount: bigint; + liquidityPool: LiquidityPool; +}; export type SwapOutAmountMapping = { - swapOutAmount: bigint, - liquidityPool: LiquidityPool, -} + swapOutAmount: bigint; + liquidityPool: LiquidityPool; +}; export type SplitCancelSwapMapping = { - txHash: string, - dex: string, -} + txHash: string; + dex: string; +}; export type DexTransactionError = { - step: TransactionStatus, - reason: string, - reasonRaw: string, + step: TransactionStatus; + reason: string; + reasonRaw: string; }; export type AssetMetadata = { - policyId: string, - nameHex: string, - decimals: number, + policyId: string; + nameHex: string; + decimals: number; }; export type Cip30Api = { - getNetworkId(): Promise; - getUtxos(): Promise; - getBalance(): Promise; - getUsedAddresses(): Promise; - getUnusedAddresses(): Promise; - getChangeAddress(): Promise; - getRewardAddresses(): Promise; - signTx(tx: string, partialSign: boolean): Promise; - signData(address: string, payload: string): Promise<{ - signature: string; - key: string; - }>; - submitTx(tx: string): Promise; + getNetworkId(): Promise; + getUtxos(): Promise; + getBalance(): Promise; + getUsedAddresses(): Promise; + getUnusedAddresses(): Promise; + getChangeAddress(): Promise; + getRewardAddresses(): Promise; + signTx(tx: string, partialSign: boolean): Promise; + signData( + address: string, + payload: string + ): Promise<{ + signature: string; + key: string; + }>; + submitTx(tx: string): Promise; + getCollateral(): Promise; + experimental: { getCollateral(): Promise; - experimental: { - getCollateral(): Promise; - on(eventName: string, callback: (...args: unknown[]) => void): void; - off(eventName: string, callback: (...args: unknown[]) => void): void; - }; + on(eventName: string, callback: (...args: unknown[]) => void): void; + off(eventName: string, callback: (...args: unknown[]) => void): void; + }; }; + +export type DatumJson = { + int?: number; + bytes?: string; + list?: Array; + map?: Array<{ k: unknown; v: unknown }>; + fields?: Array; + [constructor: string]: unknown; +}; \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 125d464..af1979f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import { Token } from '@dex/models/asset'; import { LiquidityPool } from '@dex/models/liquidity-pool'; -import { Lucid, Utils } from 'lucid-cardano'; +import { C, Datum, fromHex, Lucid, toHex, Utils } from 'lucid-cardano'; +import { DatumJson } from '@app/types'; export const lucidUtils: Utils = new Utils(new Lucid()); @@ -23,3 +24,42 @@ export function appendSlash(value?: string) { return `${value}/`; } + +/** + * Modified version from lucid + */ +export function datumJsonToCbor(json: DatumJson): Datum { + const convert = (json: DatumJson): C.PlutusData => { + if (!isNaN(json.int!)) { + return C.PlutusData.new_integer(C.BigInt.from_str(json.int!.toString())); + } else if (json.bytes || !isNaN(Number(json.bytes))) { + return C.PlutusData.new_bytes(fromHex(json.bytes!)); + } else if (json.map) { + const l = C.PlutusList.new(); + (json as any).forEach((v: DatumJson) => { + l.add(convert(v)); + }); + return C.PlutusData.new_list(l); + } else if (json.list) { + const l = C.PlutusList.new(); + json.list.forEach((v: DatumJson) => { + l.add(convert(v)); + }); + return C.PlutusData.new_list(l); + } else if (!isNaN(json.constructor! as unknown as number)) { + const l = C.PlutusList.new(); + json.fields!.forEach((v: DatumJson) => { + l.add(convert(v)); + }); + return C.PlutusData.new_constr_plutus_data( + C.ConstrPlutusData.new( + C.BigNum.from_str(json.constructor!.toString()), + l, + ), + ); + } + throw new Error("Unsupported type"); + }; + + return toHex(convert(json).to_bytes()); +} \ No newline at end of file diff --git a/tests/sundaeswap.test.ts b/tests/sundaeswap-v1.test.ts similarity index 95% rename from tests/sundaeswap.test.ts rename to tests/sundaeswap-v1.test.ts index 35d20ac..8791557 100644 --- a/tests/sundaeswap.test.ts +++ b/tests/sundaeswap-v1.test.ts @@ -2,7 +2,7 @@ import { Asset, Dexter, LiquidityPool, - SundaeSwap, + SundaeSwapV1, MockDataProvider, SwapRequest, MockWalletProvider, @@ -13,7 +13,7 @@ import { UTxO, } from '../src'; -describe('SundaeSwap', () => { +describe('SundaeSwapV1', () => { const walletProvider: MockWalletProvider = new MockWalletProvider(); walletProvider.loadWalletFromSeedPhrase(['']); @@ -25,7 +25,7 @@ describe('SundaeSwap', () => { describe('Set Swap In', () => { const liquidityPool: LiquidityPool = new LiquidityPool( - SundaeSwap.identifier, + SundaeSwapV1.identifier, 'lovelace', asset, 3699642000000n, @@ -48,7 +48,7 @@ describe('SundaeSwap', () => { }); it('Can build swap order', () => { - const sundaeswap: SundaeSwap = new SundaeSwap(); + const sundaeswap: SundaeSwapV1 = new SundaeSwapV1(); const defaultSwapParameters: DatumParameters = { [DatumParameterKey.PoolIdentifier]: '1234', [DatumParameterKey.SenderPubKeyHash]: walletProvider.publicKeyHash(), @@ -77,7 +77,7 @@ describe('SundaeSwap', () => { describe('Set Swap Out', () => { const liquidityPool: LiquidityPool = new LiquidityPool( - SundaeSwap.identifier, + SundaeSwapV1.identifier, 'lovelace', asset, 1032791394311n, @@ -99,10 +99,11 @@ describe('SundaeSwap', () => { }); describe('SundaeSwap Cancel Order', () => { - let sundaeswap: SundaeSwap; + let sundaeswap: SundaeSwapV1; const returnAddress = 'addr1'; + beforeEach(() => { - sundaeswap = new SundaeSwap(); + sundaeswap = new SundaeSwapV1(); }); it('should successfully cancel an order', async () => {