diff --git a/src/confidential-accounts/confidential-accounts.controller.spec.ts b/src/confidential-accounts/confidential-accounts.controller.spec.ts index aef1023f..767abcfc 100644 --- a/src/confidential-accounts/confidential-accounts.controller.spec.ts +++ b/src/confidential-accounts/confidential-accounts.controller.spec.ts @@ -224,4 +224,25 @@ describe('ConfidentialAccountsController', () => { ); }); }); + + describe('moveFunds', () => { + it('should call the service and return the results', async () => { + const input = { + signer, + moves: [ + { + from: '0xfrom', + to: '0xto', + proofs: [{ asset: 'someAsset', proof: 'someProof' }], + }, + ], + }; + mockConfidentialAccountsService.moveFunds.mockResolvedValue( + txResult as unknown as ServiceReturn + ); + + const result = await controller.moveFunds(input); + expect(result).toEqual(txResult); + }); + }); }); diff --git a/src/confidential-accounts/confidential-accounts.controller.ts b/src/confidential-accounts/confidential-accounts.controller.ts index 5fcae7a1..937ba27a 100644 --- a/src/confidential-accounts/confidential-accounts.controller.ts +++ b/src/confidential-accounts/confidential-accounts.controller.ts @@ -12,6 +12,7 @@ import { IncomingConfidentialAssetBalance } from '@polymeshassociation/polymesh- import { ConfidentialAccountsService } from '~/confidential-accounts/confidential-accounts.service'; import { ConfidentialAccountParamsDto } from '~/confidential-accounts/dto/confidential-account-params.dto'; +import { MoveFundsDto } from '~/confidential-accounts/dto/move-funds.dto'; import { TransactionHistoryParamsDto } from '~/confidential-accounts/dto/transaction-history-params.dto'; import { AppliedConfidentialAssetBalanceModel } from '~/confidential-accounts/models/applied-confidential-asset-balance.model'; import { AppliedConfidentialAssetBalancesModel } from '~/confidential-accounts/models/applied-confidential-asset-balances.model'; @@ -331,4 +332,36 @@ export class ConfidentialAccountsController { next, }); } + + @ApiOperation({ + summary: 'Move funds between Confidential Accounts owned by the signing Identity', + description: 'This endpoint moves funds between Confidential Accounts of the Signing Identity', + }) + @ApiNotFoundResponse({ + description: 'No Confidential Account was found', + }) + @ApiTransactionFailedResponse({ + [HttpStatus.NOT_FOUND]: [ + 'The Confidential Asset does not exists', + 'Sender Confidential Account does not exists', + 'Receiver Confidential Account does not exists', + ], + [HttpStatus.UNPROCESSABLE_ENTITY]: [ + 'The provided accounts must have identities associated with them', + 'Only the owner of the sender account can move funds', + 'The provided accounts must have the same identity', + 'The asset is frozen', + 'The sender account is frozen for trading specified asset', + 'The receiver account is frozen for trading specified asset', + ], + }) + @ApiTransactionResponse({ + description: 'Details about the transaction', + }) + @Post('move-funds') + public async moveFunds(@Body() args: MoveFundsDto): Promise { + const result = await this.confidentialAccountsService.moveFunds(args); + + return handleServiceResult(result); + } } diff --git a/src/confidential-accounts/confidential-accounts.service.spec.ts b/src/confidential-accounts/confidential-accounts.service.spec.ts index c27ef420..3413d347 100644 --- a/src/confidential-accounts/confidential-accounts.service.spec.ts +++ b/src/confidential-accounts/confidential-accounts.service.spec.ts @@ -360,4 +360,39 @@ describe('ConfidentialAccountsService', () => { expect(result).toEqual(mockHistory); }); }); + + describe('moveFunds', () => { + it('create a transaction and return service result', async () => { + const input = { + signer, + moves: [ + { + from: '0x1', + to: '0x2', + proofs: [{ asset: 'someAsset', proof: 'SomeProof' }], + }, + ], + }; + + const mockTransactions = { + blockHash: '0x1', + txHash: '0x2', + blockNumber: new BigNumber(1), + tag: TxTags.confidentialAsset.MoveAssets, + }; + const mockTransaction = new MockTransaction(mockTransactions); + + mockTransactionsService.submit.mockResolvedValue({ + result: undefined, + transactions: [mockTransaction], + }); + + const result = await service.moveFunds(input); + + expect(result).toEqual({ + result: undefined, + transactions: [mockTransaction], + }); + }); + }); }); diff --git a/src/confidential-accounts/confidential-accounts.service.ts b/src/confidential-accounts/confidential-accounts.service.ts index e6b2b686..6bbd448d 100644 --- a/src/confidential-accounts/confidential-accounts.service.ts +++ b/src/confidential-accounts/confidential-accounts.service.ts @@ -12,6 +12,7 @@ import { ResultSet, } from '@polymeshassociation/polymesh-private-sdk/types'; +import { MoveFundsDto } from '~/confidential-accounts/dto/move-funds.dto'; import { ConfidentialAssetBalanceModel } from '~/confidential-accounts/models/confidential-asset-balance.model'; import { ConfidentialTransactionDirectionEnum } from '~/confidential-transactions/types'; import { PolymeshService } from '~/polymesh/polymesh.service'; @@ -152,4 +153,13 @@ export class ConfidentialAccountsService { return account.getTransactionHistory(filters); } + + public async moveFunds(params: MoveFundsDto): ServiceReturn { + const { options, args } = extractTxOptions(params); + const { moves } = args; + + const confidentialAccounts = this.polymeshService.polymeshApi.confidentialAccounts; + + return this.transactionsService.submit(confidentialAccounts.moveFunds, moves, options); + } } diff --git a/src/confidential-accounts/dto/fund-moves.dto.ts b/src/confidential-accounts/dto/fund-moves.dto.ts new file mode 100644 index 00000000..856b05f6 --- /dev/null +++ b/src/confidential-accounts/dto/fund-moves.dto.ts @@ -0,0 +1,32 @@ +/* istanbul ignore file */ + +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsString } from 'class-validator'; + +import { ProofDto } from '~/confidential-accounts/dto/proof.dto'; + +export class FundMovesDto { + @ApiProperty({ + description: 'The Confidential Account from which to move the funds from', + example: '0xdeadbeef00000000000000000000000000000000000000000000000000000000', + type: 'string', + }) + @IsString() + readonly from: string; + + @ApiProperty({ + description: 'The Confidential Account to which to move the funds to', + example: '0xdeadbeef00000000000000000000000000000000000000000000000000000000', + type: 'string', + }) + @IsString() + readonly to: string; + + @ApiProperty({ + description: 'Proofs of the transaction', + type: ProofDto, + isArray: true, + }) + @IsArray() + proofs: ProofDto[]; +} diff --git a/src/confidential-accounts/dto/move-funds.dto.ts b/src/confidential-accounts/dto/move-funds.dto.ts new file mode 100644 index 00000000..62789a17 --- /dev/null +++ b/src/confidential-accounts/dto/move-funds.dto.ts @@ -0,0 +1,17 @@ +/* istanbul ignore file */ + +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { FundMovesDto } from '~/confidential-accounts/dto/fund-moves.dto'; +import { TransactionBaseDto } from '~/polymesh-rest-api/src/common/dto/transaction-base-dto'; + +export class MoveFundsDto extends TransactionBaseDto { + @ApiProperty({ + description: 'Asset moves', + type: FundMovesDto, + isArray: true, + }) + @IsArray() + moves: FundMovesDto[]; +} diff --git a/src/confidential-accounts/dto/proof.dto.ts b/src/confidential-accounts/dto/proof.dto.ts new file mode 100644 index 00000000..524777aa --- /dev/null +++ b/src/confidential-accounts/dto/proof.dto.ts @@ -0,0 +1,25 @@ +/* istanbul ignore file */ + +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +import { IsConfidentialAssetId } from '~/confidential-assets/decorators/validation'; + +export class ProofDto { + @ApiProperty({ + description: 'The confidential Asset IDs to be moved.', + type: 'string', + isArray: true, + example: ['76702175-d8cb-e3a5-5a19-734433351e25'], + }) + @IsConfidentialAssetId() + readonly asset: string; + + @ApiProperty({ + description: 'The Proof of the transaction', + example: '0xdeadbeef00000000000000000000000000000000000000000000000000000000', + type: 'string', + }) + @IsString() + readonly proof: string; +}