From 9158e480fdcf3f44e21df636bfce89d11a9c55d2 Mon Sep 17 00:00:00 2001 From: Lopes Date: Wed, 18 Sep 2024 18:12:09 -0300 Subject: [PATCH] CU-86dunk072 - Swap Multi Invoke - Fixes for the swap between bNEO and NEO --- .../CU-86dunk072_2024-09-18-21-09.json | 10 + .../FlamingoSwapInvocationBuilderNeo3.spec.ts | 260 +++++++++++++- .../helpers/FlamingoSwapHelper.spec.ts | 2 +- .../exchange-data/FlamingoEDSNeo3.spec.ts | 2 +- .../swap/FlamingoSwapServiceNeo3.spec.ts | 4 +- .../FlamingoSwapDetailsHandler.spec.ts | 2 +- .../handlers/FlamingoSwapRouteHandler.spec.ts | 2 +- .../FlamingoSwapInvocationBuilderNeo3.ts | 332 ++++++++++++++---- .../src/constants/FlamingoSwapConstants.ts | 2 +- .../bs-neo3/src/helpers/FlamingoSwapHelper.ts | 54 ++- .../services/blockchain-data/DoraBDSNeo3.ts | 2 +- .../services/blockchain-data/RpcBDSNeo3.ts | 2 +- .../services/exchange-data/FlamingoEDSNeo3.ts | 2 +- .../src/services/explorer/DoraESNeo3.ts | 2 +- .../services/nft-data/GhostMarketNDSNeo3.ts | 2 +- .../services/swap/FlamingoSwapServiceNeo3.ts | 31 +- .../handlers/FlamingoSwapDetailsHandler.ts | 2 +- .../swap/handlers/FlamingoSwapRouteHandler.ts | 2 +- 18 files changed, 619 insertions(+), 96 deletions(-) create mode 100644 common/changes/@cityofzion/bs-neo3/CU-86dunk072_2024-09-18-21-09.json diff --git a/common/changes/@cityofzion/bs-neo3/CU-86dunk072_2024-09-18-21-09.json b/common/changes/@cityofzion/bs-neo3/CU-86dunk072_2024-09-18-21-09.json new file mode 100644 index 0000000..24be8a7 --- /dev/null +++ b/common/changes/@cityofzion/bs-neo3/CU-86dunk072_2024-09-18-21-09.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@cityofzion/bs-neo3", + "comment": "Including wrap logic", + "type": "patch" + } + ], + "packageName": "@cityofzion/bs-neo3" +} \ No newline at end of file diff --git a/packages/bs-neo3/src/__tests__/builder/invocation/FlamingoSwapInvocationBuilderNeo3.spec.ts b/packages/bs-neo3/src/__tests__/builder/invocation/FlamingoSwapInvocationBuilderNeo3.spec.ts index 7755c06..35432d6 100644 --- a/packages/bs-neo3/src/__tests__/builder/invocation/FlamingoSwapInvocationBuilderNeo3.spec.ts +++ b/packages/bs-neo3/src/__tests__/builder/invocation/FlamingoSwapInvocationBuilderNeo3.spec.ts @@ -1,8 +1,8 @@ import { Network, SwapServiceSwapToReceiveArgs, SwapServiceSwapToUseArgs } from '@cityofzion/blockchain-service' import { ContractInvocationMulti } from '@cityofzion/neon-dappkit-types' import { FlamingoSwapInvocationBuilderNeo3 } from '../../../builder/invocation/FlamingoSwapInvocationBuilderNeo3' -import { FlamingoSwapConstants } from '../../../constants/FlamingoSwapConstants' import { BSNeo3Constants, BSNeo3NetworkId } from '../../../constants/BSNeo3Constants' +import { FlamingoSwapConstants } from '../../../constants/FlamingoSwapConstants' let network: Network @@ -11,7 +11,7 @@ describe('FlamingoSwapInvocationBuilderNeo3', () => { network = BSNeo3Constants.DEFAULT_NETWORK }) - it('Should match the invocation script swapping NEO to GAS - swapTokenToUse', () => { + it('Should match the invocation script swapping NEO to GAS (Swap Wrapping) - swapTokenToUse', () => { const NEO = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['NEO'] const bNEO = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['bNEO'] const GAS = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['GAS'] @@ -92,7 +92,6 @@ describe('FlamingoSwapInvocationBuilderNeo3', () => { { scopes: 16, allowedContracts: [ - '0x3244fcadcccff190c329f7b3083e4da2af60fbce', '0xf970f4ccecd765b63732b821775dc38c25d74f23', '0xca2d20610d7982ebe0bed124ee7e9b2d580a6efc', '0xfb75a5314069b56e136713d38477f647a13991b4', @@ -107,7 +106,133 @@ describe('FlamingoSwapInvocationBuilderNeo3', () => { expect(response).toEqual(expectedResponse) }) - it('Should match the invocation script swapping GAS to NEO - swapTokenToReceive', () => { + it('Should match the invocation script swapping NEO to bNEO (Wrap NEO) - swapTokenToUse', () => { + const NEO = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['NEO'] + const bNEO = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['bNEO'] + + const data: SwapServiceSwapToUseArgs = { + address: 'address', + amountToUse: '1', + deadline: '10', + minimumReceived: '1', + network, + routePath: [NEO, bNEO], + type: 'swapTokenToUse', + } + + const response = FlamingoSwapInvocationBuilderNeo3.swapInvocation(data) + + const expectedResponse: ContractInvocationMulti = { + invocations: [ + { + scriptHash: '0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5', + operation: 'transfer', + args: [ + { + type: 'Hash160', + value: data.address, + }, + { + type: 'Hash160', + value: '0x48c40d4666f93408be1bef038b6722404d9a4c2a', + }, + { + type: 'Integer', + value: '1', + }, + { + type: 'Any', + value: null, + }, + ], + }, + ], + signers: [ + { + scopes: 16, + allowedContracts: [ + '0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5', + '0x48c40d4666f93408be1bef038b6722404d9a4c2a', + ], + }, + ], + } + + expect(response).toEqual(expectedResponse) + }) + + it('Should match the invocation script swapping FLM to GAS (Simple swap) - swapTokenToUse', () => { + const FLM = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['FLM'] + const GAS = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['GAS'] + + const data: SwapServiceSwapToUseArgs = { + address: 'address', + amountToUse: '1', + deadline: '10', + minimumReceived: '0.01638157', + network, + routePath: [FLM, GAS], + type: 'swapTokenToUse', + } + + const response = FlamingoSwapInvocationBuilderNeo3.swapInvocation(data) + + const expectedResponse: ContractInvocationMulti = { + invocations: [ + { + scriptHash: '0xf970f4ccecd765b63732b821775dc38c25d74f23', + operation: 'swapTokenInForTokenOut', + args: [ + { + type: 'Hash160', + value: data.address, + }, + { + type: 'Integer', + value: '100000000', + }, + { + type: 'Integer', + value: '1638157', + }, + { + type: 'Array', + value: [ + { + type: 'Hash160', + value: '0xf0151f528127558851b39c2cd8aa47da7418ab28', + }, + { + type: 'Hash160', + value: '0xd2a4cff31913016155e38e474a2c06d08be276cf', + }, + ], + }, + { + type: 'Integer', + value: expect.any(String), + }, + ], + }, + ], + signers: [ + { + scopes: 16, + allowedContracts: [ + '0xf970f4ccecd765b63732b821775dc38c25d74f23', + '0xca2d20610d7982ebe0bed124ee7e9b2d580a6efc', + '0xfb75a5314069b56e136713d38477f647a13991b4', + '0xf0151f528127558851b39c2cd8aa47da7418ab28', + '0xd2a4cff31913016155e38e474a2c06d08be276cf', + ], + }, + ], + } + + expect(response).toEqual(expectedResponse) + }) + + it('Should match the invocation script swapping GAS to NEO (Swap Unwrapping) - swapTokenToReceive', () => { const NEO = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['NEO'] const bNEO = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['bNEO'] const GAS = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['GAS'] @@ -194,7 +319,132 @@ describe('FlamingoSwapInvocationBuilderNeo3', () => { '0xd2a4cff31913016155e38e474a2c06d08be276cf', '0x48c40d4666f93408be1bef038b6722404d9a4c2a', '0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5', - '0x3244fcadcccff190c329f7b3083e4da2af60fbce', + ], + }, + ], + } + + expect(response).toEqual(expectedResponse) + }) + + it('Should match the invocation script swapping bNEO to NEO (Unwrap NEO) - swapTokenToReceive', () => { + const NEO = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['NEO'] + const bNEO = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['bNEO'] + + const data: SwapServiceSwapToReceiveArgs = { + address: 'address', + amountToReceive: '1', + deadline: '10', + maximumSelling: '1', + network, + routePath: [bNEO, NEO], + type: 'swapTokenToReceive', + } + + const response = FlamingoSwapInvocationBuilderNeo3.swapInvocation(data) + + const expectedResponse: ContractInvocationMulti = { + invocations: [ + { + scriptHash: '0xd2a4cff31913016155e38e474a2c06d08be276cf', + operation: 'transfer', + args: [ + { + type: 'Hash160', + value: data.address, + }, + { + type: 'Hash160', + value: '0x48c40d4666f93408be1bef038b6722404d9a4c2a', + }, + { + type: 'Integer', + value: '100000', + }, + { + type: 'Any', + value: null, + }, + ], + }, + ], + signers: [ + { + scopes: 16, + allowedContracts: [ + '0xd2a4cff31913016155e38e474a2c06d08be276cf', + '0x48c40d4666f93408be1bef038b6722404d9a4c2a', + ], + }, + ], + } + + expect(response).toEqual(expectedResponse) + }) + + it('Should match the invocation script swapping GAS to FLM (Simple Swap) - swapTokenToReceive', () => { + const GAS = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['GAS'] + const FLM = FlamingoSwapConstants.FLAMINGO_SWAP_TOKENS[network.id]['FLM'] + + const data: SwapServiceSwapToReceiveArgs = { + address: 'address', + amountToReceive: '1', + deadline: '10', + maximumSelling: '61.0444685', + network, + routePath: [GAS, FLM], + type: 'swapTokenToReceive', + } + + const response = FlamingoSwapInvocationBuilderNeo3.swapInvocation(data) + + const expectedResponse: ContractInvocationMulti = { + invocations: [ + { + scriptHash: '0xf970f4ccecd765b63732b821775dc38c25d74f23', + operation: 'swapTokenOutForTokenIn', + args: [ + { + type: 'Hash160', + value: data.address, + }, + { + type: 'Integer', + value: '100000000', + }, + { + type: 'Integer', + value: '6104446850', + }, + { + type: 'Array', + value: [ + { + type: 'Hash160', + value: '0xd2a4cff31913016155e38e474a2c06d08be276cf', + }, + { + type: 'Hash160', + value: '0xf0151f528127558851b39c2cd8aa47da7418ab28', + }, + ], + }, + { + type: 'Integer', + value: expect.any(String), + }, + ], + }, + ], + signers: [ + { + scopes: 16, + allowedContracts: [ + '0xf970f4ccecd765b63732b821775dc38c25d74f23', + '0xca2d20610d7982ebe0bed124ee7e9b2d580a6efc', + '0xfb75a5314069b56e136713d38477f647a13991b4', + '0xd2a4cff31913016155e38e474a2c06d08be276cf', + '0xf0151f528127558851b39c2cd8aa47da7418ab28', ], }, ], diff --git a/packages/bs-neo3/src/__tests__/helpers/FlamingoSwapHelper.spec.ts b/packages/bs-neo3/src/__tests__/helpers/FlamingoSwapHelper.spec.ts index 4c8c4b4..d0414e4 100644 --- a/packages/bs-neo3/src/__tests__/helpers/FlamingoSwapHelper.spec.ts +++ b/packages/bs-neo3/src/__tests__/helpers/FlamingoSwapHelper.spec.ts @@ -1,8 +1,8 @@ import { Network, SwapRoute } from '@cityofzion/blockchain-service' import BigNumber from 'bignumber.js' +import { BSNeo3Constants, BSNeo3NetworkId } from '../../constants/BSNeo3Constants' import { FlamingoSwapConstants } from '../../constants/FlamingoSwapConstants' import { FlamingoSwapHelper } from '../../helpers/FlamingoSwapHelper' -import { BSNeo3Constants, BSNeo3NetworkId } from '../../constants/BSNeo3Constants' let network: Network diff --git a/packages/bs-neo3/src/__tests__/services/exchange-data/FlamingoEDSNeo3.spec.ts b/packages/bs-neo3/src/__tests__/services/exchange-data/FlamingoEDSNeo3.spec.ts index afe01e3..0b7937f 100644 --- a/packages/bs-neo3/src/__tests__/services/exchange-data/FlamingoEDSNeo3.spec.ts +++ b/packages/bs-neo3/src/__tests__/services/exchange-data/FlamingoEDSNeo3.spec.ts @@ -1,7 +1,7 @@ import { Network } from '@cityofzion/blockchain-service' -import { FlamingoEDSNeo3 } from '../../../services/exchange-data/FlamingoEDSNeo3' import { BSNeo3Constants, BSNeo3NetworkId } from '../../../constants/BSNeo3Constants' import { BSNeo3Helper } from '../../../helpers/BSNeo3Helper' +import { FlamingoEDSNeo3 } from '../../../services/exchange-data/FlamingoEDSNeo3' let flamingoEDSNeo3: FlamingoEDSNeo3 let network: Network diff --git a/packages/bs-neo3/src/__tests__/services/swap/FlamingoSwapServiceNeo3.spec.ts b/packages/bs-neo3/src/__tests__/services/swap/FlamingoSwapServiceNeo3.spec.ts index 7b5e49a..0716d3e 100644 --- a/packages/bs-neo3/src/__tests__/services/swap/FlamingoSwapServiceNeo3.spec.ts +++ b/packages/bs-neo3/src/__tests__/services/swap/FlamingoSwapServiceNeo3.spec.ts @@ -6,10 +6,10 @@ import { SwapServiceSwapToUseArgs, Token, } from '@cityofzion/blockchain-service' +import { BSNeo3 } from '../../../BSNeo3' +import { BSNeo3Constants, BSNeo3NetworkId } from '../../../constants/BSNeo3Constants' import { FlamingoSwapConstants } from '../../../constants/FlamingoSwapConstants' import { FlamingoSwapServiceNeo3 } from '../../../services/swap/FlamingoSwapServiceNeo3' -import { BSNeo3Constants, BSNeo3NetworkId } from '../../../constants/BSNeo3Constants' -import { BSNeo3 } from '../../../BSNeo3' let flamingoSwapServiceNeo3: FlamingoSwapServiceNeo3 let network: Network diff --git a/packages/bs-neo3/src/__tests__/services/swap/handlers/FlamingoSwapDetailsHandler.spec.ts b/packages/bs-neo3/src/__tests__/services/swap/handlers/FlamingoSwapDetailsHandler.spec.ts index 4373011..f5c1fff 100644 --- a/packages/bs-neo3/src/__tests__/services/swap/handlers/FlamingoSwapDetailsHandler.spec.ts +++ b/packages/bs-neo3/src/__tests__/services/swap/handlers/FlamingoSwapDetailsHandler.spec.ts @@ -1,7 +1,7 @@ import { Network } from '@cityofzion/blockchain-service' +import { BSNeo3Constants, BSNeo3NetworkId } from '../../../../constants/BSNeo3Constants' import { FlamingoSwapHelper } from '../../../../helpers/FlamingoSwapHelper' import { FlamingoSwapDetailsHandler } from '../../../../services/swap/handlers' -import { BSNeo3Constants, BSNeo3NetworkId } from '../../../../constants/BSNeo3Constants' let network: Network diff --git a/packages/bs-neo3/src/__tests__/services/swap/handlers/FlamingoSwapRouteHandler.spec.ts b/packages/bs-neo3/src/__tests__/services/swap/handlers/FlamingoSwapRouteHandler.spec.ts index 8d0d677..9bf364b 100644 --- a/packages/bs-neo3/src/__tests__/services/swap/handlers/FlamingoSwapRouteHandler.spec.ts +++ b/packages/bs-neo3/src/__tests__/services/swap/handlers/FlamingoSwapRouteHandler.spec.ts @@ -1,7 +1,7 @@ import { Network, SwapRoute } from '@cityofzion/blockchain-service' +import { BSNeo3Constants, BSNeo3NetworkId } from '../../../../constants/BSNeo3Constants' import { FlamingoSwapHelper } from '../../../../helpers/FlamingoSwapHelper' import { FlamingoSwapRouteHandler } from '../../../../services/swap/handlers' -import { BSNeo3Constants, BSNeo3NetworkId } from '../../../../constants/BSNeo3Constants' let network: Network diff --git a/packages/bs-neo3/src/builder/invocation/FlamingoSwapInvocationBuilderNeo3.ts b/packages/bs-neo3/src/builder/invocation/FlamingoSwapInvocationBuilderNeo3.ts index 7d0a7da..eee5fbf 100644 --- a/packages/bs-neo3/src/builder/invocation/FlamingoSwapInvocationBuilderNeo3.ts +++ b/packages/bs-neo3/src/builder/invocation/FlamingoSwapInvocationBuilderNeo3.ts @@ -1,35 +1,90 @@ -import { Network, SwapServiceSwapToReceiveArgs, SwapServiceSwapToUseArgs, Token } from '@cityofzion/blockchain-service' +import { SwapServiceSwapToReceiveArgs, SwapServiceSwapToUseArgs, Token } from '@cityofzion/blockchain-service' import { tx, u } from '@cityofzion/neon-core' -import { Arg, ContractInvocation, ContractInvocationMulti } from '@cityofzion/neon-dappkit-types' +import { Arg, ContractInvocationMulti } from '@cityofzion/neon-dappkit-types' +import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' import { FlamingoSwapConstants, FlamingoSwapScriptHashes } from '../../constants/FlamingoSwapConstants' import { FlamingoSwapHelper } from '../../helpers/FlamingoSwapHelper' import { NeonDappKitInvocationBuilderNeo3 } from './NeonDappKitInvocationBuilderNeo3' -import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' + +type SwapParams = { + scriptHashes: FlamingoSwapScriptHashes + tokenToUse: Token + tokenToReceive: Token +} + +type SwapTokenToReceiveInvocationParams = SwapServiceSwapToReceiveArgs & SwapParams +type SwapTokenToUseInvocationParams = SwapServiceSwapToUseArgs & SwapParams export class FlamingoSwapInvocationBuilderNeo3 { static swapInvocation( data: SwapServiceSwapToReceiveArgs | SwapServiceSwapToUseArgs ): ContractInvocationMulti { - return data.type === 'swapTokenToReceive' - ? this.#buildSwapTokenToReceiveInvocation(data) - : this.#buildSwapTokenToUseInvocation(data) - } + const { routePath, network, type } = data - static #buildSwapTokenToReceiveInvocation({ - address, - amountToReceive, - deadline, - maximumSelling, - network, - routePath, - }: SwapServiceSwapToReceiveArgs): ContractInvocationMulti { const [tokenToUse] = routePath const tokenToReceive = routePath[routePath.length - 1] const scriptHashes = FlamingoSwapHelper.getFlamingoSwapScriptHashes(network) - const invocations: ContractInvocation[] = [] - const allowedContracts: string[] = [] + const swapParams: SwapParams = { + scriptHashes, + tokenToUse, + tokenToReceive, + } + + if (type === 'swapTokenToReceive') { + return this.#swapTokenToReceiveInvocation({ + ...data, + ...swapParams, + }) + } + + return this.#swapTokenToUseInvocation({ + ...data, + ...swapParams, + }) + } + + static #swapTokenToReceiveInvocation(data: SwapTokenToReceiveInvocationParams): ContractInvocationMulti { + const { network, routePath } = data + + if (FlamingoSwapHelper.isSwapUnwrappingNeo(network, routePath)) { + return this.#swapUnwrappingNeoInvocation(data) + } + + if (FlamingoSwapHelper.isUnwrapNeo(network, routePath)) { + return this.#unwrapNeoInvocation(data) + } + + return this.#swapReceiveInvocation(data) + } + + static #swapTokenToUseInvocation(data: SwapTokenToUseInvocationParams): ContractInvocationMulti { + const { network, routePath } = data + + if (FlamingoSwapHelper.isSwapWrappingNeo(network, routePath)) { + return this.#swapWrappingNeoInvocation(data) + } + + if (FlamingoSwapHelper.isWrapNeo(network, routePath)) { + return this.#wrapNeoInvocation(data) + } + + return this.#swapToUseInvocation(data) + } + + static #swapUnwrappingNeoInvocation(data: SwapTokenToReceiveInvocationParams): ContractInvocationMulti { + const { + address, + amountToReceive, + deadline, + maximumSelling, + network, + routePath, + scriptHashes, + tokenToReceive, + tokenToUse, + } = data const tokenToReceiveOverrode = FlamingoSwapHelper.overrideToken(network, tokenToReceive) const routePathOverrode = FlamingoSwapHelper.overrideRoutePath(network, routePath) @@ -37,6 +92,21 @@ export class FlamingoSwapInvocationBuilderNeo3 { const amountToReceiveFormatted = FlamingoSwapHelper.formatAmount(amountToReceive, tokenToReceiveOverrode.decimals) const maximumSellingFormatted = FlamingoSwapHelper.formatAmount(maximumSelling, tokenToUse.decimals) + const amountToReceiveTransfer = u.BigInteger.fromNumber( + Number(amountToReceiveFormatted) * FlamingoSwapConstants.UNWRAPPING_FEE + ).toString() + + const GAS = FlamingoSwapHelper.getFlamingoSwapToken(network, 'GAS') + const bNEO = FlamingoSwapHelper.getFlamingoSwapToken(network, 'bNEO') + + const unwrapInvocation = NeonDappKitInvocationBuilderNeo3.transferContractInvocation({ + contractHash: GAS.hash, + tokenHash: bNEO.hash, + senderAddress: address, + amount: amountToReceiveTransfer, + }) + const unwrapAllowedContracts = [GAS.hash, bNEO.hash] + const swapInvocation = NeonDappKitInvocationBuilderNeo3.swapTokenOutForTokenInContractInvocation({ routerScriptHash: scriptHashes.flamingoSwapRouter, senderAddress: address, @@ -45,51 +115,105 @@ export class FlamingoSwapInvocationBuilderNeo3 { deadline, args: this.#mapRoutePathToArgs(routePathOverrode), }) + const swapAllowedContracts = [ + scriptHashes.flamingoSwapRouter, + scriptHashes.flamingoFactory, + scriptHashes.flamingoPairWhiteList, + ] - invocations.push(swapInvocation) - allowedContracts.push(...this.#getAllowedContracts(scriptHashes, routePath)) + const allowedContracts = [ + ...new Set([...swapAllowedContracts, ...unwrapAllowedContracts, ...routePath.map(token => token.hash)]), + ] - if (FlamingoSwapHelper.isNeoToken(network, tokenToReceive)) { - const amountToReceiveTransfer = u.BigInteger.fromNumber( - Number(amountToReceiveFormatted) * FlamingoSwapConstants.GAS_PER_NEO - ).toString() + return { + invocations: [swapInvocation, unwrapInvocation], + signers: [ + { + scopes: tx.WitnessScope.CustomContracts, + allowedContracts, + }, + ], + } + } - const gasHash = FlamingoSwapHelper.getFlamingoSwapToken(network, 'GAS').hash - const neoTransferInvocation = this.#buildNEOTransferInvocation(address, gasHash, amountToReceiveTransfer, network) + static #unwrapNeoInvocation(data: SwapTokenToReceiveInvocationParams): ContractInvocationMulti { + const { address, amountToReceive, network, tokenToReceive } = data - invocations.push(neoTransferInvocation) - allowedContracts.push(FlamingoSwapHelper.getFlamingoSwapPool(network, 'FLP-bNEO-GAS').hash) - } + const tokenToReceiveOverrode = FlamingoSwapHelper.overrideToken(network, tokenToReceive) + const amountToReceiveFormatted = FlamingoSwapHelper.formatAmount(amountToReceive, tokenToReceiveOverrode.decimals) + + const GAS = FlamingoSwapHelper.getFlamingoSwapToken(network, 'GAS') + const bNEO = FlamingoSwapHelper.getFlamingoSwapToken(network, 'bNEO') + + const unwrappedAmount = FlamingoSwapHelper.getUnwrappedAmount(amountToReceiveFormatted) + const unwrapInvocation = NeonDappKitInvocationBuilderNeo3.transferContractInvocation({ + contractHash: GAS.hash, + tokenHash: bNEO.hash, + senderAddress: address, + amount: unwrappedAmount, + }) + const unwrapAllowedContracts = [GAS.hash, bNEO.hash] return { - invocations, - signers: [{ scopes: tx.WitnessScope.CustomContracts, allowedContracts }], + invocations: [unwrapInvocation], + signers: [{ scopes: tx.WitnessScope.CustomContracts, allowedContracts: unwrapAllowedContracts }], } } - static #buildSwapTokenToUseInvocation({ - address, - amountToUse, - deadline, - minimumReceived, - network, - routePath, - }: SwapServiceSwapToUseArgs): ContractInvocationMulti { - const [tokenToUse] = routePath - const tokenToReceive = routePath[routePath.length - 1] + static #swapReceiveInvocation(data: SwapTokenToReceiveInvocationParams): ContractInvocationMulti { + const { + address, + amountToReceive, + deadline, + maximumSelling, + network, + routePath, + scriptHashes, + tokenToReceive, + tokenToUse, + } = data - const scriptHashes = FlamingoSwapHelper.getFlamingoSwapScriptHashes(network) + const tokenToReceiveOverrode = FlamingoSwapHelper.overrideToken(network, tokenToReceive) + const routePathOverrode = FlamingoSwapHelper.overrideRoutePath(network, routePath) + + const amountToReceiveFormatted = FlamingoSwapHelper.formatAmount(amountToReceive, tokenToReceiveOverrode.decimals) + const maximumSellingFormatted = FlamingoSwapHelper.formatAmount(maximumSelling, tokenToUse.decimals) + + const swapInvocation = NeonDappKitInvocationBuilderNeo3.swapTokenOutForTokenInContractInvocation({ + routerScriptHash: scriptHashes.flamingoSwapRouter, + senderAddress: address, + amountToReceive: amountToReceiveFormatted, + maximumSelling: maximumSellingFormatted, + deadline, + args: this.#mapRoutePathToArgs(routePathOverrode), + }) - const invocations: ContractInvocation[] = [] - const allowedContracts: string[] = [] + const swapAllowedContracts = [ + scriptHashes.flamingoSwapRouter, + scriptHashes.flamingoFactory, + scriptHashes.flamingoPairWhiteList, + ] - if (FlamingoSwapHelper.isNeoToken(network, tokenToUse)) { - const neoHash = FlamingoSwapHelper.getFlamingoSwapToken(network, 'NEO').hash - const neoTransferInvocation = this.#buildNEOTransferInvocation(address, neoHash, amountToUse, network) + const allowedContracts = [...new Set([...swapAllowedContracts, ...routePath.map(token => token.hash)])] - invocations.push(neoTransferInvocation) - allowedContracts.push(FlamingoSwapHelper.getFlamingoSwapPool(network, 'FLP-bNEO-GAS').hash) + return { + invocations: [swapInvocation], + signers: [{ scopes: tx.WitnessScope.CustomContracts, allowedContracts }], } + } + + static #swapWrappingNeoInvocation(data: SwapTokenToUseInvocationParams): ContractInvocationMulti { + const { + address, + amountToUse, + network, + deadline, + minimumReceived, + routePath, + scriptHashes, + tokenToReceive, + tokenToUse, + } = data const tokenToUseOverrode = FlamingoSwapHelper.overrideToken(network, tokenToUse) const routePathOverrode = FlamingoSwapHelper.overrideRoutePath(network, routePath) @@ -97,6 +221,17 @@ export class FlamingoSwapInvocationBuilderNeo3 { const amountToUseFormatted = FlamingoSwapHelper.formatAmount(amountToUse, tokenToUseOverrode.decimals) const minimumReceivedFormatted = FlamingoSwapHelper.formatAmount(minimumReceived, tokenToReceive.decimals) + const NEO = FlamingoSwapHelper.getFlamingoSwapToken(network, 'NEO') + const bNEO = FlamingoSwapHelper.getFlamingoSwapToken(network, 'bNEO') + + const wrapInvocation = NeonDappKitInvocationBuilderNeo3.transferContractInvocation({ + contractHash: NEO.hash, + tokenHash: bNEO.hash, + senderAddress: address, + amount: amountToUse, + }) + const wrapAllowedContracts = [NEO.hash, bNEO.hash] + const swapInvocation = NeonDappKitInvocationBuilderNeo3.swapTokenInForTokenOutContractInvocation({ routerScriptHash: scriptHashes.flamingoSwapRouter, amountToUse: amountToUseFormatted, @@ -106,44 +241,93 @@ export class FlamingoSwapInvocationBuilderNeo3 { args: this.#mapRoutePathToArgs(routePathOverrode), }) - invocations.push(swapInvocation) - allowedContracts.push(...this.#getAllowedContracts(scriptHashes, routePath)) + const swapAllowedContracts = [ + scriptHashes.flamingoSwapRouter, + scriptHashes.flamingoFactory, + scriptHashes.flamingoPairWhiteList, + ] + + const allowedContracts = [ + ...new Set([...swapAllowedContracts, ...wrapAllowedContracts, ...routePath.map(token => token.hash)]), + ] return { - invocations, - signers: [{ scopes: tx.WitnessScope.CustomContracts, allowedContracts }], + invocations: [wrapInvocation, swapInvocation], + signers: [ + { + scopes: tx.WitnessScope.CustomContracts, + allowedContracts, + }, + ], } } - static #mapRoutePathToArgs(routePath: Token[]): Arg[] { - return routePath.map(token => ({ - type: 'Hash160', - value: token.hash, - })) + static #wrapNeoInvocation(data: SwapTokenToUseInvocationParams): ContractInvocationMulti { + const { address, amountToUse, network } = data + + const NEO = FlamingoSwapHelper.getFlamingoSwapToken(network, 'NEO') + const bNEO = FlamingoSwapHelper.getFlamingoSwapToken(network, 'bNEO') + + const wrapInvocation = NeonDappKitInvocationBuilderNeo3.transferContractInvocation({ + contractHash: NEO.hash, + tokenHash: bNEO.hash, + senderAddress: address, + amount: amountToUse, + }) + const wrapAllowedContracts = [NEO.hash, bNEO.hash] + + return { + invocations: [wrapInvocation], + signers: [{ scopes: tx.WitnessScope.CustomContracts, allowedContracts: wrapAllowedContracts }], + } } - static #getAllowedContracts(scriptHashes: FlamingoSwapScriptHashes, routePath: Token[]): string[] { - return [ + static #swapToUseInvocation(data: SwapTokenToUseInvocationParams): ContractInvocationMulti { + const { + address, + amountToUse, + deadline, + minimumReceived, + network, + routePath, + scriptHashes, + tokenToReceive, + tokenToUse, + } = data + + const tokenToUseOverrode = FlamingoSwapHelper.overrideToken(network, tokenToUse) + const routePathOverrode = FlamingoSwapHelper.overrideRoutePath(network, routePath) + + const amountToUseFormatted = FlamingoSwapHelper.formatAmount(amountToUse, tokenToUseOverrode.decimals) + const minimumReceivedFormatted = FlamingoSwapHelper.formatAmount(minimumReceived, tokenToReceive.decimals) + + const swapInvocation = NeonDappKitInvocationBuilderNeo3.swapTokenInForTokenOutContractInvocation({ + routerScriptHash: scriptHashes.flamingoSwapRouter, + amountToUse: amountToUseFormatted, + minimumReceived: minimumReceivedFormatted, + senderAddress: address, + deadline, + args: this.#mapRoutePathToArgs(routePathOverrode), + }) + + const swapAllowedContracts = [ scriptHashes.flamingoSwapRouter, scriptHashes.flamingoFactory, scriptHashes.flamingoPairWhiteList, - ...routePath.map(token => token.hash), ] - } - static #buildNEOTransferInvocation( - senderAddress: string, - contractHash: string, - amount: string, - network: Network - ): ContractInvocation { - const bNEO = FlamingoSwapHelper.getFlamingoSwapToken(network, 'bNEO') + const allowedContracts = [...new Set([...swapAllowedContracts, ...routePath.map(token => token.hash)])] - return NeonDappKitInvocationBuilderNeo3.transferContractInvocation({ - contractHash, - tokenHash: bNEO.hash, - senderAddress, - amount, - }) + return { + invocations: [swapInvocation], + signers: [{ scopes: tx.WitnessScope.CustomContracts, allowedContracts }], + } + } + + static #mapRoutePathToArgs(routePath: Token[]): Arg[] { + return routePath.map(token => ({ + type: 'Hash160', + value: token.hash, + })) } } diff --git a/packages/bs-neo3/src/constants/FlamingoSwapConstants.ts b/packages/bs-neo3/src/constants/FlamingoSwapConstants.ts index 1988eac..40dae7d 100644 --- a/packages/bs-neo3/src/constants/FlamingoSwapConstants.ts +++ b/packages/bs-neo3/src/constants/FlamingoSwapConstants.ts @@ -36,7 +36,7 @@ export class FlamingoSwapConstants { static readonly FEE_RATE = new BigNumber(0.003) - static readonly GAS_PER_NEO = 0.001 + static readonly UNWRAPPING_FEE = 0.001 static readonly TESTNET_FLAMINGO_SWAP_TOKENS: FlamingoSwapTokens = { // ============ Neo Assets ============ // diff --git a/packages/bs-neo3/src/helpers/FlamingoSwapHelper.ts b/packages/bs-neo3/src/helpers/FlamingoSwapHelper.ts index 014966f..63f5781 100644 --- a/packages/bs-neo3/src/helpers/FlamingoSwapHelper.ts +++ b/packages/bs-neo3/src/helpers/FlamingoSwapHelper.ts @@ -1,6 +1,7 @@ import { Network, SwapRoute, Token } from '@cityofzion/blockchain-service' import { u } from '@cityofzion/neon-core' import BigNumber from 'bignumber.js' +import { BSNeo3NetworkId } from '../constants/BSNeo3Constants' import { FlamingoSwapConstants, FlamingoSwapPoolInfo, @@ -9,7 +10,6 @@ import { FlamingoSwapTokens, } from '../constants/FlamingoSwapConstants' import { FlamingoSwapRouteHandler } from '../services/swap/handlers' -import { BSNeo3NetworkId } from '../constants/BSNeo3Constants' export class FlamingoSwapHelper { static listSwappableTokensSymbol(network: Network): string[] { @@ -113,7 +113,59 @@ export class FlamingoSwapHelper { return this.normalizeHash(token.hash) === this.normalizeHash(NEO.hash) } + static isBneoToken(network: Network, token: Token): boolean { + const bNEO = this.getFlamingoSwapToken(network, 'bNEO') + + return this.normalizeHash(token.hash) === this.normalizeHash(bNEO.hash) + } + static formatAmount(amount: string, decimals: number): string { return u.BigInteger.fromDecimal(Number(amount), decimals).toString() } + + static isSwapUnwrappingNeo(network: Network, route: Token[]): boolean { + if (route.length < 3) { + return false + } + + const lastToken = route[route.length - 1] + + return this.isNeoToken(network, lastToken) + } + + static isSwapWrappingNeo(network: Network, route: Token[]): boolean { + if (route.length < 3) { + return false + } + + const firstToken = route[0] + + return this.isNeoToken(network, firstToken) + } + + static isWrapNeo(network: Network, route: Token[]): boolean { + if (route.length !== 2) { + return false + } + + const firstToken = route[0] + const lastToken = route[1] + + return this.isNeoToken(network, firstToken) && this.isBneoToken(network, lastToken) + } + + static isUnwrapNeo(network: Network, route: Token[]): boolean { + if (route.length !== 2) { + return false + } + + const firstToken = route[0] + const lastToken = route[1] + + return this.isBneoToken(network, firstToken) && this.isNeoToken(network, lastToken) + } + + static getUnwrappedAmount(amount: string): string { + return u.BigInteger.fromNumber(Number(amount) * FlamingoSwapConstants.UNWRAPPING_FEE).toString() + } } diff --git a/packages/bs-neo3/src/services/blockchain-data/DoraBDSNeo3.ts b/packages/bs-neo3/src/services/blockchain-data/DoraBDSNeo3.ts index 5ee0178..d517ee0 100644 --- a/packages/bs-neo3/src/services/blockchain-data/DoraBDSNeo3.ts +++ b/packages/bs-neo3/src/services/blockchain-data/DoraBDSNeo3.ts @@ -12,9 +12,9 @@ import { } from '@cityofzion/blockchain-service' import { NeoRESTApi } from '@cityofzion/dora-ts/dist/api' import { u, wallet } from '@cityofzion/neon-js' +import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' import { BSNeo3Helper } from '../../helpers/BSNeo3Helper' import { RpcBDSNeo3 } from './RpcBDSNeo3' -import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' const NeoRest = new NeoRESTApi({ doraUrl: 'https://dora.coz.io', diff --git a/packages/bs-neo3/src/services/blockchain-data/RpcBDSNeo3.ts b/packages/bs-neo3/src/services/blockchain-data/RpcBDSNeo3.ts index 06b8825..95b6218 100644 --- a/packages/bs-neo3/src/services/blockchain-data/RpcBDSNeo3.ts +++ b/packages/bs-neo3/src/services/blockchain-data/RpcBDSNeo3.ts @@ -14,8 +14,8 @@ import { } from '@cityofzion/blockchain-service' import { rpc, u } from '@cityofzion/neon-core' import { NeonInvoker, TypeChecker } from '@cityofzion/neon-dappkit' -import { BSNeo3Helper } from '../../helpers/BSNeo3Helper' import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' +import { BSNeo3Helper } from '../../helpers/BSNeo3Helper' export class RpcBDSNeo3 implements BlockchainDataService, BDSClaimable { readonly _tokenCache: Map = new Map() diff --git a/packages/bs-neo3/src/services/exchange-data/FlamingoEDSNeo3.ts b/packages/bs-neo3/src/services/exchange-data/FlamingoEDSNeo3.ts index 6974511..a198cf9 100644 --- a/packages/bs-neo3/src/services/exchange-data/FlamingoEDSNeo3.ts +++ b/packages/bs-neo3/src/services/exchange-data/FlamingoEDSNeo3.ts @@ -8,8 +8,8 @@ import { TokenPricesResponse, } from '@cityofzion/blockchain-service' import axios, { AxiosInstance } from 'axios' -import { BSNeo3Helper } from '../../helpers/BSNeo3Helper' import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' +import { BSNeo3Helper } from '../../helpers/BSNeo3Helper' type FlamingoTokenInfoPricesResponse = { symbol: string diff --git a/packages/bs-neo3/src/services/explorer/DoraESNeo3.ts b/packages/bs-neo3/src/services/explorer/DoraESNeo3.ts index 96c2cfa..56275b9 100644 --- a/packages/bs-neo3/src/services/explorer/DoraESNeo3.ts +++ b/packages/bs-neo3/src/services/explorer/DoraESNeo3.ts @@ -1,6 +1,6 @@ import { BuildNftUrlParams, ExplorerService, Network } from '@cityofzion/blockchain-service' -import { BSNeo3Helper } from '../../helpers/BSNeo3Helper' import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' +import { BSNeo3Helper } from '../../helpers/BSNeo3Helper' export class DoraESNeo3 implements ExplorerService { readonly #network: Network diff --git a/packages/bs-neo3/src/services/nft-data/GhostMarketNDSNeo3.ts b/packages/bs-neo3/src/services/nft-data/GhostMarketNDSNeo3.ts index 7986141..275473b 100644 --- a/packages/bs-neo3/src/services/nft-data/GhostMarketNDSNeo3.ts +++ b/packages/bs-neo3/src/services/nft-data/GhostMarketNDSNeo3.ts @@ -1,8 +1,8 @@ import { GetNftParam, GetNftsByAddressParams, Network, NftResponse, NftsResponse } from '@cityofzion/blockchain-service' import axios from 'axios' import qs from 'query-string' -import { RpcNDSNeo3 } from './RpcNDSNeo3' import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' +import { RpcNDSNeo3 } from './RpcNDSNeo3' type GhostMarketNFT = { tokenId: string diff --git a/packages/bs-neo3/src/services/swap/FlamingoSwapServiceNeo3.ts b/packages/bs-neo3/src/services/swap/FlamingoSwapServiceNeo3.ts index 14ed1db..ca84280 100644 --- a/packages/bs-neo3/src/services/swap/FlamingoSwapServiceNeo3.ts +++ b/packages/bs-neo3/src/services/swap/FlamingoSwapServiceNeo3.ts @@ -13,11 +13,11 @@ import { NeonInvoker } from '@cityofzion/neon-dappkit' import EventEmitter from 'events' import cloneDeep from 'lodash.clonedeep' import TypedEmitter from 'typed-emitter' +import { BSNeo3 } from '../../BSNeo3' import { FlamingoSwapInvocationBuilderNeo3 } from '../../builder/invocation/FlamingoSwapInvocationBuilderNeo3' +import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' import { FlamingoSwapHelper } from '../../helpers/FlamingoSwapHelper' import { FlamingoSwapDetailsHandler, FlamingoSwapRouteHandler, FlamingoSwapSocketService } from './handlers' -import { BSNeo3 } from '../../BSNeo3' -import { BSNeo3NetworkId } from '../../constants/BSNeo3Constants' type BuildSwapInvocationArgs = SwapServiceSwapToUseArgs | SwapServiceSwapToReceiveArgs @@ -196,6 +196,33 @@ export class FlamingoSwapServiceNeo3 implements SwapService { const amountToReceive = this.#lastAmountChanged === 'amountToReceive' ? this.#amountToReceive : null const amountToUse = this.#lastAmountChanged === 'amountToUse' ? this.#amountToUse : null + const initialRoutePath = [this.#tokenToReceive, this.#tokenToUse] + + if ( + FlamingoSwapHelper.isWrapNeo(this.#network, initialRoutePath) || + FlamingoSwapHelper.isUnwrapNeo(this.#network, initialRoutePath) + ) { + const amount = amountToUse ?? amountToReceive + + this.#amountToUse = amount + this.#amountToReceive = amount + this.#maximumSelling = amount + this.#minimumReceived = amount + this.#liquidityProviderFee = null + this.#priceImpact = null + this.#priceInverse = null + this.#route = [ + { + reserveTokenToReceive: '0', + reserveTokenToUse: '0', + tokenToReceive: this.#tokenToReceive, + tokenToUse: this.#tokenToUse, + }, + ] + + return + } + const { amountToUseToDisplay, amountToReceiveToDisplay, diff --git a/packages/bs-neo3/src/services/swap/handlers/FlamingoSwapDetailsHandler.ts b/packages/bs-neo3/src/services/swap/handlers/FlamingoSwapDetailsHandler.ts index cc8557b..2f24c7b 100644 --- a/packages/bs-neo3/src/services/swap/handlers/FlamingoSwapDetailsHandler.ts +++ b/packages/bs-neo3/src/services/swap/handlers/FlamingoSwapDetailsHandler.ts @@ -1,8 +1,8 @@ import { Network, SwapRoute, Token } from '@cityofzion/blockchain-service' import BigNumber from 'bignumber.js' +import { BSNeo3NetworkId } from '../../../constants/BSNeo3Constants' import { FlamingoSwapConstants } from '../../../constants/FlamingoSwapConstants' import { FlamingoSwapHelper } from '../../../helpers/FlamingoSwapHelper' -import { BSNeo3NetworkId } from '../../../constants/BSNeo3Constants' type CalculateSwapDetailsArgs = { amountToUse: string | null diff --git a/packages/bs-neo3/src/services/swap/handlers/FlamingoSwapRouteHandler.ts b/packages/bs-neo3/src/services/swap/handlers/FlamingoSwapRouteHandler.ts index 992344b..432bf9c 100644 --- a/packages/bs-neo3/src/services/swap/handlers/FlamingoSwapRouteHandler.ts +++ b/packages/bs-neo3/src/services/swap/handlers/FlamingoSwapRouteHandler.ts @@ -7,8 +7,8 @@ import { TypeChecker, } from '@cityofzion/neon-dappkit-types' import { NeonDappKitInvocationBuilderNeo3 } from '../../../builder/invocation/NeonDappKitInvocationBuilderNeo3' -import { FlamingoSwapHelper } from '../../../helpers/FlamingoSwapHelper' import { BSNeo3NetworkId } from '../../../constants/BSNeo3Constants' +import { FlamingoSwapHelper } from '../../../helpers/FlamingoSwapHelper' type CalculateBestRouteForSwapArgs = { tokenToUse: Token