diff --git a/common/changes/@cityofzion/blockchain-service/CU-86duf6qwu_2024-09-06-23-00.json b/common/changes/@cityofzion/blockchain-service/CU-86duf6qwu_2024-09-06-23-00.json new file mode 100644 index 0000000..5a5f044 --- /dev/null +++ b/common/changes/@cityofzion/blockchain-service/CU-86duf6qwu_2024-09-06-23-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@cityofzion/blockchain-service", + "comment": "Add support to send in bulk", + "type": "minor" + } + ], + "packageName": "@cityofzion/blockchain-service" +} \ No newline at end of file diff --git a/common/changes/@cityofzion/bs-ethereum/CU-86duf6qwu_2024-09-06-23-00.json b/common/changes/@cityofzion/bs-ethereum/CU-86duf6qwu_2024-09-06-23-00.json new file mode 100644 index 0000000..51f603c --- /dev/null +++ b/common/changes/@cityofzion/bs-ethereum/CU-86duf6qwu_2024-09-06-23-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@cityofzion/bs-ethereum", + "comment": "Add support to send in bulk", + "type": "minor" + } + ], + "packageName": "@cityofzion/bs-ethereum" +} \ No newline at end of file diff --git a/common/changes/@cityofzion/bs-neo-legacy/CU-86duf6qwu_2024-09-06-23-00.json b/common/changes/@cityofzion/bs-neo-legacy/CU-86duf6qwu_2024-09-06-23-00.json new file mode 100644 index 0000000..a5206a1 --- /dev/null +++ b/common/changes/@cityofzion/bs-neo-legacy/CU-86duf6qwu_2024-09-06-23-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@cityofzion/bs-neo-legacy", + "comment": "Add support to send in bulk", + "type": "minor" + } + ], + "packageName": "@cityofzion/bs-neo-legacy" +} \ No newline at end of file diff --git a/common/changes/@cityofzion/bs-neo3/CU-86duf6qwu_2024-09-06-23-00.json b/common/changes/@cityofzion/bs-neo3/CU-86duf6qwu_2024-09-06-23-00.json new file mode 100644 index 0000000..8da3ec0 --- /dev/null +++ b/common/changes/@cityofzion/bs-neo3/CU-86duf6qwu_2024-09-06-23-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@cityofzion/bs-neo3", + "comment": "Add support to send in bulk", + "type": "minor" + } + ], + "packageName": "@cityofzion/bs-neo3" +} \ No newline at end of file diff --git a/packages/blockchain-service/src/interfaces.ts b/packages/blockchain-service/src/interfaces.ts index 263cb2b..f112d53 100644 --- a/packages/blockchain-service/src/interfaces.ts +++ b/packages/blockchain-service/src/interfaces.ts @@ -32,7 +32,7 @@ export type IntentTransferParam = { export type TransferParam = { senderAccount: Account - intent: IntentTransferParam + intents: IntentTransferParam[] tipIntent?: IntentTransferParam priorityFee?: string isLedger?: boolean @@ -54,7 +54,7 @@ export interface BlockchainService + transfer(param: TransferParam): Promise } export interface BSCalculableFee { diff --git a/packages/bs-ethereum/src/BSEthereum.ts b/packages/bs-ethereum/src/BSEthereum.ts index 9f8cb63..d9e8ce3 100644 --- a/packages/bs-ethereum/src/BSEthereum.ts +++ b/packages/bs-ethereum/src/BSEthereum.ts @@ -9,6 +9,7 @@ import { BlockchainService, ExchangeDataService, ExplorerService, + IntentTransferParam, Network, NftDataService, Token, @@ -80,29 +81,28 @@ export class BSEthereum return new ethers.Wallet(account.key, provider) } - async #buildTransferParams(param: TransferParam) { - const signer = await this.#generateSigner(param.senderAccount, param.isLedger) - if (!signer.provider) throw new Error('Signer must have provider') + async #buildTransferParams(intent: IntentTransferParam) { + const provider = new ethers.providers.JsonRpcProvider(this.network.url) - const decimals = param.intent.tokenDecimals ?? 18 - const amount = ethersBigNumber.parseFixed(param.intent.amount, decimals) + const decimals = intent.tokenDecimals ?? 18 + const amount = ethersBigNumber.parseFixed(intent.amount, decimals) - const gasPrice = await signer.provider.getGasPrice() + const gasPrice = await provider.getGasPrice() let transactionParams: ethers.utils.Deferrable = { type: 2, } const isNative = - BSEthereumHelper.normalizeHash(this.feeToken.hash) === BSEthereumHelper.normalizeHash(param.intent.tokenHash) + BSEthereumHelper.normalizeHash(this.feeToken.hash) === BSEthereumHelper.normalizeHash(intent.tokenHash) if (isNative) { - transactionParams.to = param.intent.receiverAddress + transactionParams.to = intent.receiverAddress transactionParams.value = amount } else { - const contract = new ethers.Contract(param.intent.tokenHash, [ + const contract = new ethers.Contract(intent.tokenHash, [ 'function transfer(address to, uint amount) returns (bool)', ]) - const populatedTransaction = await contract.populateTransaction.transfer(param.intent.receiverAddress, amount) + const populatedTransaction = await contract.populateTransaction.transfer(intent.receiverAddress, amount) transactionParams = { ...populatedTransaction, ...transactionParams, @@ -111,7 +111,6 @@ export class BSEthereum return { transactionParams, - signer, gasPrice, } } @@ -210,30 +209,52 @@ export class BSEthereum return wallet.encrypt(password) } - async transfer(param: TransferParam): Promise { - const { signer, transactionParams, gasPrice } = await this.#buildTransferParams(param) + async transfer(param: TransferParam): Promise { + const signer = await this.#generateSigner(param.senderAccount, param.isLedger) - let gasLimit: ethers.BigNumberish - try { - gasLimit = await signer.estimateGas(transactionParams) - } catch { - gasLimit = BSEthereumConstants.DEFAULT_GAS_LIMIT + const sentTransactionHashes: string[] = [] + + for (const intent of param.intents) { + try { + const { transactionParams, gasPrice } = await this.#buildTransferParams(intent) + + let gasLimit: ethers.BigNumberish + try { + gasLimit = await signer.estimateGas(transactionParams) + } catch { + gasLimit = BSEthereumConstants.DEFAULT_GAS_LIMIT + } + + const transaction = await signer.sendTransaction({ + ...transactionParams, + gasLimit, + maxPriorityFeePerGas: gasPrice, + maxFeePerGas: gasPrice, + }) + + sentTransactionHashes.push(transaction.hash) + } catch { + /* empty */ + } } - const transaction = await signer.sendTransaction({ - ...transactionParams, - gasLimit, - maxPriorityFeePerGas: gasPrice, - maxFeePerGas: gasPrice, - }) - - return transaction.hash + return sentTransactionHashes } async calculateTransferFee(param: TransferParam): Promise { - const { signer, transactionParams, gasPrice } = await this.#buildTransferParams(param) - const estimated = await signer.estimateGas(transactionParams) - return ethers.utils.formatEther(gasPrice.mul(estimated)) + const signer = await this.#generateSigner(param.senderAccount, param.isLedger) + + let fee = ethers.utils.parseEther('0') + + for (const intent of param.intents) { + const { gasPrice, transactionParams } = await this.#buildTransferParams(intent) + const estimated = await signer.estimateGas(transactionParams) + const intentFee = gasPrice.mul(estimated) + + fee = fee.add(intentFee) + } + + return ethers.utils.formatEther(fee) } async resolveNameServiceDomain(domainName: string): Promise { diff --git a/packages/bs-ethereum/src/__tests__/BSEthereum.spec.ts b/packages/bs-ethereum/src/__tests__/BSEthereum.spec.ts index a14dbee..47b11cb 100644 --- a/packages/bs-ethereum/src/__tests__/BSEthereum.spec.ts +++ b/packages/bs-ethereum/src/__tests__/BSEthereum.spec.ts @@ -85,12 +85,14 @@ describe('BSEthereum', () => { const fee = await bsEthereum.calculateTransferFee({ senderAccount: account, - intent: { - amount: '0.1', - receiverAddress: '0xFACf5446B71dB33E920aB1769d9427146183aEcd', - tokenDecimals: 18, - tokenHash: '0xBA62BCfcAaFc6622853cca2BE6Ac7d845BC0f2Dc', - }, + intents: [ + { + amount: '0.1', + receiverAddress: '0xFACf5446B71dB33E920aB1769d9427146183aEcd', + tokenDecimals: 18, + tokenHash: '0xBA62BCfcAaFc6622853cca2BE6Ac7d845BC0f2Dc', + }, + ], }) expect(fee).toEqual(expect.any(String)) @@ -99,14 +101,16 @@ describe('BSEthereum', () => { it.skip('Should be able to transfer a native token', async () => { const account = bsEthereum.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string) - const transactionHash = await bsEthereum.transfer({ + const [transactionHash] = await bsEthereum.transfer({ senderAccount: account, - intent: { - amount: '0.00000001', - receiverAddress: '0xFACf5446B71dB33E920aB1769d9427146183aEcd', - tokenDecimals: 18, - tokenHash: '-', - }, + intents: [ + { + amount: '0.00000001', + receiverAddress: '0xFACf5446B71dB33E920aB1769d9427146183aEcd', + tokenDecimals: 18, + tokenHash: '-', + }, + ], }) expect(transactionHash).toEqual(expect.any(String)) @@ -115,14 +119,16 @@ describe('BSEthereum', () => { it.skip('Should be able to transfer a ERC20 token', async () => { const account = bsEthereum.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string) - const transactionHash = await bsEthereum.transfer({ + const [transactionHash] = await bsEthereum.transfer({ senderAccount: account, - intent: { - amount: '0.00001', - receiverAddress: '0xFACf5446B71dB33E920aB1769d9427146183aEcd', - tokenDecimals: 6, - tokenHash: '0x1291070C5f838DCCDddc56312893d3EfE9B372a8', - }, + intents: [ + { + amount: '0.00001', + receiverAddress: '0xFACf5446B71dB33E920aB1769d9427146183aEcd', + tokenDecimals: 6, + tokenHash: '0x1291070C5f838DCCDddc56312893d3EfE9B372a8', + }, + ], }) expect(transactionHash).toEqual(expect.any(String)) @@ -133,14 +139,16 @@ describe('BSEthereum', () => { const service = new BSEthereum('neo3', network, async () => transport) const [account] = await service.ledgerService.getAccounts(transport) - const transactionHash = await service.transfer({ + const [transactionHash] = await service.transfer({ senderAccount: account, - intent: { - amount: '0.00000001', - receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', - tokenDecimals: 18, - tokenHash: '-', - }, + intents: [ + { + amount: '0.00000001', + receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', + tokenDecimals: 18, + tokenHash: '-', + }, + ], isLedger: true, }) @@ -148,20 +156,22 @@ describe('BSEthereum', () => { await transport.close() }, 50000) - it.only('Should be able to transfer a ERC20 token with ledger', async () => { + it.skip('Should be able to transfer a ERC20 token with ledger', async () => { const transport = await TransportNodeHid.create() const service = new BSEthereum('neo3', network, async () => transport) const [account] = await service.ledgerService.getAccounts(transport) - const transactionHash = await service.transfer({ + const [transactionHash] = await service.transfer({ senderAccount: account, - intent: { - amount: '0.00000001', - receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', - tokenDecimals: 18, - tokenHash: '0xcf185f2F3Fe19D82bFdcee59E3330FD7ba5f27ce', - }, + intents: [ + { + amount: '0.00000001', + receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', + tokenDecimals: 18, + tokenHash: '0xcf185f2F3Fe19D82bFdcee59E3330FD7ba5f27ce', + }, + ], isLedger: true, }) @@ -178,14 +188,17 @@ describe('BSEthereum', () => { const polygonNetwork = BSEthereumConstants.TESTNET_NETWORKS.find(network => network.id === '80002')! const service = new BSEthereum('neo3', polygonNetwork) const account = service.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string) - const transactionHash = await service.transfer({ + + const [transactionHash] = await service.transfer({ senderAccount: account, - intent: { - amount: '0.00000001', - receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', - tokenDecimals: 18, - tokenHash: '-', - }, + intents: [ + { + amount: '0.00000001', + receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', + tokenDecimals: 18, + tokenHash: '-', + }, + ], }) expect(transactionHash).toEqual(expect.any(String)) @@ -194,16 +207,67 @@ describe('BSEthereum', () => { it.skip('Should be able to transfer a native token using a EVM', async () => { const service = new BSEthereum('neo3', BSEthereumConstants.NEOX_TESTNET_NETWORK) const account = service.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string) - const transactionHash = await service.transfer({ + + const [transactionHash] = await service.transfer({ senderAccount: account, - intent: { - amount: '0.0000000000001', - receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', - tokenDecimals: 18, - tokenHash: '-', - }, + intents: [ + { + amount: '0.0000000000001', + receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', + tokenDecimals: 18, + tokenHash: '-', + }, + ], }) expect(transactionHash).toEqual(expect.any(String)) }, 60000) + + it.skip('Should be able to calculate transfer fee for more than one intent', async () => { + const account = bsEthereum.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string) + + const fee = await bsEthereum.calculateTransferFee({ + senderAccount: account, + intents: [ + { + amount: '0.00000001', + receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', + tokenDecimals: 18, + tokenHash: '-', + }, + { + amount: '0.00000001', + receiverAddress: '0xFACf5446B71dB33E920aB1769d9427146183aEcd', + tokenDecimals: 18, + tokenHash: '-', + }, + ], + }) + + expect(fee).toEqual(expect.any(String)) + }, 50000) + + it.only('Should be able to transfer more than one intent', async () => { + const account = bsEthereum.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string) + + const transactionHashes = await bsEthereum.transfer({ + senderAccount: account, + intents: [ + { + amount: '0.00000001', + receiverAddress: '0x82B5Cd984880C8A821429cFFf89f36D35BaeBE89', + tokenDecimals: 18, + tokenHash: '-', + }, + { + amount: '0.00000001', + receiverAddress: '0xFACf5446B71dB33E920aB1769d9427146183aEcd', + tokenDecimals: 18, + tokenHash: '-', + }, + ], + }) + + expect(transactionHashes).toHaveLength(2) + }, 50000) }) diff --git a/packages/bs-neo-legacy/src/__tests__/BDSNeoLegacy.spec.ts b/packages/bs-neo-legacy/src/__tests__/BDSNeoLegacy.spec.ts index a2a69a9..63a6846 100644 --- a/packages/bs-neo-legacy/src/__tests__/BDSNeoLegacy.spec.ts +++ b/packages/bs-neo-legacy/src/__tests__/BDSNeoLegacy.spec.ts @@ -1,7 +1,7 @@ import { BDSClaimable, BlockchainDataService } from '@cityofzion/blockchain-service' -import { DoraBDSNeoLegacy } from '../DoraBDSNeoLegacy' -import { BSNeoLegacyHelper } from '../BSNeoLegacyHelper' -import { BSNeoLegacyConstants } from '../BsNeoLegacyConstants' +import { BSNeoLegacyConstants } from '../constants/BSNeoLegacyConstants' +import { BSNeoLegacyHelper } from '../helpers/BSNeoLegacyHelper' +import { DoraBDSNeoLegacy } from '../services/blockchain-data/DoraBDSNeoLegacy' const network = BSNeoLegacyConstants.TESTNET_NETWORKS[0] const tokens = BSNeoLegacyHelper.getTokens(network) diff --git a/packages/bs-neo-legacy/src/__tests__/BSNeoLegacy.spec.ts b/packages/bs-neo-legacy/src/__tests__/BSNeoLegacy.spec.ts index 4a88ef2..da8ac8e 100644 --- a/packages/bs-neo-legacy/src/__tests__/BSNeoLegacy.spec.ts +++ b/packages/bs-neo-legacy/src/__tests__/BSNeoLegacy.spec.ts @@ -1,6 +1,6 @@ -import { BSNeoLegacy } from '../BSNeoLegacy' import { generateMnemonic } from '@cityofzion/bs-asteroid-sdk' -import { BSNeoLegacyConstants } from '../BsNeoLegacyConstants' +import { BSNeoLegacy } from '../services/BSNeoLegacy' +import { BSNeoLegacyConstants } from '../constants/BSNeoLegacyConstants' let bsNeoLegacy: BSNeoLegacy @@ -76,14 +76,16 @@ describe('BSNeoLegacy', () => { const gasBalance = balance.find(b => b.token.symbol === 'GAS')! expect(Number(gasBalance?.amount)).toBeGreaterThan(0.00000001) - const transactionHash = await bsNeoLegacy.transfer({ + const [transactionHash] = await bsNeoLegacy.transfer({ senderAccount: account, - intent: { - amount: '0.00000001', - receiverAddress: 'AQEQdmCcitFbE6oJU5Epa7dNxhTkCmTZST', - tokenHash: gasBalance.token.hash, - tokenDecimals: gasBalance.token.decimals, - }, + intents: [ + { + amount: '0.00000001', + receiverAddress: 'AQEQdmCcitFbE6oJU5Epa7dNxhTkCmTZST', + tokenHash: gasBalance.token.hash, + tokenDecimals: gasBalance.token.decimals, + }, + ], }) expect(transactionHash).toEqual(expect.any(String)) @@ -96,14 +98,16 @@ describe('BSNeoLegacy', () => { const LXBalance = balance.find(item => item.token.symbol === 'LX')! expect(Number(LXBalance?.amount)).toBeGreaterThan(0.00000001) - const transactionHash = await bsNeoLegacy.transfer({ + const [transactionHash] = await bsNeoLegacy.transfer({ senderAccount: account, - intent: { - amount: '0.00000001', - receiverAddress: 'AQEQdmCcitFbE6oJU5Epa7dNxhTkCmTZST', - tokenHash: LXBalance.token.hash, - tokenDecimals: LXBalance.token.decimals, - }, + intents: [ + { + amount: '0.00000001', + receiverAddress: 'AQEQdmCcitFbE6oJU5Epa7dNxhTkCmTZST', + tokenHash: LXBalance.token.hash, + tokenDecimals: LXBalance.token.decimals, + }, + ], }) expect(transactionHash).toEqual(expect.any(String)) @@ -118,14 +122,16 @@ describe('BSNeoLegacy', () => { const gasBalance = balance.find(item => item.token.symbol === bsNeoLegacy.feeToken.symbol)! expect(Number(gasBalance?.amount)).toBeGreaterThan(0.00000001) - const transactionHash = await bsNeoLegacy.transfer({ + const [transactionHash] = await bsNeoLegacy.transfer({ senderAccount: account, - intent: { - amount: '0.00000001', - receiverAddress: 'AQEQdmCcitFbE6oJU5Epa7dNxhTkCmTZST', - tokenHash: LXBalance.token.hash, - tokenDecimals: LXBalance.token.decimals, - }, + intents: [ + { + amount: '0.00000001', + receiverAddress: 'AQEQdmCcitFbE6oJU5Epa7dNxhTkCmTZST', + tokenHash: LXBalance.token.hash, + tokenDecimals: LXBalance.token.decimals, + }, + ], tipIntent: { amount: '0.00000001', receiverAddress: 'AQEQdmCcitFbE6oJU5Epa7dNxhTkCmTZST', @@ -136,4 +142,42 @@ describe('BSNeoLegacy', () => { expect(transactionHash).toEqual(expect.any(String)) }) + + it.only('Should be able to transfer more than one intent', async () => { + bsNeoLegacy.setNetwork(BSNeoLegacyConstants.DEFAULT_NETWORK) + const account = bsNeoLegacy.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string) + const balance = await bsNeoLegacy.blockchainDataService.getBalance(account.address) + + const LXBalance = balance.find(item => item.token.symbol === 'LX')! + expect(Number(LXBalance?.amount)).toBeGreaterThan(0.00000002) + + const gasBalance = balance.find(item => item.token.symbol === bsNeoLegacy.feeToken.symbol)! + expect(Number(gasBalance?.amount)).toBeGreaterThan(0.00000001) + + const [transactionHash] = await bsNeoLegacy.transfer({ + senderAccount: account, + intents: [ + { + amount: '0.00000001', + receiverAddress: 'AQEQdmCcitFbE6oJU5Epa7dNxhTkCmTZST', + tokenHash: LXBalance.token.hash, + tokenDecimals: LXBalance.token.decimals, + }, + { + amount: '0.00000001', + receiverAddress: 'AJybR5Uhwvs7WqGaruQ38dkyZkaKG9tyDK', + tokenHash: LXBalance.token.hash, + tokenDecimals: LXBalance.token.decimals, + }, + { + amount: '0.00000001', + receiverAddress: 'AQEQdmCcitFbE6oJU5Epa7dNxhTkCmTZST', + tokenHash: gasBalance.token.hash, + tokenDecimals: gasBalance.token.decimals, + }, + ], + }) + + expect(transactionHash).toEqual(expect.any(String)) + }) }) diff --git a/packages/bs-neo-legacy/src/__tests__/CryptoCompareExchange.spec.ts b/packages/bs-neo-legacy/src/__tests__/CryptoCompareExchange.spec.ts index 8a7a026..91270b3 100644 --- a/packages/bs-neo-legacy/src/__tests__/CryptoCompareExchange.spec.ts +++ b/packages/bs-neo-legacy/src/__tests__/CryptoCompareExchange.spec.ts @@ -1,7 +1,7 @@ import { Network } from '@cityofzion/blockchain-service' -import { BSNeoLegacyHelper } from '../BSNeoLegacyHelper' -import { CryptoCompareEDSNeoLegacy } from '../CryptoCompareEDSNeoLegacy' -import { BSNeoLegacyConstants, BSNeoLegacyNetworkId } from '../BsNeoLegacyConstants' +import { CryptoCompareEDSNeoLegacy } from '../services/exchange-data/CryptoCompareEDSNeoLegacy' +import { BSNeoLegacyConstants, BSNeoLegacyNetworkId } from '../constants/BSNeoLegacyConstants' +import { BSNeoLegacyHelper } from '../helpers/BSNeoLegacyHelper' let cryptoCompareEDSNeoLegacy: CryptoCompareEDSNeoLegacy let network: Network diff --git a/packages/bs-neo-legacy/src/__tests__/NeoTubeESNeoLegacy.spec.ts b/packages/bs-neo-legacy/src/__tests__/NeoTubeESNeoLegacy.spec.ts index 93deb7b..cebbf84 100644 --- a/packages/bs-neo-legacy/src/__tests__/NeoTubeESNeoLegacy.spec.ts +++ b/packages/bs-neo-legacy/src/__tests__/NeoTubeESNeoLegacy.spec.ts @@ -1,5 +1,5 @@ -import { BSNeoLegacyConstants } from '../BsNeoLegacyConstants' -import { NeoTubeESNeoLegacy } from '../NeoTubeESNeoLegacy' +import { BSNeoLegacyConstants } from '../constants/BSNeoLegacyConstants' +import { NeoTubeESNeoLegacy } from '../services/explorer/NeoTubeESNeoLegacy' let neoTubeESNeoLegacy: NeoTubeESNeoLegacy diff --git a/packages/bs-neo-legacy/src/BSNeoLegacyConstants.ts b/packages/bs-neo-legacy/src/constants/BSNeoLegacyConstants.ts similarity index 94% rename from packages/bs-neo-legacy/src/BSNeoLegacyConstants.ts rename to packages/bs-neo-legacy/src/constants/BSNeoLegacyConstants.ts index d5bccc5..da1c2df 100644 --- a/packages/bs-neo-legacy/src/BSNeoLegacyConstants.ts +++ b/packages/bs-neo-legacy/src/constants/BSNeoLegacyConstants.ts @@ -1,5 +1,5 @@ -import mainnetTokens from './assets/tokens/mainnet.json' -import commonTokens from './assets/tokens/common.json' +import mainnetTokens from '../assets/tokens/mainnet.json' +import commonTokens from '../assets/tokens/common.json' import { Network, NetworkId, Token } from '@cityofzion/blockchain-service' diff --git a/packages/bs-neo-legacy/src/BSNeoLegacyHelper.ts b/packages/bs-neo-legacy/src/helpers/BSNeoLegacyHelper.ts similarity index 86% rename from packages/bs-neo-legacy/src/BSNeoLegacyHelper.ts rename to packages/bs-neo-legacy/src/helpers/BSNeoLegacyHelper.ts index b07218f..c70ab80 100644 --- a/packages/bs-neo-legacy/src/BSNeoLegacyHelper.ts +++ b/packages/bs-neo-legacy/src/helpers/BSNeoLegacyHelper.ts @@ -1,6 +1,6 @@ import { Network } from '@cityofzion/blockchain-service' -import commonTokens from './assets/tokens/common.json' -import { BSNeoLegacyConstants, BSNeoLegacyNetworkId } from './BsNeoLegacyConstants' +import commonTokens from '../assets/tokens/common.json' +import { BSNeoLegacyConstants, BSNeoLegacyNetworkId } from '../constants/BSNeoLegacyConstants' export class BSNeoLegacyHelper { static getLegacyNetwork(network: Network) { diff --git a/packages/bs-neo-legacy/src/index.ts b/packages/bs-neo-legacy/src/index.ts index 2cd3cb9..de25b29 100644 --- a/packages/bs-neo-legacy/src/index.ts +++ b/packages/bs-neo-legacy/src/index.ts @@ -1,5 +1,11 @@ -export * from './BSNeoLegacy' -export * from './BSNeoLegacyHelper' -export * from './CryptoCompareEDSNeoLegacy' -export * from './DoraBDSNeoLegacy' -export * from './NeoTubeESNeoLegacy' +export * from './services/BSNeoLegacy' + +export * from './constants/BSNeoLegacyConstants' + +export * from './helpers/BSNeoLegacyHelper' + +export * from './services/blockchain-data/DoraBDSNeoLegacy' + +export * from './services/exchange-data/CryptoCompareEDSNeoLegacy' + +export * from './services/explorer/NeoTubeESNeoLegacy' diff --git a/packages/bs-neo-legacy/src/BSNeoLegacy.ts b/packages/bs-neo-legacy/src/services/BSNeoLegacy.ts similarity index 90% rename from packages/bs-neo-legacy/src/BSNeoLegacy.ts rename to packages/bs-neo-legacy/src/services/BSNeoLegacy.ts index 1c6b1c9..4f29f14 100644 --- a/packages/bs-neo-legacy/src/BSNeoLegacy.ts +++ b/packages/bs-neo-legacy/src/services/BSNeoLegacy.ts @@ -12,12 +12,12 @@ import { ExplorerService, } from '@cityofzion/blockchain-service' import { api, sc, u, wallet } from '@cityofzion/neon-js' -import { DoraBDSNeoLegacy } from './DoraBDSNeoLegacy' -import { CryptoCompareEDSNeoLegacy } from './CryptoCompareEDSNeoLegacy' import { keychain } from '@cityofzion/bs-asteroid-sdk' -import { BSNeoLegacyHelper } from './BSNeoLegacyHelper' -import { NeoTubeESNeoLegacy } from './NeoTubeESNeoLegacy' -import { BSNeoLegacyConstants, BSNeoLegacyNetworkId } from './BsNeoLegacyConstants' +import { BSNeoLegacyConstants, BSNeoLegacyNetworkId } from '../constants/BSNeoLegacyConstants' +import { BSNeoLegacyHelper } from '../helpers/BSNeoLegacyHelper' +import { CryptoCompareEDSNeoLegacy } from './exchange-data/CryptoCompareEDSNeoLegacy' +import { DoraBDSNeoLegacy } from './blockchain-data/DoraBDSNeoLegacy' +import { NeoTubeESNeoLegacy } from './explorer/NeoTubeESNeoLegacy' export class BSNeoLegacy implements BlockchainService, BSClaimable, BSWithExplorerService @@ -104,7 +104,7 @@ export class BSNeoLegacy return wallet.encrypt(key, password) } - async transfer({ intent: transferIntent, senderAccount, tipIntent, ...params }: TransferParam): Promise { + async transfer({ intents, senderAccount, tipIntent, ...params }: TransferParam): Promise { const apiProvider = new api.neoCli.instance(this.network.url) const account = new wallet.Account(senderAccount.key) const priorityFee = Number(params.priorityFee ?? 0) @@ -112,9 +112,9 @@ export class BSNeoLegacy const nativeIntents: ReturnType = [] const nep5ScriptBuilder = new sc.ScriptBuilder() - const intents = [transferIntent, ...(tipIntent ? [tipIntent] : [])] + const concatIntents = [...intents, ...(tipIntent ? [tipIntent] : [])] - for (const intent of intents) { + for (const intent of concatIntents) { const tokenHashFixed = BSNeoLegacyHelper.normalizeHash(intent.tokenHash) const nativeAsset = BSNeoLegacyConstants.NATIVE_ASSETS.find( @@ -159,7 +159,7 @@ export class BSNeoLegacy } if (!response.tx) throw new Error('Failed to send transaction') - return response.tx.hash + return [response.tx.hash] } async claim(account: Account): Promise { diff --git a/packages/bs-neo-legacy/src/DoraBDSNeoLegacy.ts b/packages/bs-neo-legacy/src/services/blockchain-data/DoraBDSNeoLegacy.ts similarity index 97% rename from packages/bs-neo-legacy/src/DoraBDSNeoLegacy.ts rename to packages/bs-neo-legacy/src/services/blockchain-data/DoraBDSNeoLegacy.ts index aab8fae..b2ddc31 100644 --- a/packages/bs-neo-legacy/src/DoraBDSNeoLegacy.ts +++ b/packages/bs-neo-legacy/src/services/blockchain-data/DoraBDSNeoLegacy.ts @@ -13,8 +13,8 @@ import { } from '@cityofzion/blockchain-service' import { api } from '@cityofzion/dora-ts' import { rpc } from '@cityofzion/neon-js' -import { BSNeoLegacyHelper } from './BSNeoLegacyHelper' -import { BSNeoLegacyNetworkId } from './BsNeoLegacyConstants' +import { BSNeoLegacyNetworkId } from '../../constants/BSNeoLegacyConstants' +import { BSNeoLegacyHelper } from '../../helpers/BSNeoLegacyHelper' export class DoraBDSNeoLegacy implements BlockchainDataService, BDSClaimable { readonly #network: Network diff --git a/packages/bs-neo-legacy/src/CryptoCompareEDSNeoLegacy.ts b/packages/bs-neo-legacy/src/services/exchange-data/CryptoCompareEDSNeoLegacy.ts similarity index 87% rename from packages/bs-neo-legacy/src/CryptoCompareEDSNeoLegacy.ts rename to packages/bs-neo-legacy/src/services/exchange-data/CryptoCompareEDSNeoLegacy.ts index b7f10dd..4dc0ce3 100644 --- a/packages/bs-neo-legacy/src/CryptoCompareEDSNeoLegacy.ts +++ b/packages/bs-neo-legacy/src/services/exchange-data/CryptoCompareEDSNeoLegacy.ts @@ -7,8 +7,8 @@ import { TokenPricesHistoryResponse, TokenPricesResponse, } from '@cityofzion/blockchain-service' -import { BSNeoLegacyHelper } from './BSNeoLegacyHelper' -import { BSNeoLegacyNetworkId } from './BsNeoLegacyConstants' +import { BSNeoLegacyNetworkId } from '../../constants/BSNeoLegacyConstants' +import { BSNeoLegacyHelper } from '../../helpers/BSNeoLegacyHelper' export class CryptoCompareEDSNeoLegacy extends CryptoCompareEDS implements ExchangeDataService { #network: Network diff --git a/packages/bs-neo-legacy/src/NeoTubeESNeoLegacy.ts b/packages/bs-neo-legacy/src/services/explorer/NeoTubeESNeoLegacy.ts similarity index 85% rename from packages/bs-neo-legacy/src/NeoTubeESNeoLegacy.ts rename to packages/bs-neo-legacy/src/services/explorer/NeoTubeESNeoLegacy.ts index afb68ba..e4cb89f 100644 --- a/packages/bs-neo-legacy/src/NeoTubeESNeoLegacy.ts +++ b/packages/bs-neo-legacy/src/services/explorer/NeoTubeESNeoLegacy.ts @@ -1,6 +1,6 @@ import { BuildNftUrlParams, ExplorerService, Network } from '@cityofzion/blockchain-service' -import { BSNeoLegacyHelper } from './BSNeoLegacyHelper' -import { BSNeoLegacyNetworkId } from './BsNeoLegacyConstants' +import { BSNeoLegacyNetworkId } from '../../constants/BSNeoLegacyConstants' +import { BSNeoLegacyHelper } from '../../helpers/BSNeoLegacyHelper' export class NeoTubeESNeoLegacy implements ExplorerService { #network: Network diff --git a/packages/bs-neo3/src/BSNeo3.ts b/packages/bs-neo3/src/BSNeo3.ts index 77daea8..62c2ecc 100644 --- a/packages/bs-neo3/src/BSNeo3.ts +++ b/packages/bs-neo3/src/BSNeo3.ts @@ -106,10 +106,10 @@ export class BSNeo3 } } - #buildTransferInvocation({ intent, tipIntent }: TransferParam, account: Neon.wallet.Account): ContractInvocation[] { - const intents = [intent, ...(tipIntent ? [tipIntent] : [])] + #buildTransferInvocation({ intents, tipIntent }: TransferParam, account: Neon.wallet.Account): ContractInvocation[] { + const concatIntents = [...intents, ...(tipIntent ? [tipIntent] : [])] - const invocations: ContractInvocation[] = intents.map(intent => { + const invocations: ContractInvocation[] = concatIntents.map(intent => { return { operation: 'transfer', scriptHash: intent.tokenHash, @@ -218,7 +218,7 @@ export class BSNeo3 return total.toString() } - async transfer(param: TransferParam): Promise { + async transfer(param: TransferParam): Promise { const { neonJsAccount, signingCallback } = await this.generateSigningCallback(param.senderAccount, param.isLedger) const invoker = await NeonInvoker.init({ @@ -234,7 +234,7 @@ export class BSNeo3 signers: [], }) - return transactionHash + return [transactionHash] } async claim(account: Account, isLedger?: boolean): Promise { diff --git a/packages/bs-neo3/src/__tests__/services/BSNeo3.spec.ts b/packages/bs-neo3/src/__tests__/services/BSNeo3.spec.ts index dc1a4fe..78b296f 100644 --- a/packages/bs-neo3/src/__tests__/services/BSNeo3.spec.ts +++ b/packages/bs-neo3/src/__tests__/services/BSNeo3.spec.ts @@ -3,6 +3,7 @@ import { generateMnemonic } from '@cityofzion/bs-asteroid-sdk' import TransportNodeHid from '@ledgerhq/hw-transport-node-hid' import { BSNeo3 } from '../../BSNeo3' import { BSNeo3Constants, BSNeo3NetworkId } from '../../constants/BSNeo3Constants' +import { BSNeo3Helper } from '../../helpers/BSNeo3Helper' let bsNeo3: BSNeo3 let network: Network @@ -90,19 +91,17 @@ describe('BSNeo3', () => { const fee = await bsNeo3.calculateTransferFee({ senderAccount: account, - intent: { - amount: '0.00000001', - receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', - tokenHash: bsNeo3.feeToken.hash, - tokenDecimals: bsNeo3.feeToken.decimals, - }, + intents: [ + { + amount: '0.00000001', + receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', + tokenHash: bsNeo3.feeToken.hash, + tokenDecimals: bsNeo3.feeToken.decimals, + }, + ], }) - expect(fee).toEqual({ - total: expect.any(Number), - networkFee: expect.any(Number), - systemFee: expect.any(Number), - }) + expect(fee).toEqual(expect.any(String)) }) it.skip('Should be able to transfer', async () => { @@ -111,14 +110,16 @@ describe('BSNeo3', () => { const gasBalance = balance.find(b => b.token.symbol === bsNeo3.feeToken.symbol) expect(Number(gasBalance?.amount)).toBeGreaterThan(0.00000001) - const transactionHash = await bsNeo3.transfer({ + const [transactionHash] = await bsNeo3.transfer({ senderAccount: account, - intent: { - amount: '0.00000001', - receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', - tokenHash: bsNeo3.feeToken.hash, - tokenDecimals: bsNeo3.feeToken.decimals, - }, + intents: [ + { + amount: '0.00000001', + receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', + tokenHash: bsNeo3.feeToken.hash, + tokenDecimals: bsNeo3.feeToken.decimals, + }, + ], }) expect(transactionHash).toEqual(expect.any(String)) @@ -134,17 +135,19 @@ describe('BSNeo3', () => { const gasBalance = balance.find(b => b.token.symbol === service.feeToken.symbol) expect(Number(gasBalance?.amount)).toBeGreaterThan(0.00000001) - const transactionHash = await service.transfer({ + const [transactionHash] = await service.transfer({ senderAccount: account, - intent: { - amount: '1', - receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', - tokenHash: service.feeToken.hash, - tokenDecimals: service.feeToken.decimals, - }, + intents: [ + { + amount: '1', + receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', + tokenHash: service.feeToken.hash, + tokenDecimals: service.feeToken.decimals, + }, + ], isLedger: true, }) - + transport.close() expect(transactionHash).toEqual(expect.any(String)) }, 60000) @@ -161,4 +164,104 @@ describe('BSNeo3', () => { const owner = await bsNeo3.resolveNameServiceDomain('neo.neo') expect(owner).toEqual('Nj39M97Rk2e23JiULBBMQmvpcnKaRHqxFf') }) + + it.skip('Should be able to calculate transfer fee more than one intent', async () => { + const account = bsNeo3.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string) + const NEO = BSNeo3Helper.getTokens(network).find(token => token.symbol === 'NEO')! + const GAS = BSNeo3Helper.getTokens(network).find(token => token.symbol === 'GAS')! + + const fee = await bsNeo3.calculateTransferFee({ + senderAccount: account, + intents: [ + { + amount: '0.00000001', + receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', + tokenHash: GAS.hash, + tokenDecimals: GAS.decimals, + }, + { + amount: '1', + receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', + tokenHash: NEO.hash, + tokenDecimals: NEO.decimals, + }, + ], + }) + + expect(fee).toEqual(expect.any(String)) + }) + + it.skip('Should be able to transfer more than one intent', async () => { + const account = bsNeo3.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string) + const balance = await bsNeo3.blockchainDataService.getBalance(account.address) + + const NEO = BSNeo3Helper.getTokens(network).find(token => token.symbol === 'NEO')! + const GAS = BSNeo3Helper.getTokens(network).find(token => token.symbol === 'GAS')! + + const gasBalance = balance.find(b => b.token.symbol === GAS.symbol) + expect(Number(gasBalance?.amount)).toBeGreaterThan(0.00000001) + + const neoBalance = balance.find(b => b.token.symbol === NEO.symbol) + expect(Number(neoBalance?.amount)).toBeGreaterThan(1) + + const [transactionHash] = await bsNeo3.transfer({ + senderAccount: account, + intents: [ + { + amount: '0.00000001', + receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', + tokenHash: GAS.hash, + tokenDecimals: GAS.decimals, + }, + { + amount: '1', + receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', + tokenHash: NEO.hash, + tokenDecimals: NEO.decimals, + }, + ], + }) + + expect(transactionHash).toEqual(expect.any(String)) + }) + + it.only('Should be able to transfer more than one intent with ledger', async () => { + const transport = await TransportNodeHid.create() + const service = new BSNeo3('neo3', network, async () => transport) + + const account = await service.ledgerService.getAccount(transport, 0) + + const balance = await service.blockchainDataService.getBalance(account.address) + + const NEO = BSNeo3Helper.getTokens(network).find(token => token.symbol === 'NEO')! + const GAS = BSNeo3Helper.getTokens(network).find(token => token.symbol === 'GAS')! + + const gasBalance = balance.find(b => b.token.symbol === GAS.symbol) + expect(Number(gasBalance?.amount)).toBeGreaterThan(0.00000001) + + const neoBalance = balance.find(b => b.token.symbol === NEO.symbol) + expect(Number(neoBalance?.amount)).toBeGreaterThan(1) + + const [transactionHash] = await service.transfer({ + senderAccount: account, + intents: [ + { + amount: '0.00000001', + receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', + tokenHash: GAS.hash, + tokenDecimals: GAS.decimals, + }, + { + amount: '1', + receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe', + tokenHash: NEO.hash, + tokenDecimals: NEO.decimals, + }, + ], + isLedger: true, + }) + + transport.close() + expect(transactionHash).toEqual(expect.any(String)) + }, 60000) })