diff --git a/CHANGELOG.md b/CHANGELOG.md
index cdf19da..f24b800 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
All notable changes to Dexter will be documented in this file.
+## [v5.4.9]
+- Splash integration
+
## [v5.4.0]
- SundaeSwap v3 integration
diff --git a/README.md b/README.md
index 8f787b2..c845837 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,7 @@
-
-
+
### What You Can Do
diff --git a/package-lock.json b/package-lock.json
index 0686198..0441087 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,23 @@
{
"name": "@indigo-labs/dexter",
- "version": "5.4.2",
+ "version": "5.4.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@indigo-labs/dexter",
- "version": "5.4.2",
+ "version": "5.4.8",
"license": "MIT",
"dependencies": {
+ "@types/blake2b": "^2.1.3",
"@types/crypto-js": "^4.1.1",
"axios": "^0.26.1",
"axios-retry": "^3.5.1",
+ "blake2b": "^2.1.4",
"bottleneck": "^2.19.5",
"crypto-js": "^4.1.1",
+ "int64-buffer": "^1.0.1",
+ "js-encoding-utils": "^0.7.3",
"lodash": "^4.17.21",
"lucid-cardano": "^0.10.9"
},
@@ -2698,6 +2702,11 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/blake2b": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@types/blake2b/-/blake2b-2.1.3.tgz",
+ "integrity": "sha512-MFCdX0MNxFBP/xEILO5Td0kv6nI7+Q2iRWZbTL/yzH2/eDVZS5Wd1LHdsmXClvsCyzqaZfHFzZaN6BUeUCfSDA=="
+ },
"node_modules/@types/crypto-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
@@ -2915,6 +2924,11 @@
"is-retry-allowed": "^2.2.0"
}
},
+ "node_modules/b4a": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
+ "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg=="
+ },
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -3136,6 +3150,24 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/blake2b": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.4.tgz",
+ "integrity": "sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==",
+ "dependencies": {
+ "blake2b-wasm": "^2.4.0",
+ "nanoassert": "^2.0.0"
+ }
+ },
+ "node_modules/blake2b-wasm": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/blake2b-wasm/-/blake2b-wasm-2.4.0.tgz",
+ "integrity": "sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==",
+ "dependencies": {
+ "b4a": "^1.0.1",
+ "nanoassert": "^2.0.0"
+ }
+ },
"node_modules/bottleneck": {
"version": "2.19.5",
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
@@ -4119,6 +4151,14 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
+ "node_modules/int64-buffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-1.0.1.tgz",
+ "integrity": "sha512-+3azY4pXrjAupJHU1V9uGERWlhoqNswJNji6aD/02xac7oxol508AsMC5lxKhEqyZeDFy3enq5OGWXF4u75hiw==",
+ "engines": {
+ "node": ">= 4.5.0"
+ }
+ },
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -5960,6 +6000,11 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/js-encoding-utils": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/js-encoding-utils/-/js-encoding-utils-0.7.3.tgz",
+ "integrity": "sha512-cfjcyPOzkZ2esMAi6eAjuto7GiT6YpPan5xIeQyN/CFqFHTt1sdqP0PJPgzi3HqAqXKN9j9hduynkgwk+AAJOw=="
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6194,6 +6239,11 @@
"url": "https://github.com/sponsors/raouldeheer"
}
},
+ "node_modules/nanoassert": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
+ "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
diff --git a/package.json b/package.json
index 044721a..ec6e1df 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@indigo-labs/dexter",
- "version": "5.4.8",
+ "version": "5.4.9",
"license": "MIT",
"author": "Zachary Sluder",
"keywords": [
@@ -22,11 +22,15 @@
"prepublishOnly": "npm run test"
},
"dependencies": {
+ "@types/blake2b": "^2.1.3",
"@types/crypto-js": "^4.1.1",
"axios": "^0.26.1",
"axios-retry": "^3.5.1",
+ "blake2b": "^2.1.4",
"bottleneck": "^2.19.5",
"crypto-js": "^4.1.1",
+ "int64-buffer": "^1.0.1",
+ "js-encoding-utils": "^0.7.3",
"lodash": "^4.17.21",
"lucid-cardano": "^0.10.9"
},
diff --git a/src/constants.ts b/src/constants.ts
index e3722dd..c502033 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -18,6 +18,7 @@ export enum DatumParameterKey {
/**
* Swap/wallet info.
*/
+ Address = 'Address',
SenderPubKeyHash = 'SenderPubKeyHash',
SenderStakingKeyHash = 'SenderStakingKeyHash',
SenderKeyHashes = 'SenderKeyHashes',
@@ -32,6 +33,10 @@ export enum DatumParameterKey {
Expiration = 'Expiration',
AllowPartialFill = 'AllowPartialFill',
Direction = 'Direction',
+ FeePaymentKeyHash = 'FeePaymentKeyHash',
+ Beacon = 'Beacon',
+ Batcher = 'Batcher',
+ InToken = 'InToken',
/**
* Trading fees.
@@ -41,6 +46,7 @@ export enum DatumParameterKey {
DepositFee = 'DepositFee',
ScooperFee = 'ScooperFee',
BaseFee = 'BaseFee',
+ ExecutionFee = 'ExecutionFee',
FeeSharingNumerator = 'FeeSharingNumerator',
OpeningFee = 'OpeningFee',
FinalFee = 'FinalFee',
diff --git a/src/dex/api/spectrum-api.ts b/src/dex/api/spectrum-api.ts
deleted file mode 100644
index 7c27a31..0000000
--- a/src/dex/api/spectrum-api.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-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, tokensMatch } from '@app/utils';
-import { Spectrum } from '@dex/spectrum';
-
-export class SpectrumApi extends BaseApi {
-
- protected readonly api: AxiosInstance;
- protected readonly dex: Spectrum;
-
- constructor(dex: Spectrum, requestConfig: RequestConfig) {
- super();
-
- this.dex = dex;
- this.api = axios.create({
- timeout: requestConfig.timeout,
- baseURL: `${appendSlash(requestConfig.proxyUrl)}https://analytics-balanced.spectrum.fi/cardano`,
- headers: {
- 'Content-Type': 'application/json',
- }
- });
- }
-
- liquidityPools(assetA: Token, assetB?: Token): Promise {
- return this.api.get('/front/pools', ).then((response: any) => {
- return response.data.map((poolResponse: any) => {
- const tokenA: Token = poolResponse.lockedX.asset.currencySymbol !== ''
- ? new Asset(poolResponse.lockedX.asset.currencySymbol, Buffer.from(poolResponse.lockedX.asset.tokenName, 'utf8').toString('hex'))
- : 'lovelace';
- const tokenB: Token = poolResponse.lockedY.asset.currencySymbol !== ''
- ? new Asset(poolResponse.lockedY.asset.currencySymbol, Buffer.from(poolResponse.lockedY.asset.tokenName, 'utf8').toString('hex'))
- : 'lovelace';
-
- if (! tokensMatch(tokenA, assetA) || (assetB && ! tokensMatch(tokenB, assetB))) {
- return undefined;
- }
-
- let liquidityPool: LiquidityPool = new LiquidityPool(
- Spectrum.identifier,
- tokenA,
- tokenB,
- BigInt(poolResponse.lockedX.amount),
- BigInt(poolResponse.lockedY.amount),
- '', // Not supplied
- this.dex.orderAddress,
- this.dex.orderAddress,
- );
-
- const [poolNftPolicyId, poolNftName] = poolResponse.id.split('.');
- liquidityPool.poolNft = new Asset(poolNftPolicyId, Buffer.from(poolNftName, 'utf8').toString('hex'));
- liquidityPool.lpToken = new Asset(poolResponse.lockedLQ.asset.currencySymbol, Buffer.from(poolResponse.lockedLQ.asset.tokenName, 'utf8').toString('hex'));
- liquidityPool.poolFeePercent = (1 - (poolResponse.poolFeeNum / poolResponse.poolFeeDenum)) * 100;
- liquidityPool.identifier = liquidityPool.lpToken.identifier();
-
- return liquidityPool;
- }).filter((pool: LiquidityPool | undefined) => pool !== undefined) as LiquidityPool[];
- });
- }
-
-}
diff --git a/src/dex/api/splash-api.ts b/src/dex/api/splash-api.ts
new file mode 100644
index 0000000..7a3d0d2
--- /dev/null
+++ b/src/dex/api/splash-api.ts
@@ -0,0 +1,77 @@
+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 { Splash } from '@dex/splash';
+
+const MAX_INT: bigint = 9_223_372_036_854_775_807n;
+
+export class SplashApi extends BaseApi {
+
+ protected readonly api: AxiosInstance;
+ protected readonly dex: Splash;
+
+ constructor(dex: Splash, requestConfig: RequestConfig) {
+ super();
+
+ this.dex = dex;
+
+ this.api = axios.create({
+ timeout: requestConfig.timeout,
+ baseURL: `${appendSlash(requestConfig.proxyUrl)}https://api5.splash.trade/platform-api/v1/`,
+ withCredentials: false,
+ });
+ }
+
+ async liquidityPools(assetA: Token, assetB?: Token): Promise {
+ const assets: any = (await this.assets()).data['tokens'];
+
+ return this.api.get('/pools/overview?verified=false&duplicated=false').then((response: any) => {
+ return response.data.map((pool: any) => this.liquidityPoolFromResponse(pool, assets)) as LiquidityPool[];
+ });
+ }
+
+ private liquidityPoolFromResponse(poolData: any, assets: any): LiquidityPool {
+ poolData = poolData.pool;
+
+ const tokenA: Token = poolData.x.asset === '.'
+ ? 'lovelace'
+ : new Asset(poolData.x.asset.split('.')[0], poolData.x.asset.split('.')[1]);
+ const tokenB = poolData.y.asset === '.'
+ ? 'lovelace'
+ : new Asset(poolData.y.asset.split('.')[0], poolData.y.asset.split('.')[1]);
+
+ if (tokenA !== 'lovelace' && tokenA.identifier('.') in assets) {
+ tokenA.decimals = assets[tokenA.identifier('.')].decimals;
+ }
+ if (tokenB !== 'lovelace' && tokenB.identifier('.') in assets) {
+ tokenB.decimals = assets[tokenB.identifier('.')].decimals;
+ }
+
+ const liquidityPool: LiquidityPool = new LiquidityPool(
+ Splash.identifier,
+ tokenA,
+ tokenB,
+ BigInt(poolData['x']['amount']) - BigInt(poolData['treasuryX']),
+ BigInt(poolData['y']['amount']) - BigInt(poolData['treasuryY']),
+ '',
+ '',
+ '',
+ );
+
+ const [lpTokenPolicyId, lpTokenAssetName] = poolData['lq']['asset'].split('.');
+
+ liquidityPool.lpToken = new Asset(lpTokenPolicyId, lpTokenAssetName);
+ liquidityPool.totalLpTokens = MAX_INT - BigInt(poolData['lq']['amount']);
+ liquidityPool.identifier = poolData['id'];
+
+ return liquidityPool;
+ }
+
+ private assets(): Promise {
+ return axios.get('https://spectrum.fi/cardano-token-list-v2.json');
+ }
+
+}
diff --git a/src/dex/api/teddyswap-api.ts b/src/dex/api/teddyswap-api.ts
deleted file mode 100644
index 51fb920..0000000
--- a/src/dex/api/teddyswap-api.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-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, tokensMatch } from '@app/utils';
-import { TeddySwap } from '@dex/teddyswap';
-
-export class TeddyswapApi extends BaseApi {
-
- protected readonly api: AxiosInstance;
- protected readonly dex: TeddySwap;
-
- constructor(dex: TeddySwap, requestConfig: RequestConfig) {
- super();
-
- this.dex = dex;
- this.api = axios.create({
- timeout: requestConfig.timeout,
- baseURL: `${appendSlash(requestConfig.proxyUrl)}https://analytics.teddyswap.org/v1`,
- headers: {
- 'Content-Type': 'application/json',
- }
- });
- }
-
- liquidityPools(assetA: Token, assetB?: Token): Promise {
- return this.api.get('/front/pools', ).then((response: any) => {
- return response.data.map((poolResponse: any) => {
- const tokenA: Token = poolResponse.lockedX.asset.currencySymbol !== ''
- ? new Asset(poolResponse.lockedX.asset.currencySymbol, Buffer.from(poolResponse.lockedX.asset.tokenName, 'utf8').toString('hex'))
- : 'lovelace';
- const tokenB: Token = poolResponse.lockedY.asset.currencySymbol !== ''
- ? new Asset(poolResponse.lockedY.asset.currencySymbol, Buffer.from(poolResponse.lockedY.asset.tokenName, 'utf8').toString('hex'))
- : 'lovelace';
-
- if (! tokensMatch(tokenA, assetA) || (assetB && ! tokensMatch(tokenB, assetB))) {
- return undefined;
- }
-
- let liquidityPool: LiquidityPool = new LiquidityPool(
- TeddySwap.identifier,
- tokenA,
- tokenB,
- BigInt(poolResponse.lockedX.amount),
- BigInt(poolResponse.lockedY.amount),
- '', // Not supplied
- this.dex.orderAddress,
- this.dex.orderAddress,
- );
- const [poolNftPolicyId, poolNftName] = poolResponse.id.split('.');
- liquidityPool.poolNft = new Asset(poolNftPolicyId, Buffer.from(poolNftName, 'utf8').toString('hex'));
- liquidityPool.lpToken = new Asset(poolResponse.lockedLQ.asset.currencySymbol, Buffer.from(poolResponse.lockedLQ.asset.tokenName, 'utf8').toString('hex'));
- liquidityPool.poolFeePercent = (1 - (poolResponse.poolFeeNum / poolResponse.poolFeeDenum)) * 100;
- liquidityPool.identifier = liquidityPool.lpToken.identifier();
-
- return liquidityPool;
- }).filter((pool: LiquidityPool | undefined) => pool !== undefined) as LiquidityPool[];
- });
- }
-
-}
diff --git a/src/dex/base-dex.ts b/src/dex/base-dex.ts
index 955410d..d990f2c 100644
--- a/src/dex/base-dex.ts
+++ b/src/dex/base-dex.ts
@@ -47,7 +47,7 @@ export abstract class BaseDex {
/**
* Craft a swap order for this DEX.
*/
- abstract buildSwapOrder(liquidityPool: LiquidityPool, swapParameters: DatumParameters, spendUtxos?: SpendUTxO[]): Promise;
+ abstract buildSwapOrder(liquidityPool: LiquidityPool, swapParameters: DatumParameters, spendUtxos?: SpendUTxO[], dataProvider?: BaseDataProvider): Promise;
/**
* Craft a swap order cancellation for this DEX.
diff --git a/src/dex/definitions/spectrum/order.ts b/src/dex/definitions/spectrum/order.ts
deleted file mode 100644
index 2b25d08..0000000
--- a/src/dex/definitions/spectrum/order.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { DatumParameterKey } from '@app/constants';
-import { DatumParameters, DefinitionField } from '@app/types';
-
-export default {
- constructor: 0,
- fields: [
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.SwapInTokenPolicyId
- },
- {
- bytes: DatumParameterKey.SwapInTokenAssetName
- }
- ],
- },
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.SwapOutTokenPolicyId
- },
- {
- bytes: DatumParameterKey.SwapOutTokenAssetName
- }
- ],
- },
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.TokenPolicyId // Pool NFT
- },
- {
- bytes: DatumParameterKey.TokenAssetName
- }
- ],
- },
- {
- int: DatumParameterKey.LpFee
- },
- {
- int: DatumParameterKey.LpFeeNumerator // Execution fee numerator
- },
- {
- int: DatumParameterKey.LpFeeDenominator // Execution fee denominator
- },
- {
- bytes: DatumParameterKey.SenderPubKeyHash
- },
- (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
- if (! shouldExtract) {
- const stakeKeyHash: string = parameters[DatumParameterKey.SenderStakingKeyHash] as string ?? null;
-
- if (! stakeKeyHash) return;
-
- return {
- constructor: 0,
- fields: [
- {
- bytes: stakeKeyHash,
- }
- ],
- };
- }
-
- if ('fields' in field) {
- if (field.constructor === 1) return;
-
- if (field.fields.length > 0 && 'bytes' in field.fields[0]) {
- parameters[DatumParameterKey.SenderStakingKeyHash] = field.fields[0].bytes;
- }
- }
-
- return;
- },
- {
- int: DatumParameterKey.SwapInAmount
- },
- {
- int: DatumParameterKey.MinReceive
- }
- ],
-}
diff --git a/src/dex/definitions/spectrum/pool.ts b/src/dex/definitions/spectrum/pool.ts
deleted file mode 100644
index e59b246..0000000
--- a/src/dex/definitions/spectrum/pool.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { DatumParameterKey } from '@app/constants';
-
-export default {
- constructor: 0,
- fields: [
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.TokenPolicyId // Pool NFT
- },
- {
- bytes: DatumParameterKey.TokenAssetName
- }
- ]
- },
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.PoolAssetAPolicyId
- },
- {
- bytes: DatumParameterKey.PoolAssetAAssetName
- }
- ]
- },
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.PoolAssetBPolicyId
- },
- {
- bytes: DatumParameterKey.PoolAssetBAssetName
- }
- ]
- },
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.LpTokenPolicyId
- },
- {
- bytes: DatumParameterKey.LpTokenAssetName
- }
- ]
- },
- {
- int: DatumParameterKey.LpFee
- },
- [
- {
- bytes: DatumParameterKey.StakeAdminPolicy
- }
- ],
- {
- int: DatumParameterKey.LqBound
- }
- ]
-}
diff --git a/src/dex/definitions/splash/order.ts b/src/dex/definitions/splash/order.ts
new file mode 100644
index 0000000..c7bf6a8
--- /dev/null
+++ b/src/dex/definitions/splash/order.ts
@@ -0,0 +1,116 @@
+import { DatumParameterKey } from '@app/constants';
+import { DatumParameters, DefinitionField } from '@app/types';
+
+export default {
+ constructor: 0,
+ fields: [
+ {
+ bytes: DatumParameterKey.Action
+ },
+ {
+ bytes: DatumParameterKey.Beacon
+ },
+ {
+ constructor: 0,
+ fields: [
+ {
+ bytes: DatumParameterKey.SwapInTokenPolicyId
+ },
+ {
+ bytes: DatumParameterKey.SwapInTokenAssetName
+ }
+ ]
+ },
+ {
+ int: DatumParameterKey.SwapInAmount
+ },
+ {
+ int: DatumParameterKey.BaseFee
+ },
+ {
+ int: DatumParameterKey.MinReceive
+ },
+ {
+ constructor: 0,
+ fields: [
+ {
+ bytes: DatumParameterKey.SwapOutTokenPolicyId
+ },
+ {
+ bytes: DatumParameterKey.SwapOutTokenAssetName
+ }
+ ]
+ },
+ {
+ constructor: 0,
+ fields: [
+ {
+ int: DatumParameterKey.LpFeeNumerator,
+ },
+ {
+ int: DatumParameterKey.LpFeeDenominator,
+ }
+ ]
+ },
+ {
+ int: DatumParameterKey.ExecutionFee
+ },
+ {
+ constructor: 0,
+ fields: [
+ {
+ constructor: 0,
+ fields: [
+ {
+ bytes: DatumParameterKey.SenderPubKeyHash
+ }
+ ]
+ },
+ {
+ constructor: 0,
+ fields: [
+ {
+ constructor: 0,
+ fields: [
+ (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
+ if (! shouldExtract) {
+ const stakeKeyHash: string = parameters[DatumParameterKey.SenderStakingKeyHash] as string ?? null;
+
+ if (! stakeKeyHash) return;
+
+ return {
+ constructor: 0,
+ fields: [
+ {
+ bytes: stakeKeyHash,
+ }
+ ],
+ };
+ }
+
+ if ('fields' in field) {
+ if (field.constructor === 1) return;
+
+ if (field.fields.length > 0 && 'bytes' in field.fields[0]) {
+ parameters[DatumParameterKey.SenderStakingKeyHash] = field.fields[0].bytes;
+ }
+ }
+
+ return;
+ },
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ bytes: DatumParameterKey.SenderPubKeyHash
+ },
+ [
+ {
+ bytes: DatumParameterKey.Batcher
+ }
+ ]
+ ]
+}
diff --git a/src/dex/definitions/teddyswap/pool.ts b/src/dex/definitions/splash/pool.ts
similarity index 71%
rename from src/dex/definitions/teddyswap/pool.ts
rename to src/dex/definitions/splash/pool.ts
index e59b246..5237f6e 100644
--- a/src/dex/definitions/teddyswap/pool.ts
+++ b/src/dex/definitions/splash/pool.ts
@@ -1,4 +1,5 @@
import { DatumParameterKey } from '@app/constants';
+import { DatumParameters, DefinitionField } from '@app/types';
export default {
constructor: 0,
@@ -48,15 +49,19 @@ export default {
]
},
{
- int: DatumParameterKey.LpFee
+ int: DatumParameterKey.Unknown
},
- [
- {
- bytes: DatumParameterKey.StakeAdminPolicy
- }
- ],
{
- int: DatumParameterKey.LqBound
- }
+ int: DatumParameterKey.Unknown
+ },
+ {
+ int: DatumParameterKey.PoolAssetATreasury
+ },
+ {
+ int: DatumParameterKey.PoolAssetBTreasury
+ },
+ (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
+ return;
+ },
]
}
diff --git a/src/dex/definitions/teddyswap/order.ts b/src/dex/definitions/teddyswap/order.ts
deleted file mode 100644
index 2b25d08..0000000
--- a/src/dex/definitions/teddyswap/order.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { DatumParameterKey } from '@app/constants';
-import { DatumParameters, DefinitionField } from '@app/types';
-
-export default {
- constructor: 0,
- fields: [
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.SwapInTokenPolicyId
- },
- {
- bytes: DatumParameterKey.SwapInTokenAssetName
- }
- ],
- },
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.SwapOutTokenPolicyId
- },
- {
- bytes: DatumParameterKey.SwapOutTokenAssetName
- }
- ],
- },
- {
- constructor: 0,
- fields: [
- {
- bytes: DatumParameterKey.TokenPolicyId // Pool NFT
- },
- {
- bytes: DatumParameterKey.TokenAssetName
- }
- ],
- },
- {
- int: DatumParameterKey.LpFee
- },
- {
- int: DatumParameterKey.LpFeeNumerator // Execution fee numerator
- },
- {
- int: DatumParameterKey.LpFeeDenominator // Execution fee denominator
- },
- {
- bytes: DatumParameterKey.SenderPubKeyHash
- },
- (field: DefinitionField, parameters: DatumParameters, shouldExtract: boolean = true) => {
- if (! shouldExtract) {
- const stakeKeyHash: string = parameters[DatumParameterKey.SenderStakingKeyHash] as string ?? null;
-
- if (! stakeKeyHash) return;
-
- return {
- constructor: 0,
- fields: [
- {
- bytes: stakeKeyHash,
- }
- ],
- };
- }
-
- if ('fields' in field) {
- if (field.constructor === 1) return;
-
- if (field.fields.length > 0 && 'bytes' in field.fields[0]) {
- parameters[DatumParameterKey.SenderStakingKeyHash] = field.fields[0].bytes;
- }
- }
-
- return;
- },
- {
- int: DatumParameterKey.SwapInAmount
- },
- {
- int: DatumParameterKey.MinReceive
- }
- ],
-}
diff --git a/src/dex/logo/spectrum.png b/src/dex/logo/spectrum.png
deleted file mode 100644
index c63b757..0000000
Binary files a/src/dex/logo/spectrum.png and /dev/null differ
diff --git a/src/dex/logo/splash.png b/src/dex/logo/splash.png
new file mode 100644
index 0000000..ae883bb
Binary files /dev/null and b/src/dex/logo/splash.png differ
diff --git a/src/dex/logo/teddyswap.png b/src/dex/logo/teddyswap.png
deleted file mode 100644
index 2b2118e..0000000
Binary files a/src/dex/logo/teddyswap.png and /dev/null differ
diff --git a/src/dex/spectrum.ts b/src/dex/splash.ts
similarity index 54%
rename from src/dex/spectrum.ts
rename to src/dex/splash.ts
index 1ec3910..b54a665 100644
--- a/src/dex/spectrum.ts
+++ b/src/dex/splash.ts
@@ -15,39 +15,53 @@ import {
import { DefinitionBuilder } from '@app/definition-builder';
import { AddressType, DatumParameterKey } from '@app/constants';
import { BaseApi } from '@dex/api/base-api';
-import pool from './definitions/spectrum/pool';
-import order from './definitions/spectrum/order';
-import { correspondingReserves, tokensMatch } from '@app/utils';
-import { SpectrumApi } from '@dex/api/spectrum-api';
-import { Script } from 'lucid-cardano';
+import pool from '@dex/definitions/splash/pool';
+import order from '@dex/definitions/splash/order';
+import { bytesToHex, correspondingReserves, hexToBytes, lucidUtils, tokensMatch } from '@app/utils';
+import { AddressDetails, Script } from 'lucid-cardano';
+import { Uint64BE } from 'int64-buffer';
+import blake2b from 'blake2b';
+import { SplashApi } from '@dex/api/splash-api';
const MAX_INT: bigint = 9_223_372_036_854_775_807n;
+const EXECUTOR_FEE: bigint = 1100000n;
+const WORST_ORDER_STEP_COST: bigint = 900000n;
-export class Spectrum extends BaseDex {
+export class Splash extends BaseDex {
- public static readonly identifier: string = 'Spectrum';
- public readonly api: BaseApi;
+ public static readonly identifier: string = 'Splash';
+ readonly api: BaseApi;
/**
* On-Chain constants.
*/
- public readonly orderAddress: string = 'addr1wynp362vmvr8jtc946d3a3utqgclfdl5y9d3kn849e359hsskr20n';
- public readonly cancelDatum: string = 'd8799f00000001ff';
+ public readonly cancelDatum: string = 'd87980';
+ public readonly orderScriptHash: string = '464eeee89f05aff787d40045af2a40a83fd96c513197d32fbc54ff02';
+ public readonly batcherKey: string = '5cb2c968e5d1c7197a6ce7615967310a375545d9bc65063a964335b2';
public readonly orderScript: Script = {
type: 'PlutusV2',
- script: '5904f901000032323232323232323232323232323232323232323222253330143232323232323232323232323232323232323232323232323232323253330303370e90010010991919299981999b8753330333370e6eb4c0d0c0d403d2000148000520024800054cc090cdc399814805181a00e240042a66048664466ebcdd3981c0011ba730380013034004303400815330243370e666064444a666060002200426600666e00009200230380014800004d20041533024337126eb4c0d0c0d405800854cc0900044cccc8888cdc499b833370466e08cc0b403800c008004cdc019b823302d00e004483403ccdc100100099b8000648008c0d0078c0d0074dd6981a00b1bad303401b13322332303522533303200114a02a66607066ebcc0e400400c52889801181d0009ba90010023758606864606c606c606c606c606c002606a0286eb8c0d00614cc8cc0cc00452899191929981319b8f375c606c606e0046eb8c0d8c0dc0044cdc79bae3036002375c606c002606e04e606c002606603826666644444646466e24cdc099b81302900d375a00266e0ccdc10028020019814809a99981c191929981599b8f375c607660780046eb8c0ecc0f00044cdc79bae303b002375c60760026078058607600c26ea00144004dd424000606603a6eb4c0cc054004dd6981980c9bad303301853330313232325330253371e6eb8c0d4c0d8008dd7181a981b000899b8f375c606a0046eb8c0d4004c0d8098c0d4004c0c806c4cdc199b82001375a606402e66e04dd6981900b9bad30320181001337026604c01460620346604c00860620342c606600460540026ea8c0b8c0bc064dd599181718179818000981698170009817000998139bad302b00700a37566460566058605a002605460560026056002660486eb4c0a001401ccc88c8c8c94ccc0acc8c8c94ccc0b8cdc3a40040042646464a66606266e1d200000214a0266ebcdd38021ba70013034002302b001375400e2646464a66606266e1d200200214a0266ebcdd38021ba70013034002302b001375400e606200460500026ea8004400858c8c8c8c8c94ccc0bccdc3a4000004264646464a66606666e1d200000213232323253330373370e90010010b099ba548000004c0e8008c0c4004dd5000981a0008b181b00118168009baa001303000113374a9001015181900118148009baa001302c302d302e001302b302d0053756646464a66605866e1d2002002161533302c3371e6eb8c0b40040184c0b4c0b8c0bc01c58c0bc008c098004dd50009918151816000981498158019bae302700b302700a33022375a604c002008604c002604a002604a0206eb0c088008dd61810801181098108009810980f805180f800980f000980e800980e000980d800980d000980c800980c000980c002180b8008a4c2c4a66601600229000099980899baf300d3012001375200c6eb4c054c048dd5980a9809000a4000446660220040020062940cdd2a4000660026ea4008cc004dd48010042ba0489002232333004003375c601c0026eb8c038c03c004c03c004888cccc01000920002333300500248001d69bab00100323002375200244446601444a66600e002200a2a66601a66ebcc024c0380040184c010c044c0380044c008c03c00400555cfa5eb8155ce91299980299b8800248000584cc00c008004c0048894ccc014cdc3801240002600c00226600666e04009200230070012323002233002002001230022330020020015734ae855d1118011baa0015573c1',
+ script: '59042d01000033232323232323222323232232253330093232533300b0041323300100137566022602460246024602460246024601c6ea8008894ccc040004528099299980719baf00d300f301300214a226600600600260260022646464a66601c6014601e6ea80044c94ccc03cc030c040dd5000899191929998090038a99980900108008a5014a066ebcc020c04cdd5001180b180b980b980b980b980b980b980b980b980b98099baa00f3375e600860246ea8c010c048dd5180a98091baa00230043012375400260286eb0c050c054c054c044dd50028b1991191980080080191299980a8008a60103d87a80001323253330143375e6016602c6ea80080144cdd2a40006603000497ae0133004004001301900230170013758600a60206ea8010c04cc040dd50008b180098079baa0052301230130013322323300100100322533301200114a0264a66602066e3cdd7180a8010020a5113300300300130150013758602060226022602260226022602260226022601a6ea8004dd71808180898089808980898089808980898089808980898069baa0093001300c37540044601e00229309b2b19299980598050008a999804180218048008a51153330083005300900114a02c2c6ea8004c8c94ccc01cc010c020dd50028991919191919191919191919191919191919191919191919299981118128010991919191924c646600200200c44a6660500022930991980180198160011bae302a0015333022301f30233754010264646464a666052605800426464931929998141812800899192999816981800109924c64a666056605000226464a66606060660042649318140008b181880098169baa0021533302b3027001132323232323253330343037002149858dd6981a800981a8011bad30330013033002375a6062002605a6ea800858c0acdd50008b181700098151baa0031533302830240011533302b302a37540062930b0b18141baa002302100316302a001302a0023028001302437540102ca666042603c60446ea802c4c8c8c8c94ccc0a0c0ac00852616375a605200260520046eb4c09c004c08cdd50058b180d006180c8098b1bac30230013023002375c60420026042004603e002603e0046eb4c074004c074008c06c004c06c008c064004c064008dd6980b800980b8011bad30150013015002375a60260026026004602200260220046eb8c03c004c03c008dd7180680098049baa0051625333007300430083754002264646464a66601c60220042930b1bae300f001300f002375c601a00260126ea8004588c94ccc01cc0100044c8c94ccc030c03c00852616375c601a00260126ea800854ccc01cc00c0044c8c94ccc030c03c00852616375c601a00260126ea800858c01cdd50009b8748008dc3a4000ae6955ceaab9e5573eae815d0aba24c0126d8799fd87a9f581c96f5c1bee23481335ff4aece32fe1dfa1aa40a944a66d2d6edc9a9a5ffff0001',
};
constructor(requestConfig: RequestConfig = {}) {
super();
- this.api = new SpectrumApi(this, requestConfig);
+ this.api = new SplashApi(this, requestConfig);
}
public async liquidityPoolAddresses(provider: BaseDataProvider): Promise {
return Promise.resolve([
'addr1x94ec3t25egvhqy2n265xfhq882jxhkknurfe9ny4rl9k6dj764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrst84slu',
'addr1x8nz307k3sr60gu0e47cmajssy4fmld7u493a4xztjrll0aj764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrswgxsta',
+ 'addr1x8xw6pmmy8jcnpss6sg7za9c5lk2v9nflq684vzxyn70unaj764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrs4tm5z7',
+ 'addr1x8cq97k066w4rd37wprvd4qrfxctzlyd6a67us2uv6hnen9j764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrsgzvahe',
+ 'addr1xxcdveqw6g88w6cvwkf705xw30gflshu79ljc3ysrmmluadj764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrscak26z',
+ 'addr1x8mql508pa9emlqfeh0g6lmlzfmauf55eq49zmta8ny7q04j764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrs08z9dt',
+ 'addr1xxw7upjedpkr4wq839wf983jsnq3yg40l4cskzd7dy8eyndj764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrsgddq74',
+ 'addr1x8zjsd5fagcwpysv2zklwu69kkqfcpwfvtxpz8s0r5kmakaj764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrszgx7ef',
+ 'addr1x92m92cttwgpllls5y4c889splwgujjyy0eccl424nlezm9j764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrswdesh2',
+ 'addr1xxg94wrfjcdsjncmsxtj0r87zk69e0jfl28n934sznu95tdj764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrs2993lw',
+ 'addr1x9wnm7vle7al9q4aw63aw63wxz7aytnpc4h3gcjy0yufxwaj764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrs84l0h4',
]);
}
@@ -80,7 +94,6 @@ export class Spectrum extends BaseDex {
const relevantAssets = utxo.assetBalances.filter((assetBalance: AssetBalance) => {
const assetName = assetBalance.asset === 'lovelace' ? 'lovelace' : assetBalance.asset.assetName;
return !assetName?.toLowerCase()?.endsWith('_nft')
- && !assetName?.toLowerCase()?.endsWith('_identity')
&& !assetName?.toLowerCase()?.endsWith('_lq');
});
@@ -89,26 +102,27 @@ export class Spectrum extends BaseDex {
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;
- const liquidityPool: LiquidityPool = new LiquidityPool(
- Spectrum.identifier,
- relevantAssets[assetAIndex].asset,
- relevantAssets[assetBIndex].asset,
- relevantAssets[assetAIndex].quantity,
- relevantAssets[assetBIndex].quantity,
- utxo.address,
- this.orderAddress,
- this.orderAddress
- );
-
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 liquidityPool: LiquidityPool = new LiquidityPool(
+ Splash.identifier,
+ parameters.PoolAssetAPolicyId === ''
+ ? 'lovelace'
+ : new Asset(parameters.PoolAssetAPolicyId as string, parameters.PoolAssetAAssetName as string),
+ new Asset(parameters.PoolAssetBPolicyId as string, parameters.PoolAssetBAssetName as string),
+ relevantAssets[assetAIndex].quantity - BigInt(parameters.PoolAssetATreasury as number),
+ relevantAssets[assetBIndex].quantity - BigInt(parameters.PoolAssetBTreasury as number),
+ utxo.address,
+ '',
+ '',
+ );
+
const [lpTokenPolicyId, lpTokenAssetName] = typeof parameters.LpTokenPolicyId === 'string' && typeof parameters.LpTokenAssetName === 'string'
? [parameters.LpTokenPolicyId, parameters.LpTokenAssetName]
: [null, null];
@@ -131,27 +145,22 @@ export class Spectrum extends BaseDex {
liquidityPool.identifier = liquidityPool.lpToken.identifier();
liquidityPool.poolFeePercent = typeof parameters.LpFee === 'number' ? (1000 - parameters.LpFee) / 10 : 0.3;
} catch (e) {
- return liquidityPool;
+ return undefined;
}
- return liquidityPool;
+ 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;
+ return (reserveIn * reserveOut) / (reserveOut - swapOutAmount) - reserveIn;
}
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);
+ return reserveOut - (reserveIn * reserveOut) / (reserveIn + swapInAmount);
}
priceImpactPercent(liquidityPool: LiquidityPool, swapInToken: Token, swapInAmount: bigint): number {
@@ -162,7 +171,7 @@ export class Spectrum extends BaseDex {
return (1 - (Number(reserveIn) / Number(reserveIn + swapInAmount))) * 100;
}
- public async buildSwapOrder(liquidityPool: LiquidityPool, swapParameters: DatumParameters, spendUtxos: SpendUTxO[] = []): Promise {
+ public async buildSwapOrder(liquidityPool: LiquidityPool, swapParameters: DatumParameters, spendUtxos: SpendUTxO[] = [], dataProvider?: BaseDataProvider): Promise {
const batcherFee: SwapFee | undefined = this.swapOrderFees().find((fee: SwapFee) => fee.id === 'batcherFee');
const deposit: SwapFee | undefined = this.swapOrderFees().find((fee: SwapFee) => fee.id === 'deposit');
const minReceive = swapParameters.MinReceive as bigint;
@@ -170,10 +179,18 @@ export class Spectrum extends BaseDex {
if (! batcherFee || ! deposit || ! minReceive) {
return Promise.reject('Parameters for datum are not set.');
}
- if (! liquidityPool.poolNft) {
- return Promise.reject('Pool NFT is required.');
+ if (! dataProvider) {
+ return Promise.reject('Data provider is required.');
}
+ const walletUtxos: UTxO[] = await dataProvider.utxos(
+ swapParameters[DatumParameterKey.Address] as string,
+ swapParameters[DatumParameterKey.SwapInTokenPolicyId] !== ''
+ ? new Asset(swapParameters.SwapInTokenPolicyId as string, swapParameters.SwapInTokenAssetName as string)
+ : undefined
+ );
+ const firstUtxo: UTxO = walletUtxos[0];
+
const decimalToFractionalImproved = (decimalValue: bigint | number): [bigint, bigint] => {
const [whole, decimals = ''] = decimalValue.toString()?.split('.');
let truncatedDecimals = decimals.slice(0, 15);
@@ -182,17 +199,24 @@ export class Spectrum extends BaseDex {
return [numerator, denominator];
}
- const batcherFeeForToken = Number(batcherFee.value) / Number(minReceive);
- const [numerator, denominator] = decimalToFractionalImproved(batcherFeeForToken);
- const lpfee: bigint = BigInt(1000 - Math.floor(liquidityPool.poolFeePercent * 10));
+ const swapOutToken: Token = swapParameters.SwapOutTokenPolicyId === 'lovelace'
+ ? 'lovelace'
+ : new Asset(swapParameters.SwapOutTokenPolicyId as string, swapParameters.SwapOutTokenAssetName as string);
+
+ const outDecimals: number = swapOutToken === 'lovelace'
+ ? 6
+ : (tokensMatch(swapOutToken, liquidityPool.assetA)) ? (liquidityPool.assetA as Asset).decimals : (liquidityPool.assetB as Asset).decimals;
+ const [numerator, denominator] = decimalToFractionalImproved(Number(minReceive) / 10**outDecimals);
swapParameters = {
...swapParameters,
- [DatumParameterKey.TokenPolicyId]: liquidityPool.poolNft.policyId,
- [DatumParameterKey.TokenAssetName]: liquidityPool.poolNft.nameHex,
- [DatumParameterKey.LpFee]: lpfee,
+ [DatumParameterKey.Action]: '00',
+ [DatumParameterKey.BaseFee]: WORST_ORDER_STEP_COST,
+ [DatumParameterKey.ExecutionFee]: EXECUTOR_FEE,
[DatumParameterKey.LpFeeNumerator]: numerator,
[DatumParameterKey.LpFeeDenominator]: denominator,
+ [DatumParameterKey.Beacon]: bytesToHex(Uint8Array.from(new Array(28).fill(0))),
+ [DatumParameterKey.Batcher]: this.batcherKey,
};
const datumBuilder: DefinitionBuilder = new DefinitionBuilder();
@@ -200,9 +224,26 @@ export class Spectrum extends BaseDex {
builder.pushParameters(swapParameters);
});
+ const hash: string = blake2b(28).update(hexToBytes(datumBuilder.getCbor())).digest('hex');
+
+ swapParameters.Beacon = this.getBeacon(firstUtxo, hash);
+
+ await datumBuilder.loadDefinition(order).then((builder: DefinitionBuilder) => {
+ builder.pushParameters(swapParameters);
+ });
+
return [
this.buildSwapOrderPayment(swapParameters, {
- address: this.orderAddress,
+ address: lucidUtils.credentialToAddress(
+ {
+ type: 'Script',
+ hash: this.orderScriptHash,
+ },
+ {
+ type: 'Key',
+ hash: swapParameters.SenderStakingKeyHash as string,
+ },
+ ),
addressType: AddressType.Contract,
assetBalances: [
{
@@ -212,14 +253,16 @@ export class Spectrum extends BaseDex {
],
datum: datumBuilder.getCbor(),
isInlineDatum: true,
- spendUtxos: spendUtxos,
+ spendUtxos: spendUtxos.concat({ utxo: firstUtxo }),
}),
];
}
public async buildCancelSwapOrder(txOutputs: UTxO[], returnAddress: string): Promise {
const relevantUtxo: UTxO | undefined = txOutputs.find((utxo: UTxO) => {
- return utxo.address === this.orderAddress;
+ const addressDetails: AddressDetails | undefined = lucidUtils.getAddressDetails(utxo.address);
+
+ return (addressDetails.paymentCredential?.hash ?? '') === this.orderScriptHash;
});
if (! relevantUtxo) {
@@ -266,4 +309,15 @@ export class Spectrum extends BaseDex {
},
];
}
+
+ private getBeacon(utxo: UTxO, datumHash: string) {
+ return blake2b(28).update(
+ Uint8Array.from([
+ ...hexToBytes(utxo.txHash),
+ ...new Uint64BE(Number(utxo.outputIndex)).toArray(),
+ ...new Uint64BE(0).toArray(),
+ ...hexToBytes(datumHash),
+ ])
+ ).digest('hex');
+ }
}
diff --git a/src/dex/teddyswap.ts b/src/dex/teddyswap.ts
deleted file mode 100644
index f818c8d..0000000
--- a/src/dex/teddyswap.ts
+++ /dev/null
@@ -1,280 +0,0 @@
-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 { AddressType, DatumParameterKey } from '@app/constants';
-import { BaseApi } from '@dex/api/base-api';
-import pool from './definitions/teddyswap/pool';
-import order from './definitions/teddyswap/order';
-import { correspondingReserves, tokensMatch } from '@app/utils';
-import { TeddyswapApi } from '@dex/api/teddyswap-api';
-import { Script } from 'lucid-cardano';
-
-const MAX_INT: bigint = 9_223_372_036_854_775_807n;
-
-export class TeddySwap extends BaseDex {
-
- public static readonly identifier: string = 'TeddySwap';
- public readonly api: BaseApi;
-
- /**
- * On-Chain constants.
- */
- public readonly orderAddress: string = 'addr1z99tz7hungv6furtdl3zn72sree86wtghlcr4jc637r2eadkp2avt5gp297dnxhxcmy6kkptepsr5pa409qa7gf8stzs0706a3';
- public readonly cancelDatum: string = 'd8799f00000001ff';
- public readonly orderScript: Script = {
- type: 'PlutusV2',
- script: '5905090100003232323232323232323232323232323232323232323222253330153232323232323232323232323232323232323232323232323232323253330313370e90010010991919299812a99981a19b8753330343370e6eb4c0d4c0d803d2000148000520024800054cc094cdc399815005181a80e240042a6604a664466ebcdd3981c8011ba730390013035004303500815330253370e666066444a666062002200426600666e00009200230390014800004d20041533025337126eb4c0d4c0d805800854cc0940044cccc8888cdc499b833370466e08cc0b803800c008004cdc019b823302e00e004483403ccdc100100099b8000648008c0d4078c0d4074dd6981a80b1bad303501b13322332303622533303300114a02a66607266ebcc0e800400c52889801181d8009ba90010023758606a64606e606e606e606e606e002606c0286eb8c0d40604cdc3811811a9991981a0008a513232325330273371e6eb8c0dcc0e0008dd7181b981c000899b8f375c606e0046eb8c0dc004c0e00a0c0dc004c0d00704ccccc88888c8c8cdc499b8133702605401a6eb4004cdc199b82005004003302a0135333039323253302c3371e6eb8c0f0c0f4008dd7181e181e800899b8f375c60780046eb8c0f0004c0f40b4c0f00184dd400288009ba848000c0d0074dd6981a00a8009bad3034019375a6068030a666064646464a6604c66e3cdd7181b181b8011bae3036303700113371e6eb8c0d8008dd7181b000981b813981b000981980d899b83337040026eb4c0cc05ccdc09bad3033017375a6066030200266e04cc09c028c0c8068cc09c010c0c806858c0d0008c0ac004dd51817981800c9bab32302f30303031001302e302f001302f00133028375a605800e0146eacc8c0b0c0b4c0b8004c0acc0b0004c0b0004cc094dd6981480280399911919192999816191919299981799b87480080084c8c8c94ccc0c8cdc3a400000429404cdd79ba7004374e002606a00460580026ea801c4c8c8c94ccc0c8cdc3a400400429404cdd79ba7004374e002606a00460580026ea801cc0c8008c0a4004dd500088010b1919191919299981819b87480000084c8c8c8c94ccc0d0cdc3a4000004264646464a66607066e1d20020021613374a9000000981d80118190009baa0013035001163037002302e00137540026062002266e95200202b3033002302a0013754002605a605c605e0026058605c00a6eacc8c8c94ccc0b4cdc3a40040042c2a66605a66e3cdd7181700080309817181798180038b181800118138009baa00132302b302d001302a302c003375c60500166050014660466eb4c09c004010c09c004c098004c098040dd618118011bac3022002302230220013022302000a3020001301f001301e001301d001301c001301b001301a00130190013019004301800114985920aca597c9282533300b0011480004ccc044cdd7980698090009ba9006375a602a60246eacc054c04800520002233301100200100314a066e952000330013752004660026ea40080215d0245002232333004003375c601c0026eb8c038c03c004c03c004888cccc01000920002333300500248001d69bab00100323002375200244446601444a66600e002200a2a66601a66ebcc024c0380040184c010c044c0380044c008c03c00400555cfa5eb8155ce91299980299b8800248000584cc00c008004c0048894ccc014cdc3801240002600c00226600666e04009200230070012323002233002002001230022330020020015734ae855d1118011baa0015573c1',
- };
-
- constructor(requestConfig: RequestConfig = {}) {
- super();
-
- this.api = new TeddyswapApi(this, requestConfig);
- }
-
- public async liquidityPoolAddresses(provider: BaseDataProvider): Promise {
- return Promise.resolve([
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zp5hu6t748dfdd6cxlxxssyqez4wqwcrq44crfgkltqh2cqcwcjyr',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zqgdzhkv23nm3v7tanurzu8v5vll365n7hq8f26937hatlqnv5cpz',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8z8rxrld450e6c360mu72ru7u8zz0602px3esxykcx87f9ns2tytsd',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zz6mve63ntrqp7yxgkk395rngtzdmzdjzzuzdkdks0afwqsmdsegq',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zr0vp2360e2j2gve54sxsheawjd6s6we2d25xl96a3r0jdqzvyqkl',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zzlsgmhduch9juwcjf6vjqeht0jv2g2mlz86wqh42h8akdqglnguu',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zxk96389hhwyhv0t07gh89wqnaqg9cqkwsz4esd9sm562rs55tl66',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8z9re630pc4dzmhtku8276tyq0glgn53h93vw5rl9e6w4g8su86xvk',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zpczswng09euafg44jclrg5tm7xg260qzyavu9dysz8g3js7pzqla',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8z92v2k4gz85r5rq035n2llzemqvcz70h7hdr3njur05y6nsmrsjpe',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zxn5qy8sn2d7wtdtvjcsv7v0h7u9zsleljxv3nschr5sj3sla73t7',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zzw03em2wpuy6t66rx4hqmggelr8r2whwru8uuptxzwdlfsss26rc',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zphr7r6v67asj5jc5w5uapfapv0u9433m3v9aag9w46spaqc60ygw',
- 'addr1zy5th50h46anh3v7zdvh7ve6amac7k4h3mdfvt0p6czm8zrlxa5g3cwp6thfvzwhd9s4vcjjdwttsss65l09dum7g9rs0mr8px',
- ]);
- }
-
- async liquidityPools(provider: BaseDataProvider): Promise {
- const poolAddresses: string[] = await this.liquidityPoolAddresses(provider);
-
- const addressPromises: Promise[] = poolAddresses.map(async (address: string) => {
- const utxos: UTxO[] = await provider.utxos(address);
-
- return await Promise.all(
- utxos.map(async (utxo: UTxO) => {
- return await this.liquidityPoolFromUtxo(provider, utxo);
- })
- ).then((liquidityPools: (LiquidityPool | undefined)[]) => {
- return liquidityPools.filter((liquidityPool?: LiquidityPool) => {
- return liquidityPool !== undefined;
- }) as LiquidityPool[];
- });
-
- });
-
- return Promise.all(addressPromises).then((liquidityPools: Awaited[]) => liquidityPools.flat());
- }
-
- public async liquidityPoolFromUtxo(provider: BaseDataProvider, utxo: UTxO): Promise {
- if (!utxo.datumHash) {
- return Promise.resolve(undefined);
- }
-
- const relevantAssets = utxo.assetBalances.filter((assetBalance: AssetBalance) => {
- const assetName = assetBalance.asset === 'lovelace' ? 'lovelace' : assetBalance.asset.assetName;
- return !assetName?.toLowerCase()?.endsWith('_nft')
- && !assetName?.toLowerCase()?.endsWith('_identity')
- && !assetName?.toLowerCase()?.endsWith('_lp');
- });
-
- // 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;
-
- const liquidityPool: LiquidityPool = new LiquidityPool(
- TeddySwap.identifier,
- relevantAssets[assetAIndex].asset,
- relevantAssets[assetBIndex].asset,
- relevantAssets[assetAIndex].quantity,
- relevantAssets[assetBIndex].quantity,
- utxo.address,
- this.orderAddress,
- this.orderAddress
- );
-
- 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 [lpTokenPolicyId, lpTokenAssetName] = typeof parameters.LpTokenPolicyId === 'string' && typeof parameters.LpTokenAssetName === 'string'
- ? [parameters.LpTokenPolicyId, parameters.LpTokenAssetName]
- : [null, null];
- const lpTokenBalance: AssetBalance | undefined = utxo.assetBalances.find((assetBalance: AssetBalance) => {
- return assetBalance.asset !== 'lovelace'
- && assetBalance.asset.policyId === lpTokenPolicyId
- && assetBalance.asset.nameHex === lpTokenAssetName;
- });const nftToken: Asset | undefined = utxo.assetBalances.find((assetBalance) => {
- return (assetBalance.asset as Asset).assetName?.toLowerCase()?.endsWith('_identity');
- })?.asset as Asset | undefined;
-
- if (! lpTokenBalance || ! nftToken) {
- return Promise.resolve(undefined);
- }
-
- liquidityPool.poolNft = nftToken;
- liquidityPool.lpToken = lpTokenBalance.asset as Asset;
- liquidityPool.totalLpTokens = MAX_INT - lpTokenBalance.quantity;
- liquidityPool.identifier = liquidityPool.lpToken.identifier();
- liquidityPool.poolFeePercent = typeof parameters.LpFee === 'number' ? (1000 - parameters.LpFee) / 10 : 0.3;
- } catch (e) {
- return liquidityPool;
- }
-
- return liquidityPool;
- }
-
- 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 batcherFee: SwapFee | undefined = this.swapOrderFees().find((fee: SwapFee) => fee.id === 'batcherFee');
- const deposit: SwapFee | undefined = this.swapOrderFees().find((fee: SwapFee) => fee.id === 'deposit');
- const minReceive = swapParameters.MinReceive as bigint;
-
- if (!batcherFee || !deposit || !minReceive) {
- return Promise.reject('Parameters for datum are not set.');
- }
- if (! liquidityPool.poolNft) {
- return Promise.reject('Pool NFT is required.');
- }
-
- const decimalToFractionalImproved = (decimalValue: bigint | number): [bigint, bigint] => {
- const [whole, decimals = ''] = decimalValue.toString()?.split('.');
- let truncatedDecimals = decimals.slice(0, 15);
- const denominator: bigint = BigInt(10 ** truncatedDecimals.length);
- const numerator = BigInt(whole) * denominator + BigInt(decimals);
- return [numerator, denominator];
- }
-
- const batcherFeeForToken = Number(batcherFee.value) / Number(minReceive);
- const [numerator, denominator] = decimalToFractionalImproved(batcherFeeForToken);
- const lpfee: bigint = BigInt(1000 - Math.floor(liquidityPool.poolFeePercent * 10));
-
- swapParameters = {
- ...swapParameters,
- [DatumParameterKey.TokenPolicyId]: liquidityPool.poolNft.policyId,
- [DatumParameterKey.TokenAssetName]: liquidityPool.poolNft.nameHex,
- [DatumParameterKey.LpFee]: lpfee,
- [DatumParameterKey.LpFeeNumerator]: numerator,
- [DatumParameterKey.LpFeeDenominator]: denominator,
- };
-
- const datumBuilder: DefinitionBuilder = new DefinitionBuilder();
- await datumBuilder.loadDefinition(order).then((builder: DefinitionBuilder) => {
- builder.pushParameters(swapParameters);
- });
-
- return [
- this.buildSwapOrderPayment(swapParameters, {
- address: this.orderAddress,
- addressType: AddressType.Contract,
- assetBalances: [
- {
- asset: 'lovelace',
- quantity: batcherFee?.value + deposit.value,
- },
- ],
- datum: datumBuilder.getCbor(),
- isInlineDatum: true,
- spendUtxos: spendUtxos,
- }),
- ];
- }
-
- public async buildCancelSwapOrder(txOutputs: UTxO[], returnAddress: string): Promise {
- const relevantUtxo: UTxO | undefined = txOutputs.find((utxo: UTxO) => {
- return utxo.address === this.orderAddress;
- });
-
- 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: false,
- spendUtxos: [{
- utxo: relevantUtxo,
- redeemer: this.cancelDatum,
- validator: this.orderScript,
- signer: returnAddress,
- }],
- }
- ];
- }
-
- public swapOrderFees(): SwapFee[] {
- const networkFee: number = 0.5;
- const reward: number = 1;
- const minNitro: number = 1.2;
- const batcherFee: number = (reward + networkFee) * minNitro;
- const batcherFeeInAda: bigint = BigInt(Math.round(batcherFee * 10 ** 6));
-
- return [
- {
- id: 'batcherFee',
- title: 'Batcher Fee',
- description: 'Fee paid for the service of off-chain batcher to process transactions.',
- value: batcherFeeInAda,
- isReturned: false,
- },
- {
- id: 'deposit',
- title: 'Deposit',
- description: 'This amount of ADA will be held as minimum UTxO ADA and will be returned when your order is processed or cancelled.',
- value: 2_000000n,
- isReturned: true,
- },
- ];
- }
-}
diff --git a/src/dexter.ts b/src/dexter.ts
index 69b6e6f..b1bcd42 100644
--- a/src/dexter.ts
+++ b/src/dexter.ts
@@ -15,12 +15,11 @@ import { FetchRequest } from '@requests/fetch-request';
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';
import { WingRidersV2 } from '@dex/wingriders-v2';
+import { Splash } from '@dex/splash';
export class Dexter {
@@ -68,8 +67,7 @@ export class Dexter {
[WingRiders.identifier]: new WingRiders(this.requestConfig),
[WingRidersV2.identifier]: new WingRidersV2(this.requestConfig),
[VyFinance.identifier]: new VyFinance(this.requestConfig),
- [TeddySwap.identifier]: new TeddySwap(this.requestConfig),
- [Spectrum.identifier]: new Spectrum(this.requestConfig),
+ [Splash.identifier]: new Splash(this.requestConfig),
};
}
diff --git a/src/index.ts b/src/index.ts
index c3554fd..f75b32c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -47,5 +47,4 @@ export * from './dex/muesliswap';
export * from './dex/wingriders';
export * from './dex/wingriders-v2';
export * from './dex/vyfinance';
-export * from './dex/teddyswap';
-export * from './dex/spectrum';
+export * from './dex/splash';
diff --git a/src/requests/swap-request.ts b/src/requests/swap-request.ts
index 087a9cb..0e38905 100644
--- a/src/requests/swap-request.ts
+++ b/src/requests/swap-request.ts
@@ -225,6 +225,7 @@ export class SwapRequest {
// Standard parameters for a swap order
const defaultSwapParameters: DatumParameters = {
+ [DatumParameterKey.Address]: this._dexter.walletProvider.address(),
[DatumParameterKey.SenderPubKeyHash]: this._dexter.walletProvider.publicKeyHash(),
[DatumParameterKey.SenderStakingKeyHash]: this._dexter.walletProvider.stakingKeyHash(),
[DatumParameterKey.ReceiverPubKeyHash]: this._dexter.walletProvider.publicKeyHash(),
@@ -246,7 +247,8 @@ export class SwapRequest {
return {
utxo,
}
- }) as SpendUTxO[]
+ }) as SpendUTxO[],
+ this._dexter.dataProvider
);
}
diff --git a/src/utils.ts b/src/utils.ts
index af1979f..a80b97f 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -2,6 +2,7 @@ import { Token } from '@dex/models/asset';
import { LiquidityPool } from '@dex/models/liquidity-pool';
import { C, Datum, fromHex, Lucid, toHex, Utils } from 'lucid-cardano';
import { DatumJson } from '@app/types';
+import { encoder } from 'js-encoding-utils';
export const lucidUtils: Utils = new Utils(new Lucid());
@@ -62,4 +63,7 @@ export function datumJsonToCbor(json: DatumJson): Datum {
};
return toHex(convert(json).to_bytes());
-}
\ No newline at end of file
+}
+
+export const bytesToHex = (bytes: Uint8Array): string => encoder.arrayBufferToHexString(bytes);
+export const hexToBytes = (hex: string): Uint8Array => encoder.hexStringToArrayBuffer(hex);
\ No newline at end of file