diff --git a/backend/src/apis/dltConnector/DltConnectorClient.ts b/backend/src/apis/dltConnector/DltConnectorClient.ts index f864bb652..5bdf60c35 100644 --- a/backend/src/apis/dltConnector/DltConnectorClient.ts +++ b/backend/src/apis/dltConnector/DltConnectorClient.ts @@ -9,6 +9,7 @@ import { backendLogger as logger } from '@/server/logger' // eslint-disable-next-line import/named, n/no-extraneous-import import { TransactionDraft } from './model/TransactionDraft' +import { TransactionLinkDraft } from './model/TransactionLinkDraft' import { TransactionResult } from './model/TransactionResult' import { UserAccountDraft } from './model/UserAccountDraft' @@ -29,6 +30,23 @@ const sendTransaction = gql` } ` +const deferredTransfer = gql` + mutation ($input: TransactionLinkDraft!) { + deferredTransfer(data: $input) { + error { + message + name + } + succeed + recipe { + createdAt + type + messageIdHex + } + } + } +` + const registerAddress = gql` mutation ($input: UserAccountDraft!) { registerAddress(data: $input) { @@ -114,6 +132,15 @@ export class DltConnectorClient { return this.handleTransactionResult(result) } + private async deferredTransfer(input: TransactionLinkDraft) { + const { + data: { deferredTransfer: result }, + } = await this.client.rawRequest<{ deferredTransfer: TransactionResult }>(deferredTransfer, { + input, + }) + return this.handleTransactionResult(result) + } + private async registerAddress(input: UserAccountDraft) { const { data: { registerAddress: result }, @@ -128,7 +155,7 @@ export class DltConnectorClient { * and update dltTransactionId of transaction in db with iota message id */ public async transmitTransaction( - input: TransactionDraft | UserAccountDraft, + input: TransactionDraft | UserAccountDraft | TransactionLinkDraft, ): Promise { // we don't need the receive transactions, there contain basically the same data as the send transactions if ( @@ -144,6 +171,8 @@ export class DltConnectorClient { return await this.sendTransaction(input) } else if (input instanceof UserAccountDraft) { return await this.registerAddress(input) + } else if (input instanceof TransactionLinkDraft) { + return await this.deferredTransfer(input) } else { throw new LogError('unhandled branch reached') } diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts index 50296244e..c1a73edd0 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts @@ -3,6 +3,7 @@ import { ObjectLiteral, OrderByCondition, SelectQueryBuilder } from '@dbTools/ty import { DltTransaction } from '@entity/DltTransaction' import { TransactionDraft } from '@dltConnector/model/TransactionDraft' +import { TransactionLinkDraft } from '@dltConnector/model/TransactionLinkDraft' import { UserAccountDraft } from '@dltConnector/model/UserAccountDraft' import { backendLogger as logger } from '@/server/logger' @@ -13,7 +14,11 @@ export abstract class AbstractTransactionToDltRole { // public interface public abstract initWithLast(): Promise public abstract getTimestamp(): number - public abstract convertToGraphqlInput(): TransactionDraft | UserAccountDraft + public abstract convertToGraphqlInput(): + | TransactionDraft + | UserAccountDraft + | TransactionLinkDraft + public getEntity(): T | null { return this.self } diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts index 1b0ac97e6..5b1efdad6 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts @@ -2,6 +2,7 @@ import { DltTransaction } from '@entity/DltTransaction' import { TransactionLink } from '@entity/TransactionLink' import { TransactionDraft } from '@dltConnector/model/TransactionDraft' +import { TransactionLinkDraft } from '@dltConnector/model/TransactionLinkDraft' import { UserAccountDraft } from '@dltConnector/model/UserAccountDraft' import { LogError } from '@/server/LogError' @@ -29,11 +30,11 @@ export class TransactionLinkToDltRole extends AbstractTransactionToDltRole { return this.self.createdAt.getTime() } - public convertToGraphqlInput(): TransactionDraft | UserAccountDraft { + public convertToGraphqlInput(): TransactionDraft | UserAccountDraft | TransactionLinkDraft { if (!this.self) { throw new LogError('try to create dlt entry for empty transaction') } diff --git a/backend/src/apis/dltConnector/model/IdentifierSeed.ts b/backend/src/apis/dltConnector/model/IdentifierSeed.ts deleted file mode 100644 index c597d2797..000000000 --- a/backend/src/apis/dltConnector/model/IdentifierSeed.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class IdentifierSeed { - seed: string - - constructor(seed: string) { - this.seed = seed - } -} diff --git a/backend/src/apis/dltConnector/model/TransactionDraft.ts b/backend/src/apis/dltConnector/model/TransactionDraft.ts index 7413969eb..e31003986 100755 --- a/backend/src/apis/dltConnector/model/TransactionDraft.ts +++ b/backend/src/apis/dltConnector/model/TransactionDraft.ts @@ -1,52 +1,39 @@ // https://www.npmjs.com/package/@apollo/protobufjs import { Transaction } from '@entity/Transaction' -import { TransactionLink } from '@entity/TransactionLink' import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId' import { LogError } from '@/server/LogError' -import { IdentifierSeed } from './IdentifierSeed' import { UserIdentifier } from './UserIdentifier' export class TransactionDraft { user: UserIdentifier - linkedUser: UserIdentifier | IdentifierSeed + linkedUser: UserIdentifier amount: string type: string createdAt: string // only for creation transactions targetDate?: string - // only for transaction links - timeoutDate?: string - constructor(transaction: Transaction | TransactionLink) { + constructor(transaction: Transaction) { this.amount = transaction.amount.abs().toString() - if (transaction instanceof Transaction) { - if ( - !transaction.linkedUserGradidoID || - !transaction.linkedUserCommunityUuid || - !transaction.userCommunityUuid - ) { - throw new LogError( - `missing necessary field in transaction: ${transaction.id}, need linkedUserGradidoID, linkedUserCommunityUuid and userCommunityUuid`, - ) - } - this.user = new UserIdentifier(transaction.userGradidoID, transaction.userCommunityUuid) - this.linkedUser = new UserIdentifier( - transaction.linkedUserGradidoID, - transaction.linkedUserCommunityUuid, + if ( + !transaction.linkedUserGradidoID || + !transaction.linkedUserCommunityUuid || + !transaction.userCommunityUuid + ) { + throw new LogError( + `missing necessary field in transaction: ${transaction.id}, need linkedUserGradidoID, linkedUserCommunityUuid and userCommunityUuid`, ) - this.createdAt = transaction.balanceDate.toISOString() - this.targetDate = transaction.creationDate?.toISOString() - this.type = TransactionTypeId[transaction.typeId] - } else if (transaction instanceof TransactionLink) { - const user = transaction.user - this.user = new UserIdentifier(user.gradidoID, user.communityUuid) - this.linkedUser = new IdentifierSeed(transaction.code) - this.createdAt = transaction.createdAt.toISOString() - this.type = TransactionTypeId[TransactionTypeId.LINK_SUMMARY] - this.timeoutDate = transaction.validUntil.toISOString() } + this.user = new UserIdentifier(transaction.userGradidoID, transaction.userCommunityUuid) + this.linkedUser = new UserIdentifier( + transaction.linkedUserGradidoID, + transaction.linkedUserCommunityUuid, + ) + this.createdAt = transaction.balanceDate.toISOString() + this.targetDate = transaction.creationDate?.toISOString() + this.type = TransactionTypeId[transaction.typeId] } } diff --git a/backend/src/apis/dltConnector/model/TransactionLinkDraft.ts b/backend/src/apis/dltConnector/model/TransactionLinkDraft.ts new file mode 100644 index 000000000..1a12f552b --- /dev/null +++ b/backend/src/apis/dltConnector/model/TransactionLinkDraft.ts @@ -0,0 +1,21 @@ +// https://www.npmjs.com/package/@apollo/protobufjs +import { TransactionLink } from '@entity/TransactionLink' + +import { UserIdentifier } from './UserIdentifier' + +export class TransactionLinkDraft { + user: UserIdentifier + seed: string + amount: string + createdAt: string + timeoutDate: string + + constructor(transaction: TransactionLink) { + this.amount = transaction.amount.abs().toString() + const user = transaction.user + this.user = new UserIdentifier(user.gradidoID, user.communityUuid) + this.seed = transaction.code + this.createdAt = transaction.createdAt.toISOString() + this.timeoutDate = transaction.validUntil.toISOString() + } +} diff --git a/dlt-connector/src/graphql/enum/InputTransactionType.ts b/dlt-connector/src/graphql/enum/InputTransactionType.ts index 41eeac6cb..94cd39ff3 100755 --- a/dlt-connector/src/graphql/enum/InputTransactionType.ts +++ b/dlt-connector/src/graphql/enum/InputTransactionType.ts @@ -6,6 +6,9 @@ export enum InputTransactionType { CREATION = 1, SEND = 2, RECEIVE = 3, + // This is a virtual property, never occurring on the database + DECAY = 4, + LINK_SUMMARY = 5, } registerEnumType(InputTransactionType, { diff --git a/dlt-connector/src/graphql/input/IdentifierSeed.ts b/dlt-connector/src/graphql/input/IdentifierSeed.ts index 41e7a31cd..f09ad2f8b 100644 --- a/dlt-connector/src/graphql/input/IdentifierSeed.ts +++ b/dlt-connector/src/graphql/input/IdentifierSeed.ts @@ -1,9 +1,15 @@ +// https://www.npmjs.com/package/@apollo/protobufjs + import { IsString } from 'class-validator' -import { InputType, Field } from 'type-graphql' +import { Field, InputType } from 'type-graphql' @InputType() export class IdentifierSeed { @Field(() => String) @IsString() seed: string + + constructor(seed: string) { + this.seed = seed + } } diff --git a/dlt-connector/src/graphql/input/TransactionDraft.ts b/dlt-connector/src/graphql/input/TransactionDraft.ts index 393d5da93..d1fa48c3c 100755 --- a/dlt-connector/src/graphql/input/TransactionDraft.ts +++ b/dlt-connector/src/graphql/input/TransactionDraft.ts @@ -1,11 +1,9 @@ // https://www.npmjs.com/package/@apollo/protobufjs +import { InputTransactionType } from '@enum/InputTransactionType' +import { isValidDateString, isValidNumberString } from '@validator/DateString' import { IsEnum, IsObject, ValidateNested } from 'class-validator' import { InputType, Field } from 'type-graphql' -import { InputTransactionType } from '@enum/InputTransactionType' -import { isValidDateString, isValidNumberString } from '@validator/DateString' - -import { IdentifierSeed } from './IdentifierSeed' import { UserIdentifier } from './UserIdentifier' @InputType() @@ -18,7 +16,7 @@ export class TransactionDraft { @Field(() => UserIdentifier) @IsObject() @ValidateNested() - linkedUser: UserIdentifier | IdentifierSeed + linkedUser: UserIdentifier @Field(() => String) @isValidNumberString() @@ -36,9 +34,4 @@ export class TransactionDraft { @Field(() => String, { nullable: true }) @isValidDateString() targetDate?: string - - // only for transaction links - @Field(() => String, { nullable: true }) - @isValidDateString() - timeoutDate?: string } diff --git a/dlt-connector/src/graphql/input/TransactionLinkDraft.ts b/dlt-connector/src/graphql/input/TransactionLinkDraft.ts new file mode 100644 index 000000000..7ac621e35 --- /dev/null +++ b/dlt-connector/src/graphql/input/TransactionLinkDraft.ts @@ -0,0 +1,30 @@ +// https://www.npmjs.com/package/@apollo/protobufjs +import { isValidDateString, isValidNumberString } from '@validator/DateString' +import { IsObject, IsString, ValidateNested } from 'class-validator' +import { InputType, Field } from 'type-graphql' + +import { UserIdentifier } from './UserIdentifier' + +@InputType() +export class TransactionLinkDraft { + @Field(() => UserIdentifier) + @IsObject() + @ValidateNested() + user: UserIdentifier + + @Field(() => String) + @IsString() + seed: string + + @Field(() => String) + @isValidNumberString() + amount: string + + @Field(() => String) + @isValidDateString() + createdAt: string + + @Field(() => String) + @isValidDateString() + timeoutDate: string +} diff --git a/dlt-connector/src/graphql/resolver/TransactionsResolver.ts b/dlt-connector/src/graphql/resolver/TransactionsResolver.ts index e5e537ad9..8d3d5970b 100755 --- a/dlt-connector/src/graphql/resolver/TransactionsResolver.ts +++ b/dlt-connector/src/graphql/resolver/TransactionsResolver.ts @@ -1,9 +1,9 @@ +import { TransactionLinkDraft } from '@input/TransactionLinkDraft' import { Resolver, Arg, Mutation } from 'type-graphql' -import { TransactionDraft } from '@input/TransactionDraft' - import { SendToIotaContext } from '@/interactions/sendToIota/SendToIota.context' +import { TransactionDraft } from '../input/TransactionDraft' import { TransactionError } from '../model/TransactionError' import { TransactionResult } from '../model/TransactionResult' @@ -25,4 +25,21 @@ export class TransactionResolver { } } } + + @Mutation(() => TransactionResult) + async deferredTransfer( + @Arg('data') + transactionLinkDraft: TransactionLinkDraft, + ): Promise { + try { + return await SendToIotaContext(transactionLinkDraft) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error instanceof TransactionError) { + return new TransactionResult(error) + } else { + throw error + } + } + } } diff --git a/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts index 775c60cb7..c448a1704 100644 --- a/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts @@ -1,6 +1,5 @@ import { GradidoTransactionBuilder, TransferAmount } from 'gradido-blockchain-js' -import { IdentifierSeed } from '@/graphql/input/IdentifierSeed' import { TransactionDraft } from '@/graphql/input/TransactionDraft' import { LogError } from '@/server/LogError' @@ -22,9 +21,6 @@ export class CreationTransactionRole extends AbstractTransactionRole { } public async getGradidoTransactionBuilder(): Promise { - if (this.self.linkedUser instanceof IdentifierSeed) { - throw new LogError('invalid recipient, it is a IdentifierSeed instead of a UserIdentifier') - } if (!this.self.targetDate) { throw new LogError('target date missing for creation transaction') } diff --git a/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts index ff252f59d..7a4c627a1 100644 --- a/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts @@ -1,27 +1,30 @@ import { GradidoTransactionBuilder, GradidoTransfer, TransferAmount } from 'gradido-blockchain-js' -import { UserIdentifier } from '@/graphql/input/UserIdentifier' +import { IdentifierSeed } from '@/graphql/input/IdentifierSeed' +import { TransactionLinkDraft } from '@/graphql/input/TransactionLinkDraft' import { LogError } from '@/server/LogError' import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context' -import { TransferTransactionRole } from './TransferTransaction.role' +import { AbstractTransactionRole } from './AbstractTransaction.role' + +export class DeferredTransferTransactionRole extends AbstractTransactionRole { + constructor(protected self: TransactionLinkDraft) { + super() + } + + getSenderCommunityUuid(): string { + return this.self.user.communityUuid + } -export class DeferredTransferTransactionRole extends TransferTransactionRole { getRecipientCommunityUuid(): string { throw new LogError('cannot be used as cross group transaction') } public async getGradidoTransactionBuilder(): Promise { - if (this.self.linkedUser instanceof UserIdentifier) { - throw new LogError('invalid recipient, it is a UserIdentifier instead of a IdentifierSeed') - } - if (!this.self.timeoutDate) { - throw new LogError('timeoutDate date missing for deferred transfer transaction') - } const builder = new GradidoTransactionBuilder() const senderKeyPair = await KeyPairCalculation(this.self.user) - const recipientKeyPair = await KeyPairCalculation(this.self.linkedUser) + const recipientKeyPair = await KeyPairCalculation(new IdentifierSeed(this.self.seed)) builder .setCreatedAt(new Date(this.self.createdAt)) diff --git a/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts b/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts index a10a95c35..caf4df11f 100644 --- a/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts +++ b/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts @@ -12,6 +12,7 @@ import { InputTransactionType } from '@/graphql/enum/InputTransactionType' import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType' import { CommunityDraft } from '@/graphql/input/CommunityDraft' import { TransactionDraft } from '@/graphql/input/TransactionDraft' +import { TransactionLinkDraft } from '@/graphql/input/TransactionLinkDraft' import { UserAccountDraft } from '@/graphql/input/UserAccountDraft' import { TransactionError } from '@/graphql/model/TransactionError' import { TransactionRecipe } from '@/graphql/model/TransactionRecipe' @@ -23,6 +24,7 @@ import { uuid4ToHash } from '@/utils/typeConverter' import { AbstractTransactionRole } from './AbstractTransaction.role' import { CommunityRootTransactionRole } from './CommunityRootTransaction.role' import { CreationTransactionRole } from './CreationTransaction.role' +import { DeferredTransferTransactionRole } from './DeferredTransferTransaction.role' import { RegisterAddressTransactionRole } from './RegisterAddressTransaction.role' import { TransferTransactionRole } from './TransferTransaction.role' @@ -32,7 +34,7 @@ import { TransferTransactionRole } from './TransferTransaction.role' * send every transaction only once to iota! */ export async function SendToIotaContext( - input: TransactionDraft | UserAccountDraft | CommunityDraft, + input: TransactionDraft | UserAccountDraft | CommunityDraft | TransactionLinkDraft, ): Promise { const validate = (transaction: GradidoTransaction): void => { try { @@ -81,6 +83,8 @@ export async function SendToIotaContext( } else { throw new LogError('not supported transaction type') } + } else if (input instanceof TransactionLinkDraft) { + role = new DeferredTransferTransactionRole(input) } else if (input instanceof UserAccountDraft) { role = new RegisterAddressTransactionRole(input) } else if (input instanceof CommunityDraft) { diff --git a/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts index 16a2ec7ee..1e5dde5dd 100644 --- a/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts @@ -1,8 +1,6 @@ import { GradidoTransactionBuilder, TransferAmount } from 'gradido-blockchain-js' -import { IdentifierSeed } from '@/graphql/input/IdentifierSeed' import { TransactionDraft } from '@/graphql/input/TransactionDraft' -import { LogError } from '@/server/LogError' import { uuid4ToHash } from '@/utils/typeConverter' import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context' @@ -19,17 +17,10 @@ export class TransferTransactionRole extends AbstractTransactionRole { } getRecipientCommunityUuid(): string { - if (this.self.linkedUser instanceof IdentifierSeed) { - throw new LogError('invalid recipient, it is a IdentifierSeed instead of a UserIdentifier') - } return this.self.linkedUser.communityUuid } public async getGradidoTransactionBuilder(): Promise { - if (this.self.linkedUser instanceof IdentifierSeed) { - throw new LogError('invalid recipient, it is a IdentifierSeed instead of a UserIdentifier') - } - const builder = new GradidoTransactionBuilder() const senderKeyPair = await KeyPairCalculation(this.self.user) const recipientKeyPair = await KeyPairCalculation(this.self.linkedUser)