mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
finalize refactoring, change for using hiero instead of iota
This commit is contained in:
parent
6e2269a499
commit
eb29dde8c3
154
dlt-connector/biome.json
Normal file
154
dlt-connector/biome.json
Normal file
@ -0,0 +1,154 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
|
||||
"vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": [
|
||||
"**/package.json",
|
||||
"src/**/*.js",
|
||||
"src/**/*.ts",
|
||||
"!**/build",
|
||||
"!**/node_modules",
|
||||
"!**/coverage"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"useEditorconfig": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 100,
|
||||
"attributePosition": "auto",
|
||||
"bracketSpacing": true
|
||||
},
|
||||
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": false,
|
||||
"complexity": {
|
||||
"noExtraBooleanCast": "error",
|
||||
"noUselessCatch": "error",
|
||||
"noUselessConstructor": "error",
|
||||
"noUselessLoneBlockStatements": "error",
|
||||
"noUselessRename": "error",
|
||||
"noUselessTernary": "error",
|
||||
"noUselessUndefinedInitialization": "error",
|
||||
"noVoid": "error",
|
||||
"useLiteralKeys": "error",
|
||||
"useRegexLiterals": "error",
|
||||
"noAdjacentSpacesInRegex": "error",
|
||||
"noCommaOperator": "error"
|
||||
},
|
||||
"correctness": {
|
||||
"noConstAssign": "error",
|
||||
"noConstantCondition": "error",
|
||||
"noEmptyCharacterClassInRegex": "error",
|
||||
"noEmptyPattern": "error",
|
||||
"noGlobalObjectCalls": "error",
|
||||
"noInnerDeclarations": "error",
|
||||
"noInvalidConstructorSuper": "error",
|
||||
"noInvalidUseBeforeDeclaration": "error",
|
||||
"noNodejsModules": "off",
|
||||
"noNonoctalDecimalEscape": "error",
|
||||
"noPrecisionLoss": "error",
|
||||
"noSelfAssign": "error",
|
||||
"noSetterReturn": "error",
|
||||
"noSwitchDeclarations": "error",
|
||||
"noUndeclaredVariables": "error",
|
||||
"noUnreachable": "error",
|
||||
"noUnreachableSuper": "error",
|
||||
"noUnsafeFinally": "error",
|
||||
"noUnsafeOptionalChaining": "error",
|
||||
"noUnusedLabels": "error",
|
||||
"noUnusedVariables": "error",
|
||||
"useIsNan": "error",
|
||||
"useValidForDirection": "error",
|
||||
"useYield": "error",
|
||||
"noInvalidBuiltinInstantiation": "error",
|
||||
"useValidTypeof": "error"
|
||||
},
|
||||
"security": { "noGlobalEval": "error" },
|
||||
"style": {
|
||||
"noDefaultExport": "error",
|
||||
"noYodaExpression": "error",
|
||||
"useBlockStatements": "error",
|
||||
"useConsistentBuiltinInstantiation": "error",
|
||||
"useConst": "error",
|
||||
"useImportType": "off",
|
||||
"useSingleVarDeclarator": "error",
|
||||
"useArrayLiterals": "error"
|
||||
},
|
||||
"suspicious": {
|
||||
"noAsyncPromiseExecutor": "error",
|
||||
"noCatchAssign": "error",
|
||||
"noClassAssign": "error",
|
||||
"noCompareNegZero": "error",
|
||||
"noConsole": "error",
|
||||
"noControlCharactersInRegex": "error",
|
||||
"noDebugger": "error",
|
||||
"noDoubleEquals": "error",
|
||||
"noDuplicateCase": "error",
|
||||
"noDuplicateClassMembers": "error",
|
||||
"noDuplicateObjectKeys": "error",
|
||||
"noDuplicateParameters": "error",
|
||||
"noEmptyBlockStatements": "error",
|
||||
"noFallthroughSwitchClause": "error",
|
||||
"noFunctionAssign": "error",
|
||||
"noGlobalAssign": "error",
|
||||
"noImportAssign": "error",
|
||||
"noMisleadingCharacterClass": "error",
|
||||
"noPrototypeBuiltins": "error",
|
||||
"noRedeclare": "error",
|
||||
"noSelfCompare": "error",
|
||||
"noShadowRestrictedNames": "error",
|
||||
"noSparseArray": "error",
|
||||
"noUnsafeNegation": "error",
|
||||
"useDefaultSwitchClauseLast": "error",
|
||||
"useGetterReturn": "error",
|
||||
"noWith": "error",
|
||||
"noVar": "warn"
|
||||
}
|
||||
},
|
||||
"includes": ["**", "!**/node_modules", "!**/*.min.js", "!**/build", "!**/coverage"]
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"jsxQuoteStyle": "single",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingCommas": "all",
|
||||
"semicolons": "asNeeded",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSameLine": false,
|
||||
"quoteStyle": "single",
|
||||
"attributePosition": "auto",
|
||||
"bracketSpacing": true
|
||||
},
|
||||
"globals": [
|
||||
"document",
|
||||
"navigator",
|
||||
"window",
|
||||
"describe",
|
||||
"test",
|
||||
"it",
|
||||
"expect",
|
||||
"beforeAll",
|
||||
"beforeEach",
|
||||
"afterAll",
|
||||
"afterEach",
|
||||
"jest"
|
||||
],
|
||||
"parser": {
|
||||
"unsafeParameterDecoratorsEnabled": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"includes": ["**/*.ts", "**/*.tsx"],
|
||||
"linter": { "rules": { "complexity": { "noVoid": "error" } } }
|
||||
},
|
||||
{ "includes": ["**/*.test.ts"], "linter": { "rules": {} } }
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,11 @@
|
||||
{
|
||||
"name": "dlt-connector",
|
||||
"repository": "git@github.com:gradido/gradido.git",
|
||||
"description": "Gradido DLT-Connector",
|
||||
"author": "Gradido Academy - https://www.gradido.net",
|
||||
"license": "Apache-2.0",
|
||||
"version": "1.0.50",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "bun run src/index.ts",
|
||||
"build": "bun build src/index.ts --outdir=build --target=bun --external=gradido-blockchain-js --external=@iota/client",
|
||||
@ -19,10 +24,13 @@
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@elysiajs/trpc": "^1.1.0",
|
||||
"@elysiajs/websocket": "^0.2.8",
|
||||
"@hashgraph/sdk": "^2.70.0",
|
||||
"@sinclair/typebox": "^0.34.33",
|
||||
"@sinclair/typemap": "^0.10.1",
|
||||
"@trpc/server": "^11.4.3",
|
||||
"@types/bun": "^1.2.17",
|
||||
"dotenv": "^10.0.0",
|
||||
"elysia": "^1.3.5",
|
||||
"elysia": "1.3.8",
|
||||
"graphql-request": "^7.2.0",
|
||||
"jose": "^5.2.2",
|
||||
"jsonrpc-ts-client": "^0.2.3",
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
|
||||
import { KeyPairIdentifier } from './data/KeyPairIdentifier.logic'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from './config/const'
|
||||
import { HieroId } from './schemas/typeGuard.schema'
|
||||
|
||||
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
|
||||
// and ../federation/client/FederationClientFactory.ts
|
||||
@ -13,7 +13,7 @@ import { LOG4JS_BASE_CATEGORY } from './config/const'
|
||||
export class KeyPairCacheManager {
|
||||
private static instance: KeyPairCacheManager
|
||||
private cache: Map<string, KeyPairEd25519> = new Map<string, KeyPairEd25519>()
|
||||
private homeCommunityUUID: string | undefined
|
||||
private homeCommunityTopicId: HieroId | undefined
|
||||
private logger: Logger
|
||||
|
||||
/**
|
||||
@ -37,15 +37,15 @@ export class KeyPairCacheManager {
|
||||
return KeyPairCacheManager.instance
|
||||
}
|
||||
|
||||
public setHomeCommunityUUID(uuid: string): void {
|
||||
this.homeCommunityUUID = uuid
|
||||
public setHomeCommunityTopicId(topicId: HieroId): void {
|
||||
this.homeCommunityTopicId = topicId
|
||||
}
|
||||
|
||||
public getHomeCommunityUUID(): string {
|
||||
if (!this.homeCommunityUUID) {
|
||||
throw new Error('home community uuid is not set')
|
||||
public getHomeCommunityTopicId(): HieroId {
|
||||
if (!this.homeCommunityTopicId) {
|
||||
throw new Error('home community topic id is not set')
|
||||
}
|
||||
return this.homeCommunityUUID
|
||||
return this.homeCommunityTopicId
|
||||
}
|
||||
|
||||
public findKeyPair(input: string): KeyPairEd25519 | undefined {
|
||||
@ -63,7 +63,10 @@ export class KeyPairCacheManager {
|
||||
this.cache.set(input, keyPair)
|
||||
}
|
||||
|
||||
public async getKeyPair(input: string, createKeyPair: () => Promise<KeyPairEd25519>): Promise<KeyPairEd25519> {
|
||||
public async getKeyPair(
|
||||
input: string,
|
||||
createKeyPair: () => Promise<KeyPairEd25519>,
|
||||
): Promise<KeyPairEd25519> {
|
||||
const keyPair = this.cache.get(input)
|
||||
if (!keyPair) {
|
||||
const keyPair = await createKeyPair()
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { AddressType, ConfirmedTransaction } from 'gradido-blockchain-js'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import * as v from 'valibot'
|
||||
import { addressTypeSchema, confirmedTransactionSchema } from '../../schemas/typeConverter.schema'
|
||||
import { GradidoNodeErrorCodes } from '../../enum/GradidoNodeErrorCodes'
|
||||
import { rpcCall, rpcCallResolved, GradidoNodeRequestError } from './jsonrpc'
|
||||
import {
|
||||
TransactionsRangeInput,
|
||||
TransactionIdentifierInput,
|
||||
transactionsRangeSchema,
|
||||
transactionIdentifierSchema
|
||||
} from './input.schema'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { Uuidv4Hash } from '../../data/Uuidv4Hash'
|
||||
import { Hex32, Hex32Input, hex32Schema } from '../../schemas/typeGuard.schema'
|
||||
import { GradidoNodeErrorCodes } from '../../enum/GradidoNodeErrorCodes'
|
||||
import { addressTypeSchema, confirmedTransactionSchema } from '../../schemas/typeConverter.schema'
|
||||
import { Hex32, Hex32Input, HieroId, hex32Schema } from '../../schemas/typeGuard.schema'
|
||||
import {
|
||||
TransactionIdentifierInput,
|
||||
TransactionsRangeInput,
|
||||
transactionIdentifierSchema,
|
||||
transactionsRangeSchema,
|
||||
} from './input.schema'
|
||||
import { GradidoNodeRequestError, rpcCall, rpcCallResolved } from './jsonrpc'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`)
|
||||
|
||||
@ -21,7 +21,7 @@ const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`)
|
||||
* get list of confirmed transactions from a specific community
|
||||
* @param input fromTransactionId is the id of the first transaction to return
|
||||
* @param input maxResultCount is the max number of transactions to return
|
||||
* @param input topic is the community topic
|
||||
* @param input topic is the community hiero topic id
|
||||
* @returns list of confirmed transactions
|
||||
* @throws GradidoNodeRequestError
|
||||
* @example
|
||||
@ -35,7 +35,7 @@ const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`)
|
||||
*/
|
||||
async function getTransactions(input: TransactionsRangeInput): Promise<ConfirmedTransaction[]> {
|
||||
const parameter = { ...v.parse(transactionsRangeSchema, input), format: 'base64' }
|
||||
const result = await rpcCallResolved<{transactions: string[]}>('getTransactions', parameter)
|
||||
const result = await rpcCallResolved<{ transactions: string[] }>('getTransactions', parameter)
|
||||
return result.transactions.map((transactionBase64) =>
|
||||
v.parse(confirmedTransactionSchema, transactionBase64),
|
||||
)
|
||||
@ -44,12 +44,13 @@ async function getTransactions(input: TransactionsRangeInput): Promise<Confirmed
|
||||
/**
|
||||
* getTransaction
|
||||
* get a specific confirmed transaction from a specific community
|
||||
* @param transactionIdentifier
|
||||
* @param transactionIdentifier
|
||||
* @returns the confirmed transaction or undefined if transaction is not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
async function getTransaction(transactionIdentifier: TransactionIdentifierInput)
|
||||
: Promise<ConfirmedTransaction | undefined> {
|
||||
async function getTransaction(
|
||||
transactionIdentifier: TransactionIdentifierInput,
|
||||
): Promise<ConfirmedTransaction | undefined> {
|
||||
const parameter = {
|
||||
...v.parse(transactionIdentifierSchema, transactionIdentifier),
|
||||
format: 'base64',
|
||||
@ -74,7 +75,9 @@ async function getTransaction(transactionIdentifier: TransactionIdentifierInput)
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
|
||||
async function getLastTransaction(iotaTopic: Uuidv4Hash): Promise<ConfirmedTransaction | undefined> {
|
||||
async function getLastTransaction(
|
||||
iotaTopic: Uuidv4Hash,
|
||||
): Promise<ConfirmedTransaction | undefined> {
|
||||
const response = await rpcCall<{ transaction: string }>('getlasttransaction', {
|
||||
format: 'base64',
|
||||
topic: iotaTopic.getAsHexString(),
|
||||
@ -92,19 +95,19 @@ async function getLastTransaction(iotaTopic: Uuidv4Hash): Promise<ConfirmedTrans
|
||||
|
||||
/**
|
||||
* getAddressType
|
||||
* get the address type of a specific user
|
||||
* get the address type of a specific user
|
||||
* can be used to check if user/account exists on blockchain
|
||||
* look also for gmw, auf and deferred transfer accounts
|
||||
* @param pubkey the public key of the user or account
|
||||
* @param iotaTopic the community topic
|
||||
* @returns the address type of the user/account or undefined
|
||||
* @returns the address type of the user/account or undefined
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
|
||||
async function getAddressType(pubkey: Hex32Input, iotaTopic: Uuidv4Hash): Promise<AddressType> {
|
||||
async function getAddressType(pubkey: Hex32Input, hieroTopic: HieroId): Promise<AddressType> {
|
||||
const parameter = {
|
||||
pubkey: v.parse(hex32Schema, pubkey),
|
||||
communityId: iotaTopic.getAsHexString(),
|
||||
communityId: hieroTopic,
|
||||
}
|
||||
const response = await rpcCallResolved<{ addressType: string }>('getaddresstype', parameter)
|
||||
return v.parse(addressTypeSchema, response.addressType)
|
||||
@ -118,17 +121,26 @@ async function getAddressType(pubkey: Hex32Input, iotaTopic: Uuidv4Hash): Promis
|
||||
* @returns the public key of the user as hex32 string or undefined if user is not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
async function findUserByNameHash(nameHash: Uuidv4Hash, iotaTopic: Uuidv4Hash): Promise<Hex32 | undefined> {
|
||||
async function findUserByNameHash(
|
||||
nameHash: Uuidv4Hash,
|
||||
hieroTopic: HieroId,
|
||||
): Promise<Hex32 | undefined> {
|
||||
const parameter = {
|
||||
nameHash: nameHash.getAsHexString(),
|
||||
communityId: iotaTopic.getAsHexString(),
|
||||
communityId: hieroTopic,
|
||||
}
|
||||
const response = await rpcCall<{ pubkey: string; timeUsed: string }>('findUserByNameHash', parameter)
|
||||
if(response.isSuccess()) {
|
||||
const response = await rpcCall<{ pubkey: string; timeUsed: string }>(
|
||||
'findUserByNameHash',
|
||||
parameter,
|
||||
)
|
||||
if (response.isSuccess()) {
|
||||
logger.info(`call findUserByNameHash, used ${response.result.timeUsed}`)
|
||||
return v.parse(hex32Schema, response.result.pubkey)
|
||||
}
|
||||
if (response.isError() && response.error.code === GradidoNodeErrorCodes.JSON_RPC_ERROR_ADDRESS_NOT_FOUND) {
|
||||
if (
|
||||
response.isError() &&
|
||||
response.error.code === GradidoNodeErrorCodes.JSON_RPC_ERROR_ADDRESS_NOT_FOUND
|
||||
) {
|
||||
logger.debug(`call findUserByNameHash, return with error: ${response.error.message}`)
|
||||
}
|
||||
return undefined
|
||||
@ -136,10 +148,10 @@ async function findUserByNameHash(nameHash: Uuidv4Hash, iotaTopic: Uuidv4Hash):
|
||||
|
||||
/**
|
||||
* getTransactionsForAccount
|
||||
* get list of confirmed transactions for a specific account
|
||||
* get list of confirmed transactions for a specific account
|
||||
* @param transactionRange the range of transactions to return
|
||||
* @param pubkey the public key of the account
|
||||
* @returns list of confirmed transactions
|
||||
* @returns list of confirmed transactions
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
async function getTransactionsForAccount(
|
||||
@ -151,7 +163,10 @@ async function getTransactionsForAccount(
|
||||
pubkey: v.parse(hex32Schema, pubkey),
|
||||
format: 'base64',
|
||||
}
|
||||
const response = await rpcCallResolved<{transactions: string[]}>('listtransactionsforaddress', parameter)
|
||||
const response = await rpcCallResolved<{ transactions: string[] }>(
|
||||
'listtransactionsforaddress',
|
||||
parameter,
|
||||
)
|
||||
return response.transactions.map((transactionBase64) =>
|
||||
v.parse(confirmedTransactionSchema, transactionBase64),
|
||||
)
|
||||
|
||||
@ -1,36 +1,35 @@
|
||||
import * as v from 'valibot'
|
||||
import { hex32Schema, iotaMessageIdSchema } from '../../schemas/typeGuard.schema'
|
||||
import { hieroIdSchema, hieroTransactionIdSchema } from '../../schemas/typeGuard.schema'
|
||||
|
||||
export const transactionsRangeSchema = v.object({
|
||||
// default value is 1, from first transactions
|
||||
fromTransactionId: v.undefinedable(v.pipe(v.number(), v.minValue(1, 'expect number >= 1')), 1),
|
||||
fromTransactionId: v.nullish(v.pipe(v.number(), v.minValue(1, 'expect number >= 1')), 1),
|
||||
// default value is 100, max 100 transactions
|
||||
maxResultCount: v.undefinedable(v.pipe(v.number(), v.minValue(1, 'expect number >= 1')), 100),
|
||||
topic: hex32Schema,
|
||||
maxResultCount: v.nullish(v.pipe(v.number(), v.minValue(1, 'expect number >= 1')), 100),
|
||||
topic: hieroIdSchema,
|
||||
})
|
||||
|
||||
export type TransactionsRangeInput = v.InferInput<typeof transactionsRangeSchema>
|
||||
|
||||
|
||||
// allow TransactionIdentifier to only contain either transactionNr or iotaMessageId
|
||||
export const transactionIdentifierSchema = v.pipe(
|
||||
v.object({
|
||||
transactionNr: v.nullish(
|
||||
v.pipe(v.number('expect number type'), v.minValue(1, 'expect number >= 1')),
|
||||
undefined
|
||||
undefined,
|
||||
),
|
||||
iotaMessageId: v.nullish(iotaMessageIdSchema, undefined),
|
||||
topic: hex32Schema,
|
||||
hieroTransactionId: v.nullish(hieroTransactionIdSchema, undefined),
|
||||
topic: hieroIdSchema,
|
||||
}),
|
||||
v.custom((value: any) => {
|
||||
const setFieldsCount = Number(value.transactionNr !== undefined) + Number(value.iotaMessageId !== undefined)
|
||||
const setFieldsCount =
|
||||
Number(value.transactionNr !== undefined) + Number(value.hieroTransactionId !== undefined)
|
||||
if (setFieldsCount !== 1) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 'expect transactionNr or iotaMessageId not both')
|
||||
}, 'expect transactionNr or hieroTransactionId not both'),
|
||||
)
|
||||
|
||||
export type TransactionIdentifierInput = v.InferInput<typeof transactionIdentifierSchema>
|
||||
export type TransactionIdentifier = v.InferOutput<typeof transactionIdentifierSchema>
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc'
|
||||
import { isPortOpenRetry } from '../../utils/network'
|
||||
import { CONFIG } from '../../config'
|
||||
import JsonRpcClient from 'jsonrpc-ts-client'
|
||||
import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc'
|
||||
import { getLogger } from 'log4js'
|
||||
import { CONFIG } from '../../config'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { isPortOpenRetry } from '../../utils/network'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`)
|
||||
|
||||
@ -24,7 +24,10 @@ export class GradidoNodeRequestError<T> extends Error {
|
||||
}
|
||||
|
||||
// return result on success or throw error
|
||||
export function resolveResponse<T, R>(response: JsonRpcEitherResponse<T>, onSuccess: (result: T) => R): R {
|
||||
export function resolveResponse<T, R>(
|
||||
response: JsonRpcEitherResponse<T>,
|
||||
onSuccess: (result: T) => R,
|
||||
): R {
|
||||
if (response.isSuccess()) {
|
||||
return onSuccess(response.result)
|
||||
} else if (response.isError()) {
|
||||
@ -36,7 +39,10 @@ export function resolveResponse<T, R>(response: JsonRpcEitherResponse<T>, onSucc
|
||||
type WithTimeUsed<T> = T & { timeUsed?: string }
|
||||
|
||||
// template rpcCall, check first if port is open before executing json rpc 2.0 request
|
||||
export async function rpcCall<T>(method: string, parameter: any): Promise<JsonRpcEitherResponse<T>> {
|
||||
export async function rpcCall<T>(
|
||||
method: string,
|
||||
parameter: any,
|
||||
): Promise<JsonRpcEitherResponse<T>> {
|
||||
logger.debug('call %s with %s', method, parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
return client.exec<T>(method, parameter)
|
||||
@ -51,4 +57,4 @@ export async function rpcCallResolved<T>(method: string, parameter: any): Promis
|
||||
}
|
||||
return result as T
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
74
dlt-connector/src/client/HieroClient.ts
Normal file
74
dlt-connector/src/client/HieroClient.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
AccountBalance,
|
||||
AccountBalanceQuery,
|
||||
Client,
|
||||
LocalProvider,
|
||||
PrivateKey,
|
||||
TopicMessageSubmitTransaction,
|
||||
TransactionReceipt,
|
||||
TransactionResponse,
|
||||
Wallet,
|
||||
} from '@hashgraph/sdk'
|
||||
import { GradidoTransaction } from 'gradido-blockchain-js'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { CONFIG } from '../config'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../config/const'
|
||||
import { HieroId } from '../schemas/typeGuard.schema'
|
||||
|
||||
export class HieroClient {
|
||||
private static instance: HieroClient
|
||||
wallet: Wallet
|
||||
logger: Logger
|
||||
|
||||
private constructor() {
|
||||
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.HieroClient`)
|
||||
const provider = LocalProvider.fromClient(Client.forName(CONFIG.HIERO_HEDERA_NETWORK))
|
||||
let operatorKey: PrivateKey
|
||||
if (CONFIG.HIERO_OPERATOR_KEY.length === 64) {
|
||||
operatorKey = PrivateKey.fromStringED25519(CONFIG.HIERO_OPERATOR_KEY)
|
||||
} else {
|
||||
operatorKey = PrivateKey.fromStringECDSA(CONFIG.HIERO_OPERATOR_KEY)
|
||||
}
|
||||
this.wallet = new Wallet(CONFIG.HIERO_OPERATOR_ID, operatorKey, provider)
|
||||
}
|
||||
|
||||
public static getInstance(): HieroClient {
|
||||
if (!CONFIG.HIERO_ACTIVE) {
|
||||
throw new Error('hiero is disabled via config...')
|
||||
}
|
||||
if (!HieroClient.instance) {
|
||||
HieroClient.instance = new HieroClient()
|
||||
}
|
||||
|
||||
return HieroClient.instance
|
||||
}
|
||||
|
||||
public async sendMessage(
|
||||
topicId: HieroId,
|
||||
transaction: GradidoTransaction,
|
||||
): Promise<{ receipt: TransactionReceipt; response: TransactionResponse }> {
|
||||
const serializedTransaction = transaction.getSerializedTransaction()
|
||||
if (!serializedTransaction) {
|
||||
throw new Error('cannot serialize transaction')
|
||||
}
|
||||
// send one message
|
||||
const hieroTransaction = await new TopicMessageSubmitTransaction({
|
||||
topicId,
|
||||
message: serializedTransaction.data(),
|
||||
}).freezeWithSigner(this.wallet)
|
||||
const signedHieroTransaction = await hieroTransaction.signWithSigner(this.wallet)
|
||||
const sendResponse = await signedHieroTransaction.executeWithSigner(this.wallet)
|
||||
const sendReceipt = await sendResponse.getReceiptWithSigner(this.wallet)
|
||||
this.logger.info(
|
||||
`message sent to topic ${topicId}, status: ${sendReceipt.status.toString()}, transaction id: ${sendResponse.transactionId.toString()}`,
|
||||
)
|
||||
return { receipt: sendReceipt, response: sendResponse }
|
||||
}
|
||||
|
||||
public async getBalance(): Promise<AccountBalance> {
|
||||
const balance = await new AccountBalanceQuery()
|
||||
.setAccountId(this.wallet.getAccountId())
|
||||
.executeWithSigner(this.wallet)
|
||||
return balance
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,10 @@
|
||||
import { gql, GraphQLClient } from 'graphql-request'
|
||||
import { GraphQLClient, gql } from 'graphql-request'
|
||||
import { SignJWT } from 'jose'
|
||||
|
||||
import { CONFIG } from '../../config'
|
||||
import { communitySchema, type Community } from './community.schema'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import * as v from 'valibot'
|
||||
|
||||
const homeCommunity = gql`
|
||||
query {
|
||||
homeCommunity {
|
||||
uuid
|
||||
foreign
|
||||
creationDate
|
||||
}
|
||||
}
|
||||
`
|
||||
import { CONFIG } from '../../config'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { type Community, communitySchema, homeCommunityGraphqlQuery } from './community.schema'
|
||||
|
||||
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
|
||||
// and ../federation/client/FederationClientFactory.ts
|
||||
@ -26,7 +15,7 @@ const homeCommunity = gql`
|
||||
export class BackendClient {
|
||||
private static instance: BackendClient
|
||||
client: GraphQLClient
|
||||
logger: Logger
|
||||
logger: Logger
|
||||
|
||||
/**
|
||||
* The Singleton's constructor should always be private to prevent direct
|
||||
@ -56,14 +45,14 @@ export class BackendClient {
|
||||
public static getInstance(): BackendClient | undefined {
|
||||
if (!BackendClient.instance) {
|
||||
BackendClient.instance = new BackendClient()
|
||||
}
|
||||
}
|
||||
return BackendClient.instance
|
||||
}
|
||||
|
||||
public async getHomeCommunityDraft(): Promise<Community> {
|
||||
this.logger.info('check home community on backend')
|
||||
const { data, errors } = await this.client.rawRequest<{ homeCommunity: Community }>(
|
||||
homeCommunity,
|
||||
homeCommunityGraphqlQuery,
|
||||
{}, // empty variables
|
||||
await this.getRequestHeader(),
|
||||
)
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import { communitySchema } from './community.schema'
|
||||
import { uuidv4Schema } from '../../schemas/typeGuard.schema'
|
||||
import * as v from 'valibot'
|
||||
// only for IDE, bun don't need this to work
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import * as v from 'valibot'
|
||||
import { uuidv4Schema } from '../../schemas/typeGuard.schema'
|
||||
import { communitySchema } from './community.schema'
|
||||
|
||||
describe('community.schema', () => {
|
||||
it('community', () => {
|
||||
expect(v.parse(communitySchema, {
|
||||
uuid: '4f28e081-5c39-4dde-b6a4-3bde71de8d65',
|
||||
foreign: false,
|
||||
createdAt: '2021-01-01',
|
||||
})).toEqual(
|
||||
{
|
||||
uuid: v.parse(uuidv4Schema, '4f28e081-5c39-4dde-b6a4-3bde71de8d65'),
|
||||
expect(
|
||||
v.parse(communitySchema, {
|
||||
uuid: '4f28e081-5c39-4dde-b6a4-3bde71de8d65',
|
||||
foreign: false,
|
||||
createdAt: new Date('2021-01-01'),
|
||||
},
|
||||
)
|
||||
createdAt: '2021-01-01',
|
||||
}),
|
||||
).toEqual({
|
||||
uuid: v.parse(uuidv4Schema, '4f28e081-5c39-4dde-b6a4-3bde71de8d65'),
|
||||
foreign: false,
|
||||
createdAt: new Date('2021-01-01'),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { gql } from 'graphql-request'
|
||||
import * as v from 'valibot'
|
||||
import { uuidv4Schema } from '../../schemas/typeGuard.schema'
|
||||
import { dateSchema } from '../../schemas/typeConverter.schema'
|
||||
import { hieroIdSchema, uuidv4Schema } from '../../schemas/typeGuard.schema'
|
||||
|
||||
/**
|
||||
* Schema Definitions for rpc call parameter, when dlt-connector is called from backend
|
||||
@ -11,9 +12,22 @@ import { dateSchema } from '../../schemas/typeConverter.schema'
|
||||
*/
|
||||
export const communitySchema = v.object({
|
||||
uuid: uuidv4Schema,
|
||||
topicId: hieroIdSchema,
|
||||
foreign: v.boolean('expect boolean type'),
|
||||
createdAt: dateSchema,
|
||||
})
|
||||
|
||||
export type CommunityInput = v.InferInput<typeof communitySchema>
|
||||
export type Community = v.InferOutput<typeof communitySchema>
|
||||
|
||||
// graphql query for getting home community in tune with community schema
|
||||
export const homeCommunityGraphqlQuery = gql`
|
||||
query {
|
||||
homeCommunity {
|
||||
uuid
|
||||
topicId
|
||||
foreign
|
||||
creationDate
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/* eslint-disable n/no-process-env */
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const logging = {
|
||||
@ -25,7 +26,16 @@ const iota = {
|
||||
IOTA_HOME_COMMUNITY_SEED: process.env.IOTA_HOME_COMMUNITY_SEED ?? null,
|
||||
}
|
||||
|
||||
const apis = {
|
||||
const hiero = {
|
||||
HIERO_ACTIVE: process.env.HIERO_ACTIVE === 'true' || false,
|
||||
HIERO_HEDERA_NETWORK: process.env.HIERO_HEDERA_NETWORK ?? 'testnet',
|
||||
HIERO_OPERATOR_ID: process.env.HIERO_OPERATOR_ID ?? '0.0.2',
|
||||
HIERO_OPERATOR_KEY:
|
||||
process.env.HIERO_OPERATOR_KEY ??
|
||||
'302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137',
|
||||
}
|
||||
|
||||
const apis = {
|
||||
CONNECT_TIMEOUT_MS: process.env.CONNECT_TIMEOUT_MS
|
||||
? Number.parseInt(process.env.CONNECT_TIMEOUT_MS)
|
||||
: 1000,
|
||||
@ -45,5 +55,6 @@ export const CONFIG = {
|
||||
...server,
|
||||
...secrets,
|
||||
...iota,
|
||||
...hiero,
|
||||
...apis,
|
||||
}
|
||||
|
||||
24
dlt-connector/src/config/schema.ts
Normal file
24
dlt-connector/src/config/schema.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import * as v from 'valibot'
|
||||
|
||||
export const HIERO_ACTIVE = v.nullish(
|
||||
v.boolean('Flag to indicate if the Hiero (Hedera Hashgraph Ledger) service is used.'),
|
||||
false,
|
||||
)
|
||||
|
||||
export const HIERO_HEDERA_NETWORK = v.nullish(
|
||||
v.union([v.literal('mainnet'), v.literal('testnet'), v.literal('previewnet')]),
|
||||
'testnet',
|
||||
)
|
||||
|
||||
export const HIERO_OPERATOR_ID = v.nullish(
|
||||
v.pipe(v.string('The operator ID for Hiero integration'), v.regex(/^[0-9]+\.[0-9]+\.[0-9]+$/)),
|
||||
'0.0.2',
|
||||
)
|
||||
|
||||
export const HIERO_OPERATOR_KEY = v.nullish(
|
||||
v.pipe(
|
||||
v.string('The operator key for Hiero integration, default is for local default node'),
|
||||
v.regex(/^[0-9a-fA-F]{64,96}$/),
|
||||
),
|
||||
'302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137',
|
||||
)
|
||||
@ -1,14 +1,10 @@
|
||||
import { MemoryBlock } from 'gradido-blockchain-js'
|
||||
import { IdentifierAccount, IdentifierAccountInput, identifierAccountSchema } from '../schemas/account.schema'
|
||||
import { ParameterError } from '../errors'
|
||||
import * as v from 'valibot'
|
||||
import { IdentifierAccount } from '../schemas/account.schema'
|
||||
import { HieroId } from '../schemas/typeGuard.schema'
|
||||
|
||||
export class KeyPairIdentifierLogic {
|
||||
public identifier: IdentifierAccount
|
||||
public constructor(identifier: IdentifierAccountInput) {
|
||||
// check if data structure is like expected and fill in defaults
|
||||
this.identifier = v.parse(identifierAccountSchema, identifier)
|
||||
}
|
||||
public constructor(public identifier: IdentifierAccount) {}
|
||||
|
||||
isCommunityKeyPair(): boolean {
|
||||
return !this.identifier.seed && !this.identifier.account
|
||||
@ -36,19 +32,21 @@ export class KeyPairIdentifierLogic {
|
||||
|
||||
getSeed(): string {
|
||||
if (!this.identifier.seed) {
|
||||
throw new Error('get seed called on non seed key pair identifier, please check first with isSeedKeyPair()')
|
||||
throw new Error(
|
||||
'get seed called on non seed key pair identifier, please check first with isSeedKeyPair()',
|
||||
)
|
||||
}
|
||||
return this.identifier.seed.seed
|
||||
}
|
||||
|
||||
getCommunityUuid(): string {
|
||||
return this.identifier.communityUuid
|
||||
getCommunityTopicId(): HieroId {
|
||||
return this.identifier.communityTopicId
|
||||
}
|
||||
|
||||
getUserUuid(): string {
|
||||
if (!this.identifier.account) {
|
||||
throw new Error(
|
||||
'get user uuid called on non user key pair identifier, please check first with isUserKeyPair() or isAccountKeyPair()'
|
||||
'get user uuid called on non user key pair identifier, please check first with isUserKeyPair() or isAccountKeyPair()',
|
||||
)
|
||||
}
|
||||
return this.identifier.account.userUuid
|
||||
@ -57,19 +55,23 @@ export class KeyPairIdentifierLogic {
|
||||
getAccountNr(): number {
|
||||
if (!this.identifier.account?.accountNr) {
|
||||
throw new Error(
|
||||
'get account nr called on non account key pair identifier, please check first with isAccountKeyPair()'
|
||||
'get account nr called on non account key pair identifier, please check first with isAccountKeyPair()',
|
||||
)
|
||||
}
|
||||
return this.identifier.account.accountNr
|
||||
}
|
||||
|
||||
getSeedKey(): string { return this.getSeed() }
|
||||
getCommunityKey(): string { return this.getCommunityUuid() }
|
||||
getCommunityUserKey(): string {
|
||||
getSeedKey(): string {
|
||||
return this.getSeed()
|
||||
}
|
||||
getCommunityKey(): HieroId {
|
||||
return this.getCommunityTopicId()
|
||||
}
|
||||
getCommunityUserKey(): string {
|
||||
return this.createCommunityUserHash()
|
||||
}
|
||||
getCommunityUserAccountKey(): string {
|
||||
return this.createCommunityUserHash() + this.getAccountNr().toString()
|
||||
getCommunityUserAccountKey(): string {
|
||||
return this.createCommunityUserHash() + this.getAccountNr().toString()
|
||||
}
|
||||
|
||||
getKey(): string {
|
||||
@ -86,12 +88,11 @@ export class KeyPairIdentifierLogic {
|
||||
}
|
||||
|
||||
private createCommunityUserHash(): string {
|
||||
if (!this.identifier.account?.userUuid || !this.identifier.communityUuid) {
|
||||
throw new ParameterError('userUuid and/or communityUuid is undefined')
|
||||
if (!this.identifier.account?.userUuid || !this.identifier.communityTopicId) {
|
||||
throw new ParameterError('userUuid and/or communityTopicId is undefined')
|
||||
}
|
||||
const resultHexString =
|
||||
this.identifier.communityUuid.replace(/-/g, '')
|
||||
+ this.identifier.account.userUuid.replace(/-/g, '')
|
||||
const resultHexString =
|
||||
this.identifier.communityTopicId + this.identifier.account.userUuid.replace(/-/g, '')
|
||||
return MemoryBlock.fromHex(resultHexString).calculateHash().convertToHex()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
import { MemoryBlock } from 'gradido-blockchain-js'
|
||||
import { Uuidv4, Hex32, hex32Schema, MemoryBlock32, memoryBlock32Schema } from '../schemas/typeGuard.schema'
|
||||
import * as v from 'valibot'
|
||||
import {
|
||||
Hex32,
|
||||
hex32Schema,
|
||||
MemoryBlock32,
|
||||
memoryBlock32Schema,
|
||||
Uuidv4,
|
||||
} from '../schemas/typeGuard.schema'
|
||||
|
||||
/**
|
||||
* Uuidv4Hash is a class that represents a uuidv4 BLAKE2b hash
|
||||
* to get the hash, the - in the uuidv4 will be removed and the result interpreted as hex string
|
||||
* then the hash will be calculated with BLAKE2b algorithm
|
||||
* then the hash will be calculated with BLAKE2b algorithm
|
||||
* crypto_generichash from libsodium will be called when calling MemoryBlock.calculateHash()
|
||||
*
|
||||
*
|
||||
* This will be used as NameHash for user identification (user uuid as input)
|
||||
* This will be used as IotaTopic for transactions (community uuid as input)
|
||||
*/
|
||||
@ -17,7 +23,10 @@ export class Uuidv4Hash {
|
||||
uuidv4HashString: Hex32 | undefined
|
||||
|
||||
constructor(uuidv4: Uuidv4) {
|
||||
this.uuidv4Hash = v.parse(memoryBlock32Schema, MemoryBlock.fromHex(uuidv4.replace(/-/g, '')).calculateHash())
|
||||
this.uuidv4Hash = v.parse(
|
||||
memoryBlock32Schema,
|
||||
MemoryBlock.fromHex(uuidv4.replace(/-/g, '')).calculateHash(),
|
||||
)
|
||||
}
|
||||
|
||||
getAsMemoryBlock(): MemoryBlock32 {
|
||||
|
||||
@ -4,9 +4,9 @@ import {
|
||||
AddressType_COMMUNITY_HUMAN,
|
||||
AddressType_COMMUNITY_PROJECT,
|
||||
AddressType_CRYPTO_ACCOUNT,
|
||||
AddressType_DEFERRED_TRANSFER,
|
||||
AddressType_NONE,
|
||||
AddressType_SUBACCOUNT,
|
||||
AddressType_DEFERRED_TRANSFER,
|
||||
} from 'gradido-blockchain-js'
|
||||
|
||||
export enum AddressType {
|
||||
@ -18,4 +18,4 @@ export enum AddressType {
|
||||
NONE = AddressType_NONE,
|
||||
SUBACCOUNT = AddressType_SUBACCOUNT,
|
||||
DEFERRED_TRANSFER = AddressType_DEFERRED_TRANSFER,
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,4 +17,4 @@ export enum GradidoNodeErrorCodes {
|
||||
// -32603 Internal error Internal JSON - RPC error.
|
||||
INTERNAL_ERROR = -32603,
|
||||
// -32000 to -32099 Server error Reserved for implementation-defined server-errors.
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
// enum for graphql but with int because it is the same in backend
|
||||
// for transaction type from backend
|
||||
export enum InputTransactionType {
|
||||
GRADIDO_TRANSFER = 'GRADIDO_TRANSFER',
|
||||
GRADIDO_CREATION = 'GRADIDO_CREATION',
|
||||
GROUP_FRIENDS_UPDATE = 'GROUP_FRIENDS_UPDATE',
|
||||
REGISTER_ADDRESS = 'REGISTER_ADDRESS',
|
||||
GRADIDO_DEFERRED_TRANSFER = 'GRADIDO_DEFERRED_TRANSFER',
|
||||
GRADIDO_REDEEM_DEFERRED_TRANSFER = 'GRADIDO_REDEEM_DEFERRED_TRANSFER',
|
||||
COMMUNITY_ROOT = 'COMMUNITY_ROOT',
|
||||
}
|
||||
// enum for graphql but with int because it is the same in backend
|
||||
// for transaction type from backend
|
||||
export enum InputTransactionType {
|
||||
GRADIDO_TRANSFER = 'GRADIDO_TRANSFER',
|
||||
GRADIDO_CREATION = 'GRADIDO_CREATION',
|
||||
GROUP_FRIENDS_UPDATE = 'GROUP_FRIENDS_UPDATE',
|
||||
REGISTER_ADDRESS = 'REGISTER_ADDRESS',
|
||||
GRADIDO_DEFERRED_TRANSFER = 'GRADIDO_DEFERRED_TRANSFER',
|
||||
GRADIDO_REDEEM_DEFERRED_TRANSFER = 'GRADIDO_REDEEM_DEFERRED_TRANSFER',
|
||||
COMMUNITY_ROOT = 'COMMUNITY_ROOT',
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { TransactionIdentifier } from './schemas/transaction.schema'
|
||||
import { IdentifierAccount } from './schemas/account.schema'
|
||||
import { TransactionIdentifier } from './schemas/transaction.schema'
|
||||
|
||||
export class GradidoNodeError extends Error {
|
||||
constructor(message: string) {
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { Elysia } from 'elysia'
|
||||
import { CONFIG } from './config'
|
||||
import { loadCryptoKeys, MemoryBlock } from 'gradido-blockchain-js'
|
||||
import { getLogger, configure } from 'log4js'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { isPortOpenRetry } from './utils/network'
|
||||
import { BackendClient } from './client/backend/BackendClient'
|
||||
import { KeyPairCacheManager } from './KeyPairCacheManager'
|
||||
import { getTransaction } from './client/GradidoNode/api'
|
||||
import { Uuidv4Hash } from './data/Uuidv4Hash'
|
||||
import { SendToIotaContext } from './interactions/sendToIota/SendToIota.context'
|
||||
import { keyGenerationSeedSchema } from './schemas/base.schema'
|
||||
import { Elysia } from 'elysia'
|
||||
import { loadCryptoKeys, MemoryBlock } from 'gradido-blockchain-js'
|
||||
import { configure, getLogger } from 'log4js'
|
||||
import * as v from 'valibot'
|
||||
import { BackendClient } from './client/backend/BackendClient'
|
||||
import { getTransaction } from './client/GradidoNode/api'
|
||||
import { CONFIG } from './config'
|
||||
import { SendToIotaContext } from './interactions/sendToIota/SendToIota.context'
|
||||
import { KeyPairCacheManager } from './KeyPairCacheManager'
|
||||
import { keyGenerationSeedSchema } from './schemas/base.schema'
|
||||
import { isPortOpenRetry } from './utils/network'
|
||||
import { appRoutes } from './server'
|
||||
|
||||
async function main() {
|
||||
// configure log4js
|
||||
@ -23,7 +23,7 @@ async function main() {
|
||||
logger.error('IOTA_HOME_COMMUNITY_SEED must be a valid hex string, at least 64 characters long')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
||||
// load crypto keys for gradido blockchain lib
|
||||
loadCryptoKeys(
|
||||
MemoryBlock.fromHex(CONFIG.GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET),
|
||||
@ -38,22 +38,21 @@ async function main() {
|
||||
// wait for backend server
|
||||
await isPortOpenRetry(CONFIG.BACKEND_SERVER_URL)
|
||||
const homeCommunity = await backend.getHomeCommunityDraft()
|
||||
KeyPairCacheManager.getInstance().setHomeCommunityUUID(homeCommunity.uuid)
|
||||
const topic = new Uuidv4Hash(homeCommunity.uuid).getAsHexString()
|
||||
logger.info('home community topic: %s', topic)
|
||||
KeyPairCacheManager.getInstance().setHomeCommunityTopicId(homeCommunity.topicId)
|
||||
logger.info('home community topic: %s', homeCommunity.topicId)
|
||||
logger.info('gradido node server: %s', CONFIG.NODE_SERVER_URL)
|
||||
// ask gradido node if community blockchain was created
|
||||
try {
|
||||
if (!await getTransaction({ transactionNr: 1, topic })) {
|
||||
if (!(await getTransaction({ transactionNr: 1, topic: homeCommunity.topicId }))) {
|
||||
// if not exist, create community root transaction
|
||||
await SendToIotaContext(homeCommunity)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('error requesting gradido node: ', e)
|
||||
}
|
||||
// listen for rpc request from backend (replace graphql with json rpc)
|
||||
const app = new Elysia()
|
||||
.get('/', () => "Hello Elysia")
|
||||
// listen for rpc request from backend (graphql replaced with trpc and elysia)
|
||||
new Elysia()
|
||||
.use(appRoutes)
|
||||
.listen(CONFIG.DLT_CONNECTOR_PORT, () => {
|
||||
logger.info(`Server is running at http://localhost:${CONFIG.DLT_CONNECTOR_PORT}`)
|
||||
})
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
import { communityUuidToTopicSchema } from '../../client/backend/community.schema'
|
||||
import * as v from 'valibot'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
|
||||
export abstract class AbstractRemoteKeyPairRole {
|
||||
protected topic: string
|
||||
public constructor(communityUuid: string) {
|
||||
this.topic = v.parse(communityUuidToTopicSchema, communityUuid)
|
||||
protected topic: HieroId
|
||||
public constructor(communityTopicId: HieroId) {
|
||||
this.topic = communityTopicId
|
||||
}
|
||||
public abstract retrieveKeyPair(): Promise<KeyPairEd25519>
|
||||
}
|
||||
|
||||
@ -3,7 +3,10 @@ import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
import { AbstractKeyPairRole } from './AbstractKeyPair.role'
|
||||
|
||||
export class AccountKeyPairRole extends AbstractKeyPairRole {
|
||||
public constructor(private accountNr: number, private userKeyPair: KeyPairEd25519) {
|
||||
public constructor(
|
||||
private accountNr: number,
|
||||
private userKeyPair: KeyPairEd25519,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,22 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
|
||||
import { getTransaction } from '../../client/GradidoNode/api'
|
||||
|
||||
import {
|
||||
GradidoNodeInvalidTransactionError,
|
||||
GradidoNodeMissingTransactionError,
|
||||
} from '../../errors'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
import { AbstractRemoteKeyPairRole } from './AbstractRemoteKeyPair.role'
|
||||
import { GradidoNodeInvalidTransactionError, GradidoNodeMissingTransactionError } from '../../errors'
|
||||
|
||||
export class ForeignCommunityKeyPairRole extends AbstractRemoteKeyPairRole {
|
||||
public constructor(communityUuid: string) {
|
||||
super(communityUuid)
|
||||
public constructor(communityTopicId: HieroId) {
|
||||
super(communityTopicId)
|
||||
}
|
||||
|
||||
public async retrieveKeyPair(): Promise<KeyPairEd25519> {
|
||||
const transactionIdentifier = {
|
||||
transactionNr: 1,
|
||||
iotaTopic: this.topic,
|
||||
topic: this.topic,
|
||||
}
|
||||
const firstTransaction = await getTransaction(transactionIdentifier)
|
||||
if (!firstTransaction) {
|
||||
@ -22,21 +25,21 @@ export class ForeignCommunityKeyPairRole extends AbstractRemoteKeyPairRole {
|
||||
const transactionBody = firstTransaction.getGradidoTransaction()?.getTransactionBody()
|
||||
if (!transactionBody) {
|
||||
throw new GradidoNodeInvalidTransactionError(
|
||||
'Invalid transaction, body is missing',
|
||||
transactionIdentifier
|
||||
'Invalid transaction, body is missing',
|
||||
transactionIdentifier,
|
||||
)
|
||||
}
|
||||
if (!transactionBody.isCommunityRoot()) {
|
||||
throw new GradidoNodeInvalidTransactionError(
|
||||
'Invalid transaction, community root type expected',
|
||||
transactionIdentifier
|
||||
transactionIdentifier,
|
||||
)
|
||||
}
|
||||
const communityRoot = transactionBody.getCommunityRoot()
|
||||
if (!communityRoot) {
|
||||
throw new GradidoNodeInvalidTransactionError(
|
||||
'Invalid transaction, community root is missing',
|
||||
transactionIdentifier
|
||||
transactionIdentifier,
|
||||
)
|
||||
}
|
||||
return new KeyPairEd25519(communityRoot.getPublicKey())
|
||||
|
||||
@ -20,11 +20,10 @@ export async function KeyPairCalculation(input: KeyPairIdentifierLogic): Promise
|
||||
return new LinkedTransactionKeyPairRole(input.getSeed()).generateKeyPair()
|
||||
}
|
||||
// If input does not belong to the home community, handle as remote key pair
|
||||
if (cache.getHomeCommunityUUID() !== input.getCommunityUuid()) {
|
||||
const role =
|
||||
input.isAccountKeyPair()
|
||||
? new RemoteAccountKeyPairRole(input.identifier)
|
||||
: new ForeignCommunityKeyPairRole(input.getCommunityUuid())
|
||||
if (cache.getHomeCommunityTopicId() !== input.getCommunityTopicId()) {
|
||||
const role = input.isAccountKeyPair()
|
||||
? new RemoteAccountKeyPairRole(input.identifier)
|
||||
: new ForeignCommunityKeyPairRole(input.getCommunityTopicId())
|
||||
return await role.retrieveKeyPair()
|
||||
}
|
||||
const communityKeyPair = await cache.getKeyPair(input.getCommunityKey(), async () => {
|
||||
@ -37,10 +36,7 @@ export async function KeyPairCalculation(input: KeyPairIdentifierLogic): Promise
|
||||
return communityKeyPair
|
||||
}
|
||||
const userKeyPair = await cache.getKeyPair(input.getCommunityUserKey(), async () => {
|
||||
return new UserKeyPairRole(
|
||||
input.getUserUuid(),
|
||||
communityKeyPair,
|
||||
).generateKeyPair()
|
||||
return new UserKeyPairRole(input.getUserUuid(), communityKeyPair).generateKeyPair()
|
||||
})
|
||||
if (!userKeyPair) {
|
||||
throw new Error("couldn't generate user key pair")
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { KeyPairEd25519, MemoryBlock } from 'gradido-blockchain-js'
|
||||
|
||||
import { AbstractKeyPairRole } from './AbstractKeyPair.role'
|
||||
import { ParameterError } from '../../errors'
|
||||
import { AbstractKeyPairRole } from './AbstractKeyPair.role'
|
||||
|
||||
export class LinkedTransactionKeyPairRole extends AbstractKeyPairRole {
|
||||
public constructor(private seed: string) {
|
||||
@ -14,7 +13,9 @@ export class LinkedTransactionKeyPairRole extends AbstractKeyPairRole {
|
||||
const hash = new MemoryBlock(this.seed).calculateHash()
|
||||
const keyPair = KeyPairEd25519.create(hash)
|
||||
if (!keyPair) {
|
||||
throw new ParameterError(`error creating Ed25519 KeyPair from seed: ${this.seed.substring(0, 5)}...`)
|
||||
throw new ParameterError(
|
||||
`error creating Ed25519 KeyPair from seed: ${this.seed.substring(0, 5)}...`,
|
||||
)
|
||||
}
|
||||
return keyPair
|
||||
}
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
import { KeyPairEd25519, MemoryBlock } from 'gradido-blockchain-js'
|
||||
|
||||
import { findUserByNameHash } from '../../client/GradidoNode/api'
|
||||
import { IdentifierAccount } from '../../schemas/account.schema'
|
||||
import { Uuidv4Hash } from '../../data/Uuidv4Hash'
|
||||
import { GradidoNodeMissingUserError, ParameterError } from '../../errors'
|
||||
import { IdentifierAccount } from '../../schemas/account.schema'
|
||||
import { AbstractRemoteKeyPairRole } from './AbstractRemoteKeyPair.role'
|
||||
import { uuid4ToHashSchema } from '../../schemas/typeConverter.schema'
|
||||
import * as v from 'valibot'
|
||||
|
||||
export class RemoteAccountKeyPairRole extends AbstractRemoteKeyPairRole {
|
||||
public constructor(private identifier: IdentifierAccount) {
|
||||
super(identifier.communityUuid)
|
||||
super(identifier.communityTopicId)
|
||||
}
|
||||
|
||||
public async retrieveKeyPair(): Promise<KeyPairEd25519> {
|
||||
@ -18,11 +17,11 @@ export class RemoteAccountKeyPairRole extends AbstractRemoteKeyPairRole {
|
||||
}
|
||||
|
||||
const accountPublicKey = await findUserByNameHash(
|
||||
v.parse(uuid4ToHashSchema, this.identifier.account.userUuid),
|
||||
new Uuidv4Hash(this.identifier.account.userUuid),
|
||||
this.topic,
|
||||
)
|
||||
if (accountPublicKey) {
|
||||
return new KeyPairEd25519(accountPublicKey)
|
||||
return new KeyPairEd25519(MemoryBlock.createPtr(MemoryBlock.fromHex(accountPublicKey)))
|
||||
}
|
||||
throw new GradidoNodeMissingUserError('cannot find remote user', this.identifier)
|
||||
}
|
||||
|
||||
@ -4,7 +4,10 @@ import { hardenDerivationIndex } from '../../utils/derivationHelper'
|
||||
import { AbstractKeyPairRole } from './AbstractKeyPair.role'
|
||||
|
||||
export class UserKeyPairRole extends AbstractKeyPairRole {
|
||||
public constructor(private userUuid: string, private communityKeys: KeyPairEd25519) {
|
||||
public constructor(
|
||||
private userUuid: string,
|
||||
private communityKeys: KeyPairEd25519,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
import { beforeAll, describe, expect, it } from 'bun:test'
|
||||
import { KeyPairEd25519, MemoryBlock } from 'gradido-blockchain-js'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { UserKeyPairRole } from './UserKeyPair.role'
|
||||
|
||||
let communityKeyPair: KeyPairEd25519
|
||||
describe('UserKeyPairRole', () => {
|
||||
beforeAll(() => {
|
||||
communityKeyPair = KeyPairEd25519.create(new MemoryBlock('test').calculateHash())!
|
||||
})
|
||||
it('should generate key pair', () => {
|
||||
const userUUidPublicKeyPairs = [
|
||||
{
|
||||
userUuid: '648f28cc-209c-4696-b381-5156fc1a7bcb',
|
||||
publicKeyHex: 'ebd636d7e1e7177c0990d2ce836d8ced8b05ad75d62a7120a5d4a67bdd9dddb9',
|
||||
},
|
||||
{
|
||||
userUuid: 'fb65ef70-4c33-4dbc-aca8-3bae2609b04b',
|
||||
publicKeyHex: 'd89fe30c53852dc2c8281581e6904da396c3104fc11c0a6d9b4a0afa5ce54dc1',
|
||||
},
|
||||
{
|
||||
userUuid: 'd58b6355-a337-4c80-b593-18d78b5cdab0',
|
||||
publicKeyHex: 'dafb51eb8143cc7b2559235978ab845922ca8efa938ece078f45957ae3b92458',
|
||||
},
|
||||
{
|
||||
userUuid: '61144289-f69b-43a7-8b7d-5dcf3fa8ca68',
|
||||
publicKeyHex: '8d4db4ecfb65e40d1a4d4d4858be1ddd54b64aa845ceaa6698c336424ae0fc58',
|
||||
},
|
||||
{
|
||||
userUuid: 'c63e6870-196c-4013-b30a-d0a5170e8150',
|
||||
publicKeyHex: 'c240a8978da03f24d75108c4f06550a9bde46c902684a6d19d8b990819f518c8',
|
||||
},
|
||||
{
|
||||
userUuid: '6b40a2c9-4c0f-426d-bb55-353967e89aa2',
|
||||
publicKeyHex: '75531146e27b557085c09545e7a5e95f7bfd66d0de30c31befc34e4061f4e148',
|
||||
},
|
||||
{
|
||||
userUuid: '50c47820-6d15-449d-89c3-b1fd02674c80',
|
||||
publicKeyHex: '2c9ccb9914009bb6f24e41659223a5f8ce200cb5a4abdd808db57819f43c0ea2',
|
||||
},
|
||||
{
|
||||
userUuid: 'dc902954-bfc1-412e-b2f6-857e8bd45f0f',
|
||||
publicKeyHex: '4546870cf56093755c1f410a53c5908a5f30f26bc553110779e8bf5b841d904a',
|
||||
},
|
||||
{
|
||||
userUuid: 'd77cd665-0d60-4033-a887-07944466ccbe',
|
||||
publicKeyHex: '6563a5ca2944ba47391afd11a46e65bb7eb90657179dbc2d6be88af9ffa849a9',
|
||||
},
|
||||
{
|
||||
userUuid: 'ee83ed52-8a37-4a8a-8eed-ffaaac7d0d03',
|
||||
publicKeyHex: '03968833ee5d4839cb9091f39f76c9f5ca35f117b953229b59549cce907a60ea',
|
||||
},
|
||||
]
|
||||
for (let i = 0; i < userUUidPublicKeyPairs.length; i++) {
|
||||
const pair = userUUidPublicKeyPairs[i]
|
||||
const userKeyPairRole = new UserKeyPairRole(pair.userUuid, communityKeyPair)
|
||||
const accountKeyPair = userKeyPairRole.generateKeyPair()
|
||||
expect(accountKeyPair).toBeDefined()
|
||||
const publicKeyHex = accountKeyPair.getPublicKey()?.convertToHex()
|
||||
expect(publicKeyHex).toHaveLength(64)
|
||||
expect(publicKeyHex).toBe(pair.publicKeyHex)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -1,7 +1,8 @@
|
||||
import { GradidoTransactionBuilder } from 'gradido-blockchain-js'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
|
||||
export abstract class AbstractTransactionRole {
|
||||
abstract getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder>
|
||||
abstract getSenderCommunityUuid(): string
|
||||
abstract getRecipientCommunityUuid(): string
|
||||
abstract getSenderCommunityTopicId(): HieroId
|
||||
abstract getRecipientCommunityTopicId(): HieroId
|
||||
}
|
||||
|
||||
@ -1,45 +1,41 @@
|
||||
import { GradidoTransactionBuilder } from 'gradido-blockchain-js'
|
||||
|
||||
import { Community } from '../../client/backend/community.schema'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
import {
|
||||
AUF_ACCOUNT_DERIVATION_INDEX,
|
||||
GMW_ACCOUNT_DERIVATION_INDEX,
|
||||
hardenDerivationIndex,
|
||||
} from '../../utils/derivationHelper'
|
||||
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
import { Community, CommunityInput, communitySchema } from '../../client/backend/community.schema'
|
||||
import * as v from 'valibot'
|
||||
|
||||
export class CommunityRootTransactionRole extends AbstractTransactionRole {
|
||||
private com: Community
|
||||
constructor(input: CommunityInput) {
|
||||
constructor(private readonly community: Community) {
|
||||
super()
|
||||
this.com = v.parse(communitySchema, input)
|
||||
}
|
||||
|
||||
getSenderCommunityUuid(): string {
|
||||
return this.com.uuid
|
||||
getSenderCommunityTopicId(): HieroId {
|
||||
return this.community.topicId
|
||||
}
|
||||
|
||||
getRecipientCommunityUuid(): string {
|
||||
getRecipientCommunityTopicId(): HieroId {
|
||||
throw new Error('cannot be used as cross group transaction')
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
const communityKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic({ communityUuid: this.com.uuid }))
|
||||
new KeyPairIdentifierLogic({ communityTopicId: this.community.topicId }),
|
||||
)
|
||||
const gmwKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
|
||||
)
|
||||
) // as unknown as KeyPairEd25519
|
||||
const aufKeyPair = communityKeyPair.deriveChild(
|
||||
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
|
||||
)
|
||||
) // as unknown as KeyPairEd25519
|
||||
builder
|
||||
.setCreatedAt(this.com.createdAt)
|
||||
.setCreatedAt(this.community.createdAt)
|
||||
.setCommunityRoot(
|
||||
communityKeyPair.getPublicKey(),
|
||||
gmwKeyPair.getPublicKey(),
|
||||
|
||||
@ -4,78 +4,69 @@ import {
|
||||
GradidoTransactionBuilder,
|
||||
TransferAmount,
|
||||
} from 'gradido-blockchain-js'
|
||||
|
||||
import { parse } from 'valibot'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import { CreationTransactionInput, creationTransactionSchema, CreationTransaction } from '../../schemas/transaction.schema'
|
||||
import { KeyPairCacheManager } from '../../KeyPairCacheManager'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
|
||||
import {
|
||||
CreationTransaction,
|
||||
creationTransactionSchema,
|
||||
Transaction,
|
||||
} from '../../schemas/transaction.schema'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
import * as v from 'valibot'
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
import { Uuidv4, uuidv4Schema } from '../../schemas/typeConverter.schema'
|
||||
|
||||
export class CreationTransactionRole extends AbstractTransactionRole {
|
||||
private tx: CreationTransaction
|
||||
private homeCommunityUuid: Uuidv4
|
||||
constructor(input: CreationTransactionInput) {
|
||||
private readonly homeCommunityTopicId: HieroId
|
||||
private readonly creationTransaction: CreationTransaction
|
||||
constructor(transaction: Transaction) {
|
||||
super()
|
||||
this.tx = v.parse(creationTransactionSchema, input)
|
||||
this.homeCommunityUuid = v.parse(
|
||||
uuidv4Schema,
|
||||
KeyPairCacheManager.getInstance().getHomeCommunityUUID()
|
||||
)
|
||||
this.creationTransaction = parse(creationTransactionSchema, transaction)
|
||||
this.homeCommunityTopicId = KeyPairCacheManager.getInstance().getHomeCommunityTopicId()
|
||||
if (
|
||||
this.homeCommunityUuid !== this.tx.user.communityUuid ||
|
||||
this.homeCommunityUuid !== this.tx.linkedUser.communityUuid
|
||||
this.homeCommunityTopicId !== this.creationTransaction.user.communityTopicId ||
|
||||
this.homeCommunityTopicId !== this.creationTransaction.linkedUser.communityTopicId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'creation: both recipient and signer must belong to home community',
|
||||
})
|
||||
throw new Error('creation: both recipient and signer must belong to home community')
|
||||
}
|
||||
}
|
||||
|
||||
getSenderCommunityUuid(): string {
|
||||
return this.tx.user.communityUuid
|
||||
getSenderCommunityTopicId(): HieroId {
|
||||
return this.creationTransaction.user.communityTopicId
|
||||
}
|
||||
|
||||
getRecipientCommunityUuid(): string {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'creation: cannot be used as cross group transaction',
|
||||
})
|
||||
getRecipientCommunityTopicId(): HieroId {
|
||||
throw new Error('creation: cannot be used as cross group transaction')
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
// Recipient: user (account owner)
|
||||
const recipientKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(this.tx.user)
|
||||
new KeyPairIdentifierLogic(this.creationTransaction.user),
|
||||
)
|
||||
// Signer: linkedUser (admin/moderator)
|
||||
const signerKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(this.tx.linkedUser)
|
||||
new KeyPairIdentifierLogic(this.creationTransaction.linkedUser),
|
||||
)
|
||||
const homeCommunityKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic({
|
||||
communityUuid: this.homeCommunityUuid
|
||||
new KeyPairIdentifierLogic({
|
||||
communityTopicId: this.homeCommunityTopicId,
|
||||
}),
|
||||
)
|
||||
// Memo: encrypted, home community and recipient can decrypt it
|
||||
builder
|
||||
.setCreatedAt(this.tx.createdAt)
|
||||
.addMemo(new EncryptedMemo(
|
||||
this.tx.memo,
|
||||
new AuthenticatedEncryption(homeCommunityKeyPair),
|
||||
new AuthenticatedEncryption(recipientKeyPair),
|
||||
))
|
||||
.setTransactionCreation(
|
||||
new TransferAmount(
|
||||
recipientKeyPair.getPublicKey(),
|
||||
this.tx.amount,
|
||||
.setCreatedAt(this.creationTransaction.createdAt)
|
||||
.addMemo(
|
||||
new EncryptedMemo(
|
||||
this.creationTransaction.memo,
|
||||
new AuthenticatedEncryption(homeCommunityKeyPair),
|
||||
new AuthenticatedEncryption(recipientKeyPair),
|
||||
),
|
||||
this.tx.targetDate,
|
||||
)
|
||||
.setTransactionCreation(
|
||||
new TransferAmount(recipientKeyPair.getPublicKey(), this.creationTransaction.amount),
|
||||
this.creationTransaction.targetDate,
|
||||
)
|
||||
.sign(signerKeyPair)
|
||||
return builder
|
||||
|
||||
@ -5,53 +5,52 @@ import {
|
||||
GradidoTransfer,
|
||||
TransferAmount,
|
||||
} from 'gradido-blockchain-js'
|
||||
|
||||
import { parse } from 'valibot'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import { IdentifierSeed, identifierSeedSchema } from '../../schemas/account.schema'
|
||||
import {
|
||||
DeferredTransferTransaction,
|
||||
deferredTransferTransactionSchema,
|
||||
Transaction,
|
||||
} from '../../schemas/transaction.schema'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
import { DeferredTransferTransactionInput, deferredTransferTransactionSchema, DeferredTransferTransaction } from '../../schemas/transaction.schema'
|
||||
import * as v from 'valibot'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { identifierSeedSchema, IdentifierSeed } from '../../schemas/account.schema'
|
||||
|
||||
export class DeferredTransferTransactionRole extends AbstractTransactionRole {
|
||||
private tx: DeferredTransferTransaction
|
||||
private seed: IdentifierSeed
|
||||
constructor(protected input: DeferredTransferTransactionInput) {
|
||||
private readonly seed: IdentifierSeed
|
||||
private readonly deferredTransferTransaction: DeferredTransferTransaction
|
||||
constructor(transaction: Transaction) {
|
||||
super()
|
||||
this.tx = v.parse(deferredTransferTransactionSchema, input)
|
||||
this.seed = v.parse(identifierSeedSchema, input.linkedUser.seed)
|
||||
this.deferredTransferTransaction = parse(deferredTransferTransactionSchema, transaction)
|
||||
this.seed = parse(identifierSeedSchema, this.deferredTransferTransaction.linkedUser.seed)
|
||||
}
|
||||
|
||||
getSenderCommunityUuid(): string {
|
||||
return this.tx.user.communityUuid
|
||||
getSenderCommunityTopicId(): HieroId {
|
||||
return this.deferredTransferTransaction.user.communityTopicId
|
||||
}
|
||||
|
||||
getRecipientCommunityUuid(): string {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_IMPLEMENTED',
|
||||
message: 'deferred transfer: cannot be used as cross group transaction yet',
|
||||
})
|
||||
getRecipientCommunityTopicId(): HieroId {
|
||||
throw new Error('deferred transfer: cannot be used as cross group transaction yet')
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
const senderKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(this.tx.user)
|
||||
new KeyPairIdentifierLogic(this.deferredTransferTransaction.user),
|
||||
)
|
||||
const recipientKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic({
|
||||
communityUuid: this.tx.linkedUser.communityUuid,
|
||||
new KeyPairIdentifierLogic({
|
||||
communityTopicId: this.deferredTransferTransaction.linkedUser.communityTopicId,
|
||||
seed: this.seed,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
builder
|
||||
.setCreatedAt(this.tx.createdAt)
|
||||
.setCreatedAt(this.deferredTransferTransaction.createdAt)
|
||||
.addMemo(
|
||||
new EncryptedMemo(
|
||||
this.tx.memo,
|
||||
this.deferredTransferTransaction.memo,
|
||||
new AuthenticatedEncryption(senderKeyPair),
|
||||
new AuthenticatedEncryption(recipientKeyPair),
|
||||
),
|
||||
@ -60,13 +59,13 @@ export class DeferredTransferTransactionRole extends AbstractTransactionRole {
|
||||
new GradidoTransfer(
|
||||
new TransferAmount(
|
||||
senderKeyPair.getPublicKey(),
|
||||
this.tx.amount.calculateCompoundInterest(
|
||||
this.tx.timeoutDuration.getSeconds(),
|
||||
this.deferredTransferTransaction.amount.calculateCompoundInterest(
|
||||
this.deferredTransferTransaction.timeoutDuration.getSeconds(),
|
||||
),
|
||||
),
|
||||
recipientKeyPair.getPublicKey(),
|
||||
),
|
||||
this.tx.timeoutDuration,
|
||||
this.deferredTransferTransaction.timeoutDuration,
|
||||
)
|
||||
.sign(senderKeyPair)
|
||||
return builder
|
||||
|
||||
@ -1,88 +1,71 @@
|
||||
import { GradidoTransactionBuilder, GradidoTransfer, TransferAmount } from 'gradido-blockchain-js'
|
||||
import { parse } from 'valibot'
|
||||
import { getTransactionsForAccount } from '../../client/GradidoNode/api'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import {
|
||||
GradidoTransactionBuilder,
|
||||
GradidoTransfer,
|
||||
GradidoUnit,
|
||||
TransferAmount,
|
||||
} from 'gradido-blockchain-js'
|
||||
|
||||
import { getTransactionsForAccount } from '@/client/GradidoNode'
|
||||
import { KeyPairIdentifier } from '@/data/KeyPairIdentifier'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { communityUuidToTopic, uuid4ToHash } from '@/utils/typeConverter'
|
||||
|
||||
RedeemDeferredTransferTransaction,
|
||||
redeemDeferredTransferTransactionSchema,
|
||||
Transaction,
|
||||
UserAccount,
|
||||
} from '../../schemas/transaction.schema'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
|
||||
export class RedeemDeferredTransferTransactionRole extends AbstractTransactionRole {
|
||||
private linkedUser: UserIdentifier
|
||||
constructor(protected self: TransactionDraft) {
|
||||
private linkedUser: UserAccount
|
||||
private readonly redeemDeferredTransferTransaction: RedeemDeferredTransferTransaction
|
||||
constructor(transaction: Transaction) {
|
||||
super()
|
||||
if (!this.self.linkedUser) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'transfer: linked user missing',
|
||||
)
|
||||
}
|
||||
this.linkedUser = this.self.linkedUser
|
||||
this.redeemDeferredTransferTransaction = parse(
|
||||
redeemDeferredTransferTransactionSchema,
|
||||
transaction,
|
||||
)
|
||||
this.linkedUser = this.redeemDeferredTransferTransaction.linkedUser
|
||||
}
|
||||
|
||||
getSenderCommunityUuid(): string {
|
||||
return this.self.user.communityUuid
|
||||
getSenderCommunityTopicId(): HieroId {
|
||||
return this.redeemDeferredTransferTransaction.user.communityTopicId
|
||||
}
|
||||
|
||||
getRecipientCommunityUuid(): string {
|
||||
return this.linkedUser.communityUuid
|
||||
getRecipientCommunityTopicId(): HieroId {
|
||||
return this.linkedUser.communityTopicId
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
if (!this.self.amount) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'redeem deferred transfer: amount missing',
|
||||
)
|
||||
}
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
const senderKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.self.user))
|
||||
const senderKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(this.redeemDeferredTransferTransaction.user),
|
||||
)
|
||||
const senderPublicKey = senderKeyPair.getPublicKey()
|
||||
if (!senderPublicKey) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.INVALID_PARAMETER,
|
||||
"redeem deferred transfer: couldn't calculate sender public key",
|
||||
)
|
||||
throw new Error("redeem deferred transfer: couldn't calculate sender public key")
|
||||
}
|
||||
// load deferred transfer transaction from gradido node
|
||||
const transactions = await getTransactionsForAccount(
|
||||
senderPublicKey,
|
||||
communityUuidToTopic(this.getSenderCommunityUuid()),
|
||||
{ maxResultCount: 2, topic: this.getSenderCommunityTopicId() },
|
||||
senderPublicKey.convertToHex(),
|
||||
)
|
||||
if (!transactions || transactions.length !== 1) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.NOT_FOUND,
|
||||
"redeem deferred transfer: couldn't find deferred transfer on Gradido Node",
|
||||
)
|
||||
throw new Error("redeem deferred transfer: couldn't find deferred transfer on Gradido Node")
|
||||
}
|
||||
const deferredTransfer = transactions[0]
|
||||
const deferredTransferBody = deferredTransfer.getGradidoTransaction()?.getTransactionBody()
|
||||
if (!deferredTransferBody) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.NOT_FOUND,
|
||||
throw new Error(
|
||||
"redeem deferred transfer: couldn't deserialize deferred transfer from Gradido Node",
|
||||
)
|
||||
}
|
||||
const recipientKeyPair = await KeyPairCalculation(new KeyPairIdentifier(this.linkedUser))
|
||||
const recipientKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(this.linkedUser))
|
||||
|
||||
builder
|
||||
.setCreatedAt(new Date(this.self.createdAt))
|
||||
.setCreatedAt(this.redeemDeferredTransferTransaction.createdAt)
|
||||
.setRedeemDeferredTransfer(
|
||||
deferredTransfer.getId(),
|
||||
new GradidoTransfer(
|
||||
new TransferAmount(
|
||||
senderKeyPair.getPublicKey(),
|
||||
GradidoUnit.fromString(this.self.amount),
|
||||
this.redeemDeferredTransferTransaction.amount,
|
||||
),
|
||||
recipientKeyPair.getPublicKey(),
|
||||
),
|
||||
@ -91,13 +74,11 @@ export class RedeemDeferredTransferTransactionRole extends AbstractTransactionRo
|
||||
for (let i = 0; i < memos.size(); i++) {
|
||||
builder.addMemo(memos.get(i))
|
||||
}
|
||||
const senderCommunity = this.self.user.communityUuid
|
||||
const recipientCommunity = this.linkedUser.communityUuid
|
||||
const senderCommunity = this.redeemDeferredTransferTransaction.user.communityTopicId
|
||||
const recipientCommunity = this.linkedUser.communityTopicId
|
||||
if (senderCommunity !== recipientCommunity) {
|
||||
// we have a cross group transaction
|
||||
builder
|
||||
.setSenderCommunity(uuid4ToHash(senderCommunity).convertToHex())
|
||||
.setRecipientCommunity(uuid4ToHash(recipientCommunity).convertToHex())
|
||||
builder.setSenderCommunity(senderCommunity).setRecipientCommunity(recipientCommunity)
|
||||
}
|
||||
builder.sign(senderKeyPair)
|
||||
return builder
|
||||
|
||||
@ -1,69 +1,60 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { GradidoTransactionBuilder } from 'gradido-blockchain-js'
|
||||
|
||||
import { parse } from 'valibot'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
|
||||
import { Uuidv4Hash } from '../../data/Uuidv4Hash'
|
||||
import {
|
||||
IdentifierCommunityAccount,
|
||||
identifierCommunityAccountSchema,
|
||||
} from '../../schemas/account.schema'
|
||||
import {
|
||||
RegisterAddressTransaction,
|
||||
registerAddressTransactionSchema,
|
||||
Transaction,
|
||||
} from '../../schemas/transaction.schema'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
import { RegisterAddressTransactionInput, registerAddressTransactionSchema, RegisterAddressTransaction } from '../../schemas/transaction.schema'
|
||||
import { IdentifierAccount, IdentifierCommunityAccount, identifierCommunityAccountSchema } from '../../schemas/account.schema'
|
||||
import * as v from 'valibot'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { uuid4ToHashSchema } from '../../schemas/typeConverter.schema'
|
||||
|
||||
export class RegisterAddressTransactionRole extends AbstractTransactionRole {
|
||||
private tx: RegisterAddressTransaction
|
||||
private account: IdentifierCommunityAccount
|
||||
constructor(input: RegisterAddressTransactionInput) {
|
||||
private readonly registerAddressTransaction: RegisterAddressTransaction
|
||||
private readonly account: IdentifierCommunityAccount
|
||||
constructor(input: Transaction) {
|
||||
super()
|
||||
this.tx = v.parse(registerAddressTransactionSchema, input)
|
||||
this.account = v.parse(identifierCommunityAccountSchema, input.user.account)
|
||||
this.registerAddressTransaction = parse(registerAddressTransactionSchema, input)
|
||||
this.account = parse(identifierCommunityAccountSchema, input.user.account)
|
||||
}
|
||||
|
||||
getSenderCommunityUuid(): string {
|
||||
return this.tx.user.communityUuid
|
||||
getSenderCommunityTopicId(): HieroId {
|
||||
return this.registerAddressTransaction.user.communityTopicId
|
||||
}
|
||||
|
||||
getRecipientCommunityUuid(): string {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_IMPLEMENTED',
|
||||
message: 'register address: cannot be used as cross group transaction yet',
|
||||
})
|
||||
getRecipientCommunityTopicId(): HieroId {
|
||||
throw new Error('register address: cannot be used as cross group transaction yet')
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
const communityKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic({ communityUuid: this.tx.user.communityUuid }),
|
||||
)
|
||||
const userKeyPairIdentifier: IdentifierAccount = {
|
||||
communityUuid: this.tx.user.communityUuid,
|
||||
account: {
|
||||
userUuid: this.account.userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
}
|
||||
const accountKeyPairIdentifier: IdentifierAccount = {
|
||||
communityUuid: this.tx.user.communityUuid,
|
||||
account: {
|
||||
userUuid: this.account.userUuid,
|
||||
accountNr: this.account.accountNr,
|
||||
},
|
||||
}
|
||||
const userKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(userKeyPairIdentifier)
|
||||
new KeyPairIdentifierLogic({
|
||||
communityTopicId: this.registerAddressTransaction.user.communityTopicId,
|
||||
}),
|
||||
)
|
||||
const accountKeyPairIdentifier = this.registerAddressTransaction.user
|
||||
// when accountNr is 0 it is the user account
|
||||
const userKeyPairIdentifier = accountKeyPairIdentifier
|
||||
userKeyPairIdentifier.account.accountNr = 0
|
||||
|
||||
const userKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(userKeyPairIdentifier))
|
||||
const accountKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(accountKeyPairIdentifier)
|
||||
new KeyPairIdentifierLogic(accountKeyPairIdentifier),
|
||||
)
|
||||
|
||||
builder
|
||||
.setCreatedAt(this.tx.createdAt)
|
||||
.setCreatedAt(this.registerAddressTransaction.createdAt)
|
||||
.setRegisterAddress(
|
||||
userKeyPair.getPublicKey(),
|
||||
this.tx.accountType,
|
||||
v.parse(uuid4ToHashSchema, this.account.userUuid),
|
||||
this.registerAddressTransaction.accountType,
|
||||
new Uuidv4Hash(this.account.userUuid).getAsMemoryBlock(),
|
||||
accountKeyPair.getPublicKey(),
|
||||
)
|
||||
.sign(communityKeyPair)
|
||||
|
||||
@ -1,24 +1,17 @@
|
||||
/* eslint-disable camelcase */
|
||||
import {
|
||||
GradidoTransaction,
|
||||
InteractionSerialize,
|
||||
InteractionValidate,
|
||||
MemoryBlock,
|
||||
InteractionValidate,
|
||||
MemoryBlock,
|
||||
ValidateType_SINGLE,
|
||||
} from 'gradido-blockchain-js'
|
||||
|
||||
import { sendMessage as iotaSendMessage } from '@/client/IotaClient'
|
||||
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 { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { TransactionRecipe } from '@/graphql/model/TransactionRecipe'
|
||||
import { TransactionResult } from '@/graphql/model/TransactionResult'
|
||||
import { logger } from '@/logging/logger'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { communityUuidToTopic } from '@/utils/typeConverter'
|
||||
|
||||
import { getLogger } from 'log4js'
|
||||
import { safeParse, parse } from 'valibot'
|
||||
import { Community, communitySchema } from '../../client/backend/community.schema'
|
||||
import { HieroClient } from '../../client/HieroClient'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import { Transaction, transactionSchema } from '../../schemas/transaction.schema'
|
||||
import { HieroId, HieroTransactionId, hieroTransactionIdSchema } from '../../schemas/typeGuard.schema'
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
import { CommunityRootTransactionRole } from './CommunityRootTransaction.role'
|
||||
import { CreationTransactionRole } from './CreationTransaction.role'
|
||||
@ -26,6 +19,9 @@ import { DeferredTransferTransactionRole } from './DeferredTransferTransaction.r
|
||||
import { RedeemDeferredTransferTransactionRole } from './RedeemDeferredTransferTransaction.role'
|
||||
import { RegisterAddressTransactionRole } from './RegisterAddressTransaction.role'
|
||||
import { TransferTransactionRole } from './TransferTransaction.role'
|
||||
import { InputTransactionType } from '../../enum/InputTransactionType'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.interactions.sendToIota.SendToIotaContext`)
|
||||
|
||||
/**
|
||||
* @DCI-Context
|
||||
@ -33,95 +29,74 @@ import { TransferTransactionRole } from './TransferTransaction.role'
|
||||
* send every transaction only once to iota!
|
||||
*/
|
||||
export async function SendToIotaContext(
|
||||
input: TransactionDraft | CommunityDraft,
|
||||
): Promise<TransactionResult> {
|
||||
input: Transaction | Community,
|
||||
): Promise<HieroTransactionId> {
|
||||
// let gradido blockchain validator run, it will throw an exception when something is wrong
|
||||
const validate = (transaction: GradidoTransaction): void => {
|
||||
try {
|
||||
// throw an exception when something is wrong
|
||||
const validator = new InteractionValidate(transaction)
|
||||
validator.run(ValidateType_SINGLE)
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
throw new TransactionError(TransactionErrorType.VALIDATION_ERROR, e.message)
|
||||
} else if (typeof e === 'string') {
|
||||
throw new TransactionError(TransactionErrorType.VALIDATION_ERROR, e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
const validator = new InteractionValidate(transaction)
|
||||
validator.run(ValidateType_SINGLE)
|
||||
}
|
||||
|
||||
const sendViaIota = async (
|
||||
// send transaction as hiero topic message
|
||||
const sendViaHiero = async (
|
||||
gradidoTransaction: GradidoTransaction,
|
||||
topic: string,
|
||||
): Promise<MemoryBlock> => {
|
||||
// protobuf serializing function
|
||||
const serialized = new InteractionSerialize(gradidoTransaction).run()
|
||||
if (!serialized) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.PROTO_ENCODE_ERROR,
|
||||
'cannot serialize transaction',
|
||||
)
|
||||
}
|
||||
const resultMessage = await iotaSendMessage(
|
||||
Uint8Array.from(serialized.data()),
|
||||
Uint8Array.from(Buffer.from(topic, 'hex')),
|
||||
)
|
||||
logger.info('transmitted Gradido Transaction to Iota', {
|
||||
messageId: resultMessage.messageId,
|
||||
})
|
||||
return MemoryBlock.fromHex(resultMessage.messageId)
|
||||
topic: HieroId,
|
||||
): Promise<string> => {
|
||||
const client = HieroClient.getInstance()
|
||||
const resultMessage = await client.sendMessage(topic, gradidoTransaction)
|
||||
const transactionId = resultMessage.response.transactionId.toString()
|
||||
logger.info('transmitted Gradido Transaction to Iota', { transactionId })
|
||||
return transactionId
|
||||
}
|
||||
|
||||
let role: AbstractTransactionRole
|
||||
if (input instanceof TransactionDraft) {
|
||||
switch (input.type) {
|
||||
case InputTransactionType.GRADIDO_CREATION:
|
||||
role = new CreationTransactionRole(input)
|
||||
break
|
||||
case InputTransactionType.GRADIDO_TRANSFER:
|
||||
role = new TransferTransactionRole(input)
|
||||
break
|
||||
case InputTransactionType.REGISTER_ADDRESS:
|
||||
role = new RegisterAddressTransactionRole(input)
|
||||
break
|
||||
case InputTransactionType.GRADIDO_DEFERRED_TRANSFER:
|
||||
role = new DeferredTransferTransactionRole(input)
|
||||
break
|
||||
case InputTransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER:
|
||||
role = new RedeemDeferredTransferTransactionRole(input)
|
||||
break
|
||||
default:
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.NOT_IMPLEMENTED_YET,
|
||||
'not supported transaction type: ' + input.type,
|
||||
)
|
||||
// choose correct role based on transaction type and input type
|
||||
const chooseCorrectRole = (input: Transaction | Community): AbstractTransactionRole => {
|
||||
const transactionParsingResult = safeParse(transactionSchema, input)
|
||||
const communityParsingResult = safeParse(communitySchema, input)
|
||||
if (transactionParsingResult.success) {
|
||||
const transaction = transactionParsingResult.output
|
||||
switch (transaction.type) {
|
||||
case InputTransactionType.GRADIDO_CREATION:
|
||||
return new CreationTransactionRole(transaction)
|
||||
case InputTransactionType.GRADIDO_TRANSFER:
|
||||
return new TransferTransactionRole(transaction)
|
||||
case InputTransactionType.REGISTER_ADDRESS:
|
||||
return new RegisterAddressTransactionRole(transaction)
|
||||
case InputTransactionType.GRADIDO_DEFERRED_TRANSFER:
|
||||
return new DeferredTransferTransactionRole(transaction)
|
||||
case InputTransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER:
|
||||
return new RedeemDeferredTransferTransactionRole(transaction)
|
||||
default:
|
||||
throw new Error('not supported transaction type: ' + transaction.type)
|
||||
}
|
||||
} else if (communityParsingResult.success) {
|
||||
return new CommunityRootTransactionRole(communityParsingResult.output)
|
||||
} else {
|
||||
throw new Error('not expected input')
|
||||
}
|
||||
} else if (input instanceof CommunityDraft) {
|
||||
role = new CommunityRootTransactionRole(input)
|
||||
} else {
|
||||
throw new LogError('not expected input')
|
||||
}
|
||||
|
||||
const role = chooseCorrectRole(input)
|
||||
const builder = await role.getGradidoTransactionBuilder()
|
||||
if (builder.isCrossCommunityTransaction()) {
|
||||
const outboundTransaction = builder.buildOutbound()
|
||||
validate(outboundTransaction)
|
||||
const outboundIotaMessageId = await sendViaIota(
|
||||
const outboundIotaMessageId = await sendViaHiero(
|
||||
outboundTransaction,
|
||||
communityUuidToTopic(role.getSenderCommunityUuid()),
|
||||
role.getSenderCommunityTopicId(),
|
||||
)
|
||||
builder.setParentMessageId(outboundIotaMessageId)
|
||||
builder.setParentMessageId(MemoryBlock.createPtr(new MemoryBlock(outboundIotaMessageId)))
|
||||
const inboundTransaction = builder.buildInbound()
|
||||
validate(inboundTransaction)
|
||||
await sendViaIota(inboundTransaction, communityUuidToTopic(role.getRecipientCommunityUuid()))
|
||||
return new TransactionResult(new TransactionRecipe(outboundTransaction, outboundIotaMessageId))
|
||||
await sendViaHiero(inboundTransaction, role.getRecipientCommunityTopicId())
|
||||
return parse(hieroTransactionIdSchema, outboundIotaMessageId)
|
||||
} else {
|
||||
const transaction = builder.build()
|
||||
validate(transaction)
|
||||
const iotaMessageId = await sendViaIota(
|
||||
const iotaMessageId = await sendViaHiero(
|
||||
transaction,
|
||||
communityUuidToTopic(role.getSenderCommunityUuid()),
|
||||
role.getSenderCommunityTopicId(),
|
||||
)
|
||||
return new TransactionResult(new TransactionRecipe(transaction, iotaMessageId))
|
||||
return parse(hieroTransactionIdSchema, iotaMessageId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,60 +4,61 @@ import {
|
||||
GradidoTransactionBuilder,
|
||||
TransferAmount,
|
||||
} from 'gradido-blockchain-js'
|
||||
|
||||
import { parse } from 'valibot'
|
||||
import { KeyPairIdentifierLogic } from '../../data/KeyPairIdentifier.logic'
|
||||
import {
|
||||
TransferTransaction,
|
||||
transferTransactionSchema,
|
||||
Transaction,
|
||||
} from '../../schemas/transaction.schema'
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
import { TransferTransactionInput, transferTransactionSchema, TransferTransaction } from '../../schemas/transaction.schema'
|
||||
import * as v from 'valibot'
|
||||
import { uuid4ToTopicSchema } from '../../schemas/typeConverter.schema'
|
||||
import { HieroId } from '../../schemas/typeGuard.schema'
|
||||
|
||||
export class TransferTransactionRole extends AbstractTransactionRole {
|
||||
private tx: TransferTransaction
|
||||
constructor(input: TransferTransactionInput) {
|
||||
private transferTransaction: TransferTransaction
|
||||
constructor(input: Transaction) {
|
||||
super()
|
||||
this.tx = v.parse(transferTransactionSchema, input)
|
||||
this.transferTransaction = parse(transferTransactionSchema, input)
|
||||
}
|
||||
|
||||
getSenderCommunityUuid(): string {
|
||||
return this.tx.user.communityUuid
|
||||
getSenderCommunityTopicId(): HieroId {
|
||||
return this.transferTransaction.user.communityTopicId
|
||||
}
|
||||
|
||||
getRecipientCommunityUuid(): string {
|
||||
return this.tx.linkedUser.communityUuid
|
||||
getRecipientCommunityTopicId(): HieroId {
|
||||
return this.transferTransaction.linkedUser.communityTopicId
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
// sender + signer
|
||||
const senderKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(this.tx.user)
|
||||
)
|
||||
const senderKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(this.transferTransaction.user))
|
||||
// recipient
|
||||
const recipientKeyPair = await KeyPairCalculation(
|
||||
new KeyPairIdentifierLogic(this.tx.linkedUser)
|
||||
new KeyPairIdentifierLogic(this.transferTransaction.linkedUser),
|
||||
)
|
||||
|
||||
builder
|
||||
.setCreatedAt(new Date(this.tx.createdAt))
|
||||
.setCreatedAt(this.transferTransaction.createdAt)
|
||||
.addMemo(
|
||||
new EncryptedMemo(
|
||||
this.tx.memo,
|
||||
this.transferTransaction.memo,
|
||||
new AuthenticatedEncryption(senderKeyPair),
|
||||
new AuthenticatedEncryption(recipientKeyPair),
|
||||
),
|
||||
)
|
||||
.setTransactionTransfer(
|
||||
new TransferAmount(senderKeyPair.getPublicKey(), this.tx.amount),
|
||||
new TransferAmount(senderKeyPair.getPublicKey(), this.transferTransaction.amount),
|
||||
recipientKeyPair.getPublicKey(),
|
||||
)
|
||||
const senderCommunity = this.tx.user.communityUuid
|
||||
const recipientCommunity = this.tx.linkedUser.communityUuid
|
||||
const senderCommunity = this.transferTransaction.user.communityTopicId
|
||||
const recipientCommunity = this.transferTransaction.linkedUser.communityTopicId
|
||||
if (senderCommunity !== recipientCommunity) {
|
||||
// we have a cross group transaction
|
||||
builder
|
||||
.setSenderCommunity(v.parse(uuid4ToTopicSchema, senderCommunity))
|
||||
.setRecipientCommunity(v.parse(uuid4ToTopicSchema, recipientCommunity))
|
||||
.setSenderCommunity(senderCommunity)
|
||||
.setRecipientCommunity(recipientCommunity)
|
||||
}
|
||||
builder.sign(senderKeyPair)
|
||||
return builder
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import * as v from 'valibot'
|
||||
import { uuidv4Schema } from './typeGuard.schema'
|
||||
import { hieroIdSchema, uuidv4Schema } from './typeGuard.schema'
|
||||
|
||||
// use code from transaction links
|
||||
export const identifierSeedSchema = v.object({
|
||||
seed: v.pipe(
|
||||
v.string('expect string type'),
|
||||
v.length(24, 'expect seed length 24')
|
||||
)
|
||||
seed: v.pipe(v.string('expect string type'), v.length(24, 'expect seed length 24')),
|
||||
})
|
||||
|
||||
export type IdentifierSeed = v.InferOutput<typeof identifierSeedSchema>
|
||||
@ -22,7 +19,7 @@ export type IdentifierCommunityAccount = v.InferOutput<typeof identifierCommunit
|
||||
// identifier for gradido account, including the community uuid
|
||||
export const identifierAccountSchema = v.pipe(
|
||||
v.object({
|
||||
communityUuid: uuidv4Schema,
|
||||
communityTopicId: hieroIdSchema,
|
||||
account: v.nullish(identifierCommunityAccountSchema, undefined),
|
||||
seed: v.nullish(identifierSeedSchema, undefined),
|
||||
}),
|
||||
@ -32,7 +29,7 @@ export const identifierAccountSchema = v.pipe(
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 'expect seed or account')
|
||||
}, 'expect seed or account'),
|
||||
)
|
||||
|
||||
export type IdentifierAccountInput = v.InferInput<typeof identifierAccountSchema>
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import * as v from 'valibot'
|
||||
import { MemoryBlock } from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
|
||||
export const keyGenerationSeedSchema = v.pipe(
|
||||
v.string('expect string type'),
|
||||
v.hexadecimal('expect hexadecimal string'),
|
||||
v.length(64, 'expect seed length minimum 64 characters (32 Bytes)'),
|
||||
v.transform<string, MemoryBlock>(
|
||||
(input: string) => MemoryBlock.fromHex(input),
|
||||
),
|
||||
)
|
||||
v.transform<string, MemoryBlock>((input: string) => MemoryBlock.fromHex(input)),
|
||||
)
|
||||
|
||||
@ -1,10 +1,26 @@
|
||||
import { describe, it, expect } from 'bun:test'
|
||||
import { transactionIdentifierSchema, transactionSchema, TransactionInput, memoSchema } from './transaction.schema'
|
||||
import { InputTransactionType } from '../enum/InputTransactionType'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import * as v from 'valibot'
|
||||
import { GradidoUnit, DurationSeconds } from 'gradido-blockchain-js'
|
||||
import { describe, expect, it, beforeAll } from 'bun:test'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { DurationSeconds, GradidoUnit } from 'gradido-blockchain-js'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { parse } from 'valibot'
|
||||
import { InputTransactionType } from '../enum/InputTransactionType'
|
||||
import {
|
||||
TransactionInput,
|
||||
transactionSchema,
|
||||
} from './transaction.schema'
|
||||
import { transactionIdentifierSchema } from '../client/GradidoNode/input.schema'
|
||||
import {
|
||||
gradidoAmountSchema,
|
||||
HieroId,
|
||||
hieroIdSchema,
|
||||
HieroTransactionId,
|
||||
hieroTransactionIdSchema,
|
||||
Memo,
|
||||
memoSchema,
|
||||
timeoutDurationSchema,
|
||||
Uuidv4,
|
||||
uuidv4Schema
|
||||
} from '../schemas/typeGuard.schema'
|
||||
|
||||
const transactionLinkCode = (date: Date): string => {
|
||||
const time = date.getTime().toString(16)
|
||||
@ -14,149 +30,154 @@ const transactionLinkCode = (date: Date): string => {
|
||||
.substring(0, 24 - time.length) + time
|
||||
)
|
||||
}
|
||||
let topic: HieroId
|
||||
const topicString = '0.0.261'
|
||||
let hieroTransactionId: HieroTransactionId
|
||||
beforeAll(() => {
|
||||
topic = parse(hieroIdSchema, topicString)
|
||||
hieroTransactionId = parse(hieroTransactionIdSchema, '0.0.261-1755348116-1281621')
|
||||
})
|
||||
|
||||
describe('transaction schemas', () => {
|
||||
|
||||
describe('transactionIdentifierSchema ', () => {
|
||||
it('valid, transaction identified by transactionNr and topic', () => {
|
||||
expect(v.parse(transactionIdentifierSchema, {
|
||||
transactionNr: 1,
|
||||
iotaTopic: 'c00b210fc0a189df054eb9dafb584c527e9aeb537a62a35d44667f54529c73f5'
|
||||
})).toEqual({
|
||||
transactionNr: 1,
|
||||
iotaMessageId: undefined,
|
||||
iotaTopic: 'c00b210fc0a189df054eb9dafb584c527e9aeb537a62a35d44667f54529c73f5'
|
||||
expect(
|
||||
parse(transactionIdentifierSchema, {
|
||||
transactionNr: 1,
|
||||
topic: topicString,
|
||||
}),
|
||||
).toEqual({
|
||||
transactionNr: 1,
|
||||
hieroTransactionId: undefined,
|
||||
topic,
|
||||
})
|
||||
})
|
||||
it('valid, transaction identified by iotaMessageId and topic', () => {
|
||||
expect(v.parse(transactionIdentifierSchema, {
|
||||
iotaMessageId: '1b33a3cf7eb5dde04ed7ae571db1763006811ff6b7bb35b3d1c780de153af9dd',
|
||||
iotaTopic: 'c00b210fc0a189df054eb9dafb584c527e9aeb537a62a35d44667f54529c73f5'
|
||||
})).toEqual({
|
||||
transactionNr: 0,
|
||||
iotaMessageId: '1b33a3cf7eb5dde04ed7ae571db1763006811ff6b7bb35b3d1c780de153af9dd',
|
||||
iotaTopic: 'c00b210fc0a189df054eb9dafb584c527e9aeb537a62a35d44667f54529c73f5'
|
||||
it('valid, transaction identified by hieroTransactionId and topic', () => {
|
||||
expect(
|
||||
parse(transactionIdentifierSchema, {
|
||||
hieroTransactionId: '0.0.261-1755348116-1281621',
|
||||
topic: topicString,
|
||||
}),
|
||||
).toEqual({
|
||||
hieroTransactionId,
|
||||
topic
|
||||
})
|
||||
})
|
||||
it('invalid, missing topic', () => {
|
||||
expect(() => v.parse(transactionIdentifierSchema, {
|
||||
transactionNr: 1,
|
||||
iotaMessageId: '1b33a3cf7eb5dde04ed7ae571db1763006811ff6b7bb35b3d1c780de153af9dd',
|
||||
})).toThrowError(new Error('Invalid key: Expected "iotaTopic" but received undefined'))
|
||||
expect(() =>
|
||||
parse(transactionIdentifierSchema, {
|
||||
transactionNr: 1,
|
||||
hieroTransactionId: '0.0.261-1755348116-1281621',
|
||||
}),
|
||||
).toThrowError(new Error('Invalid key: Expected "topic" but received undefined'))
|
||||
})
|
||||
it('invalid, transactionNr and iotaMessageId set', () => {
|
||||
expect(() => v.parse(transactionIdentifierSchema, {
|
||||
transactionNr: 1,
|
||||
iotaMessageId: '1b33a3cf7eb5dde04ed7ae571db1763006811ff6b7bb35b3d1c780de153af9dd',
|
||||
iotaTopic: 'c00b210fc0a189df054eb9dafb584c527e9aeb537a62a35d44667f54529c73f5'
|
||||
})).toThrowError(new Error('expect transactionNr or iotaMessageId not both'))
|
||||
expect(() =>
|
||||
parse(transactionIdentifierSchema, {
|
||||
transactionNr: 1,
|
||||
hieroTransactionId: '0.0.261-1755348116-1281621',
|
||||
topic
|
||||
}),
|
||||
).toThrowError(new Error('expect transactionNr or hieroTransactionId not both'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('transactionSchema', () => {
|
||||
describe('transactionSchema', () => {
|
||||
let userUuid: Uuidv4
|
||||
let userUuidString: string
|
||||
let memoString: string
|
||||
let memo: Memo
|
||||
beforeAll(() => {
|
||||
userUuidString = uuidv4()
|
||||
userUuid = parse(uuidv4Schema, userUuidString)
|
||||
memoString = 'TestMemo'
|
||||
memo = parse(memoSchema, memoString)
|
||||
})
|
||||
it('valid, register new user address', () => {
|
||||
const registerAddress: TransactionInput = {
|
||||
user: {
|
||||
communityUuid: uuidv4(),
|
||||
account: {
|
||||
userUuid: uuidv4(),
|
||||
}
|
||||
},
|
||||
type: InputTransactionType.REGISTER_ADDRESS,
|
||||
user: {
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
type: InputTransactionType.REGISTER_ADDRESS,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
}
|
||||
expect(v.parse(transactionSchema, registerAddress)).toEqual({
|
||||
expect(parse(transactionSchema, registerAddress)).toEqual({
|
||||
user: {
|
||||
communityUuid: registerAddress.user.communityUuid,
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid: registerAddress.user.account!.userUuid,
|
||||
accountNr: 1,
|
||||
}
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
type: registerAddress.type,
|
||||
createdAt: new Date(registerAddress.createdAt),
|
||||
})
|
||||
})
|
||||
})
|
||||
it('valid, gradido transfer', () => {
|
||||
const communityUuid = uuidv4()
|
||||
const gradidoTransfer: TransactionInput = {
|
||||
user: {
|
||||
communityUuid,
|
||||
account: {
|
||||
userUuid: uuidv4(),
|
||||
}
|
||||
},
|
||||
linkedUser: {
|
||||
communityUuid,
|
||||
account: {
|
||||
userUuid: uuidv4(),
|
||||
}
|
||||
},
|
||||
amount: '100',
|
||||
memo: 'TestMemo',
|
||||
type: InputTransactionType.GRADIDO_TRANSFER,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
}
|
||||
expect(v.parse(transactionSchema, gradidoTransfer)).toEqual({
|
||||
user: {
|
||||
communityUuid,
|
||||
account: {
|
||||
userUuid: gradidoTransfer.user.account!.userUuid,
|
||||
accountNr: 1,
|
||||
}
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
linkedUser: {
|
||||
communityUuid,
|
||||
account: {
|
||||
userUuid: gradidoTransfer.linkedUser!.account!.userUuid,
|
||||
accountNr: 1,
|
||||
}
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
amount: GradidoUnit.fromString(gradidoTransfer.amount!),
|
||||
memo: gradidoTransfer.memo,
|
||||
amount: '100',
|
||||
memo: memoString,
|
||||
type: InputTransactionType.GRADIDO_TRANSFER,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
}
|
||||
expect(parse(transactionSchema, gradidoTransfer)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
amount: parse(gradidoAmountSchema, gradidoTransfer.amount!),
|
||||
memo,
|
||||
type: gradidoTransfer.type,
|
||||
createdAt: new Date(gradidoTransfer.createdAt),
|
||||
})
|
||||
})
|
||||
|
||||
it('valid, gradido creation', () => {
|
||||
const communityUuid = uuidv4()
|
||||
const gradidoCreation: TransactionInput = {
|
||||
user: {
|
||||
communityUuid,
|
||||
account: {
|
||||
userUuid: uuidv4(),
|
||||
}
|
||||
},
|
||||
linkedUser: {
|
||||
communityUuid,
|
||||
account: {
|
||||
userUuid: uuidv4(),
|
||||
}
|
||||
},
|
||||
amount: '1000',
|
||||
memo: 'For your help',
|
||||
type: InputTransactionType.GRADIDO_CREATION,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
targetDate: '2021-11-01T10:00'
|
||||
}
|
||||
expect(v.parse(transactionSchema, gradidoCreation)).toEqual({
|
||||
user: {
|
||||
communityUuid,
|
||||
account: {
|
||||
userUuid: gradidoCreation.user.account!.userUuid,
|
||||
accountNr: 1,
|
||||
}
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
linkedUser: {
|
||||
communityUuid,
|
||||
account: {
|
||||
userUuid: gradidoCreation.linkedUser!.account!.userUuid,
|
||||
accountNr: 1,
|
||||
}
|
||||
communityTopicId: topicString,
|
||||
account: { userUuid: userUuidString },
|
||||
},
|
||||
amount: GradidoUnit.fromString(gradidoCreation.amount!),
|
||||
memo: gradidoCreation.memo,
|
||||
amount: '1000',
|
||||
memo: memoString,
|
||||
type: InputTransactionType.GRADIDO_CREATION,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
targetDate: '2021-11-01T10:00',
|
||||
}
|
||||
expect(parse(transactionSchema, gradidoCreation)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: { userUuid, accountNr: 0 },
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topic,
|
||||
account: { userUuid, accountNr: 0 },
|
||||
},
|
||||
amount: parse(gradidoAmountSchema, gradidoCreation.amount!),
|
||||
memo,
|
||||
type: gradidoCreation.type,
|
||||
createdAt: new Date(gradidoCreation.createdAt),
|
||||
targetDate: new Date(gradidoCreation.targetDate!),
|
||||
@ -164,44 +185,44 @@ describe('transaction schemas', () => {
|
||||
})
|
||||
it('valid, gradido transaction link / deferred transfer', () => {
|
||||
const gradidoTransactionLink: TransactionInput = {
|
||||
user: {
|
||||
communityUuid: uuidv4(),
|
||||
account: {
|
||||
userUuid: uuidv4(),
|
||||
}
|
||||
},
|
||||
linkedUser: {
|
||||
communityUuid: uuidv4(),
|
||||
seed: {
|
||||
seed: transactionLinkCode(new Date()),
|
||||
}
|
||||
},
|
||||
amount: '100',
|
||||
memo: 'use link wisely',
|
||||
type: InputTransactionType.GRADIDO_DEFERRED_TRANSFER,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
timeoutDuration: 60*60*24*30,
|
||||
}
|
||||
expect(v.parse(transactionSchema, gradidoTransactionLink)).toEqual({
|
||||
user: {
|
||||
communityUuid: gradidoTransactionLink.user.communityUuid,
|
||||
communityTopicId: topicString,
|
||||
account: {
|
||||
userUuid: gradidoTransactionLink.user.account!.userUuid,
|
||||
accountNr: 1,
|
||||
}
|
||||
userUuid: userUuidString,
|
||||
},
|
||||
},
|
||||
linkedUser: {
|
||||
communityUuid: gradidoTransactionLink.linkedUser!.communityUuid,
|
||||
communityTopicId: topicString,
|
||||
seed: {
|
||||
seed: transactionLinkCode(new Date()),
|
||||
},
|
||||
},
|
||||
amount: '100',
|
||||
memo: memoString,
|
||||
type: InputTransactionType.GRADIDO_DEFERRED_TRANSFER,
|
||||
createdAt: '2022-01-01T00:00:00.000Z',
|
||||
timeoutDuration: 60 * 60 * 24 * 30,
|
||||
}
|
||||
expect(parse(transactionSchema, gradidoTransactionLink)).toEqual({
|
||||
user: {
|
||||
communityTopicId: topic,
|
||||
account: {
|
||||
userUuid,
|
||||
accountNr: 0,
|
||||
},
|
||||
},
|
||||
linkedUser: {
|
||||
communityTopicId: topic,
|
||||
seed: {
|
||||
seed: gradidoTransactionLink.linkedUser!.seed!.seed,
|
||||
}
|
||||
},
|
||||
},
|
||||
amount: GradidoUnit.fromString(gradidoTransactionLink.amount!),
|
||||
memo: gradidoTransactionLink.memo,
|
||||
amount: parse(gradidoAmountSchema, gradidoTransactionLink.amount!),
|
||||
memo,
|
||||
type: gradidoTransactionLink.type,
|
||||
createdAt: new Date(gradidoTransactionLink.createdAt),
|
||||
timeoutDuration: new DurationSeconds(gradidoTransactionLink.timeoutDuration!),
|
||||
timeoutDuration: parse(timeoutDurationSchema, gradidoTransactionLink.timeoutDuration!),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,43 +1,66 @@
|
||||
import * as v from 'valibot'
|
||||
import { dateFromStringSchema } from './typeConverter.schema'
|
||||
import { identifierAccountSchema } from './account.schema'
|
||||
import { InputTransactionType } from '../enum/InputTransactionType'
|
||||
import { accountTypeToAddressTypeSchema } from './typeConverter.schema'
|
||||
|
||||
import {
|
||||
identifierAccountSchema,
|
||||
identifierCommunityAccountSchema,
|
||||
identifierSeedSchema,
|
||||
} from './account.schema'
|
||||
import { accountTypeSchema, addressTypeSchema, dateSchema } from './typeConverter.schema'
|
||||
import {
|
||||
gradidoAmountSchema,
|
||||
hieroIdSchema,
|
||||
memoSchema,
|
||||
timeoutDurationSchema,
|
||||
} from './typeGuard.schema'
|
||||
|
||||
export const transactionSchema = v.object({
|
||||
user: identifierAccountScmhema,
|
||||
user: identifierAccountSchema,
|
||||
linkedUser: v.nullish(identifierAccountSchema, undefined),
|
||||
amount: v.nullish(amountToGradidoUnitSchema, undefined),
|
||||
amount: v.nullish(gradidoAmountSchema, undefined),
|
||||
memo: v.nullish(memoSchema, undefined),
|
||||
type: v.enum(InputTransactionType),
|
||||
createdAt: dateFromStringSchema,
|
||||
targetDate: v.nullish(dateFromStringSchema, undefined),
|
||||
createdAt: dateSchema,
|
||||
targetDate: v.nullish(dateSchema, undefined),
|
||||
timeoutDuration: v.nullish(timeoutDurationSchema, undefined),
|
||||
accountType: v.nullish(accountTypeToAddressTypeSchema, undefined),
|
||||
accountType: v.nullish(accountTypeSchema, undefined),
|
||||
})
|
||||
|
||||
export type TransactionInput = v.InferInput<typeof transactionSchema>
|
||||
export type Transaction = v.InferOutput<typeof transactionSchema>
|
||||
|
||||
// if the account is identified by seed
|
||||
export const seedAccountSchema = v.object({
|
||||
communityTopicId: hieroIdSchema,
|
||||
seed: identifierSeedSchema,
|
||||
})
|
||||
|
||||
// if the account is identified by userUuid and accountNr
|
||||
export const userAccountSchema = v.object({
|
||||
communityTopicId: hieroIdSchema,
|
||||
account: identifierCommunityAccountSchema,
|
||||
})
|
||||
|
||||
export type UserAccountInput = v.InferInput<typeof userAccountSchema>
|
||||
export type UserAccount = v.InferOutput<typeof userAccountSchema>
|
||||
|
||||
export const creationTransactionSchema = v.object({
|
||||
user: identifierAccountSchema,
|
||||
linkedUser: identifierAccountSchema,
|
||||
amount: amountToGradidoUnitSchema,
|
||||
user: userAccountSchema,
|
||||
linkedUser: userAccountSchema,
|
||||
amount: gradidoAmountSchema,
|
||||
memo: memoSchema,
|
||||
createdAt: dateFromStringSchema,
|
||||
targetDate: dateFromStringSchema,
|
||||
createdAt: dateSchema,
|
||||
targetDate: dateSchema,
|
||||
})
|
||||
|
||||
export type CreationTransactionInput = v.InferInput<typeof creationTransactionSchema>
|
||||
export type CreationTransaction = v.InferOutput<typeof creationTransactionSchema>
|
||||
|
||||
export const transferTransactionSchema = v.object({
|
||||
user: identifierAccountSchema,
|
||||
linkedUser: identifierAccountSchema,
|
||||
amount: amountToGradidoUnitSchema,
|
||||
user: userAccountSchema,
|
||||
linkedUser: userAccountSchema,
|
||||
amount: gradidoAmountSchema,
|
||||
memo: memoSchema,
|
||||
createdAt: dateFromStringSchema,
|
||||
createdAt: dateSchema,
|
||||
})
|
||||
|
||||
export type TransferTransactionInput = v.InferInput<typeof transferTransactionSchema>
|
||||
@ -45,23 +68,40 @@ export type TransferTransaction = v.InferOutput<typeof transferTransactionSchema
|
||||
|
||||
// linked user is later needed for move account transaction
|
||||
export const registerAddressTransactionSchema = v.object({
|
||||
user: identifierAccountSchema,
|
||||
createdAt: dateFromStringSchema,
|
||||
accountType: accountTypeToAddressTypeSchema,
|
||||
user: userAccountSchema,
|
||||
createdAt: dateSchema,
|
||||
accountType: addressTypeSchema,
|
||||
})
|
||||
|
||||
export type RegisterAddressTransactionInput = v.InferInput<typeof registerAddressTransactionSchema>
|
||||
export type RegisterAddressTransaction = v.InferOutput<typeof registerAddressTransactionSchema>
|
||||
|
||||
|
||||
// deferred transfer transaction: from user account to seed
|
||||
export const deferredTransferTransactionSchema = v.object({
|
||||
user: identifierAccountSchema,
|
||||
linkedUser: identifierAccountSchema,
|
||||
amount: amountToGradidoUnitSchema,
|
||||
user: userAccountSchema,
|
||||
linkedUser: seedAccountSchema,
|
||||
amount: gradidoAmountSchema,
|
||||
memo: memoSchema,
|
||||
createdAt: dateFromStringSchema,
|
||||
createdAt: dateSchema,
|
||||
timeoutDuration: timeoutDurationSchema,
|
||||
})
|
||||
|
||||
export type DeferredTransferTransactionInput = v.InferInput<typeof deferredTransferTransactionSchema>
|
||||
export type DeferredTransferTransactionInput = v.InferInput<
|
||||
typeof deferredTransferTransactionSchema
|
||||
>
|
||||
export type DeferredTransferTransaction = v.InferOutput<typeof deferredTransferTransactionSchema>
|
||||
|
||||
// redeem deferred transaction: from seed to user account
|
||||
export const redeemDeferredTransferTransactionSchema = v.object({
|
||||
user: seedAccountSchema,
|
||||
linkedUser: userAccountSchema,
|
||||
amount: gradidoAmountSchema,
|
||||
createdAt: dateSchema,
|
||||
})
|
||||
|
||||
export type RedeemDeferredTransferTransactionInput = v.InferInput<
|
||||
typeof redeemDeferredTransferTransactionSchema
|
||||
>
|
||||
export type RedeemDeferredTransferTransaction = v.InferOutput<
|
||||
typeof redeemDeferredTransferTransactionSchema
|
||||
>
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
|
||||
import { accountTypeSchema, addressTypeSchema, confirmedTransactionSchema } from './typeConverter.schema'
|
||||
import * as v from 'valibot'
|
||||
// only for IDE, bun don't need this to work
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import { dateSchema } from './typeConverter.schema'
|
||||
import { AddressType_COMMUNITY_AUF, AddressType_COMMUNITY_PROJECT } from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { AccountType } from '../enum/AccountType'
|
||||
import {
|
||||
accountTypeSchema,
|
||||
addressTypeSchema,
|
||||
confirmedTransactionSchema,
|
||||
dateSchema,
|
||||
} from './typeConverter.schema'
|
||||
|
||||
describe('basic.schema', () => {
|
||||
|
||||
describe('date', () => {
|
||||
it('from string', () => {
|
||||
const date = v.parse(dateSchema, '2021-01-01:10:10')
|
||||
@ -45,9 +47,12 @@ describe('basic.schema', () => {
|
||||
expect(accountType).toBe(AccountType.COMMUNITY_AUF)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
it('confirmedTransactionSchema', () => {
|
||||
const confirmedTransaction = v.parse(confirmedTransactionSchema, 'CAcSAgoAGgYIwvK5/wUiAzMuNCogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
|
||||
const confirmedTransaction = v.parse(
|
||||
confirmedTransactionSchema,
|
||||
'CAcSAgoAGgYIwvK5/wUiAzMuNCogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
||||
)
|
||||
expect(confirmedTransaction.getId()).toBe(7)
|
||||
expect(confirmedTransaction.getConfirmedAt().getSeconds()).toBe(1609464130)
|
||||
expect(confirmedTransaction.getVersionNumber()).toBe('3.4')
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
import {
|
||||
AddressType as AddressType,
|
||||
ConfirmedTransaction,
|
||||
} from 'gradido-blockchain-js'
|
||||
import { AccountType } from '../enum/AccountType'
|
||||
import { AddressType, ConfirmedTransaction } from 'gradido-blockchain-js'
|
||||
import * as v from 'valibot'
|
||||
import { confirmedTransactionFromBase64, isAddressType, toAddressType, toAccountType } from '../utils/typeConverter'
|
||||
import { AccountType } from '../enum/AccountType'
|
||||
import {
|
||||
confirmedTransactionFromBase64,
|
||||
isAddressType,
|
||||
toAccountType,
|
||||
toAddressType,
|
||||
} from '../utils/typeConverter'
|
||||
|
||||
/**
|
||||
* dateSchema for creating a date from string or Date object
|
||||
*/
|
||||
export const dateSchema = v.pipe(
|
||||
v.union([
|
||||
v.string('expect valid date string'),
|
||||
v.instance(Date, 'expect Date object')
|
||||
]),
|
||||
v.union([v.string('expect valid date string'), v.instance(Date, 'expect Date object')]),
|
||||
v.transform<string | Date, Date>((input) => {
|
||||
let date: Date
|
||||
let date: Date
|
||||
if (input instanceof Date) {
|
||||
date = input
|
||||
} else {
|
||||
@ -25,7 +24,7 @@ export const dateSchema = v.pipe(
|
||||
throw new Error('invalid date')
|
||||
}
|
||||
return date
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
/**
|
||||
@ -39,7 +38,7 @@ export const addressTypeSchema = v.pipe(
|
||||
v.enum(AccountType, 'expect account type'),
|
||||
v.custom<AddressType>(isAddressType, 'expect AddressType'),
|
||||
]),
|
||||
v.transform<AccountType | AddressType, AddressType>((value) => toAddressType(value)),
|
||||
v.transform<AccountType | AddressType, AddressType>((value) => toAddressType(value)),
|
||||
)
|
||||
|
||||
/**
|
||||
@ -58,7 +57,7 @@ export const confirmedTransactionSchema = v.pipe(
|
||||
v.instance(ConfirmedTransaction, 'expect ConfirmedTransaction'),
|
||||
v.pipe(
|
||||
v.string('expect confirmed Transaction base64 as string type'),
|
||||
v.base64('expect to be valid base64')
|
||||
v.base64('expect to be valid base64'),
|
||||
),
|
||||
]),
|
||||
v.transform<string | ConfirmedTransaction, ConfirmedTransaction>(
|
||||
@ -70,5 +69,3 @@ export const confirmedTransactionSchema = v.pipe(
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { describe, it, expect } from 'bun:test'
|
||||
import { uuidv4Schema, memoSchema } from './typeGuard.schema'
|
||||
import * as v from 'valibot'
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import * as v from 'valibot'
|
||||
import { memoSchema, uuidv4Schema } from './typeGuard.schema'
|
||||
|
||||
describe('typeGuard.schema', () => {
|
||||
describe('Uuidv4', () => {
|
||||
@ -14,7 +14,7 @@ describe('typeGuard.schema', () => {
|
||||
})
|
||||
describe('Basic Type Schemas for transactions', () => {
|
||||
describe('Memo', () => {
|
||||
it('min length', () => {
|
||||
it('min length', () => {
|
||||
const memoValue = 'memo1'
|
||||
const memoValueParsed = v.parse(memoSchema, memoValue)
|
||||
expect(memoValueParsed.toString()).toBe(memoValue)
|
||||
@ -30,8 +30,10 @@ describe('typeGuard.schema', () => {
|
||||
})
|
||||
it('to long', () => {
|
||||
const memoValue = 's'.repeat(256)
|
||||
expect(() => v.parse(memoSchema, memoValue)).toThrow(new Error('expect string length <= 255'))
|
||||
expect(() => v.parse(memoSchema, memoValue)).toThrow(
|
||||
new Error('expect string length <= 255'),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
/**
|
||||
* # TypeGuards
|
||||
* # TypeGuards
|
||||
* Expand TypeScript Default Types with custom type which a based on a default type (or class)
|
||||
* Use valibot, so we can describe the type and validate it easy at runtime
|
||||
* After transpiling TypeScript unique symbol are gone
|
||||
* Infos at opaque type in typescript: https://evertpot.com/opaque-ts-types/
|
||||
*
|
||||
*
|
||||
* declare const validAmount: unique symbol
|
||||
* export type Amount = number & { [validAmount]: true };
|
||||
* Can be compared with using `typedef int Amount;` in C/C++
|
||||
* Example:
|
||||
* Example:
|
||||
* To create a instance of Amount:
|
||||
* `const amount: Amount = v.parse(amountSchema, 1.21)`
|
||||
* `const amount: Amount = v.parse(amountSchema, 1.21)`
|
||||
* must be called and ensure the value is valid
|
||||
* If it isn't valid, v.parse will throw an error
|
||||
* Alternatively v.safeParse can be used, this don't throw but it return null on error
|
||||
*/
|
||||
|
||||
import { DurationSeconds, GradidoUnit, MemoryBlock, MemoryBlockPtr } from 'gradido-blockchain-js'
|
||||
import { validate, version } from 'uuid'
|
||||
import * as v from 'valibot'
|
||||
import { MemoryBlock, DurationSeconds, GradidoUnit } from 'gradido-blockchain-js'
|
||||
|
||||
/**
|
||||
* type guard for uuid v4
|
||||
@ -26,17 +26,15 @@ import { MemoryBlock, DurationSeconds, GradidoUnit } from 'gradido-blockchain-js
|
||||
* uuidv4 is used for communityUuid and userUuid
|
||||
*/
|
||||
declare const validUuidv4: unique symbol
|
||||
export type Uuidv4 = string & { [validUuidv4]: true };
|
||||
export type Uuidv4 = string & { [validUuidv4]: true }
|
||||
|
||||
export const uuidv4Schema = v.pipe(
|
||||
v.string('expect string type'),
|
||||
v.custom<string>((value) =>
|
||||
(typeof value === 'string' && validate(value) && version(value) === 4),
|
||||
'uuid v4 expected'
|
||||
),
|
||||
v.transform<string, Uuidv4>(
|
||||
(input: string) => input as Uuidv4,
|
||||
v.custom<string>(
|
||||
(value) => typeof value === 'string' && validate(value) && version(value) === 4,
|
||||
'uuid v4 expected',
|
||||
),
|
||||
v.transform<string, Uuidv4>((input: string) => input as Uuidv4),
|
||||
)
|
||||
|
||||
export type Uuidv4Input = v.InferInput<typeof uuidv4Schema>
|
||||
@ -48,16 +46,30 @@ export type Uuidv4Input = v.InferInput<typeof uuidv4Schema>
|
||||
*/
|
||||
|
||||
declare const validMemoryBlock32: unique symbol
|
||||
export type MemoryBlock32 = MemoryBlock & { [validMemoryBlock32]: true };
|
||||
export type MemoryBlock32 = MemoryBlockPtr & { [validMemoryBlock32]: true }
|
||||
|
||||
export const memoryBlock32Schema = v.pipe(
|
||||
v.instance(MemoryBlock, 'expect MemoryBlock type'),
|
||||
v.custom<MemoryBlock>(
|
||||
(val): boolean => val instanceof MemoryBlock && val.size() === 32 && !val.isEmpty(),
|
||||
'expect MemoryBlock size = 32 and not empty'
|
||||
),
|
||||
v.transform<MemoryBlock, MemoryBlock32>(
|
||||
(input: MemoryBlock) => input as MemoryBlock32,
|
||||
v.union([
|
||||
v.instance(MemoryBlock, 'expect MemoryBlock type'),
|
||||
v.instance(MemoryBlockPtr, 'expect MemoryBlockPtr type'),
|
||||
]),
|
||||
v.custom<MemoryBlock | MemoryBlockPtr>((val): boolean => {
|
||||
if (val instanceof MemoryBlockPtr) {
|
||||
return val.size() === 32 && !val.isEmpty()
|
||||
}
|
||||
if (val instanceof MemoryBlock) {
|
||||
return val.size() === 32 && !val.isEmpty()
|
||||
}
|
||||
return false
|
||||
}, 'expect MemoryBlock size = 32 and not empty'),
|
||||
v.transform<MemoryBlock | MemoryBlockPtr, MemoryBlock32>(
|
||||
(input: MemoryBlock | MemoryBlockPtr) => {
|
||||
let memoryBlock: MemoryBlockPtr = input as MemoryBlockPtr
|
||||
if (input instanceof MemoryBlock) {
|
||||
memoryBlock = MemoryBlock.createPtr(input)
|
||||
}
|
||||
return memoryBlock as MemoryBlock32
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@ -68,7 +80,7 @@ export const memoryBlock32Schema = v.pipe(
|
||||
* hex32 is a hex string of length 64 (binary size = 32)
|
||||
*/
|
||||
declare const validHex32: unique symbol
|
||||
export type Hex32 = string & { [validHex32]: true };
|
||||
export type Hex32 = string & { [validHex32]: true }
|
||||
|
||||
export const hex32Schema = v.pipe(
|
||||
v.union([
|
||||
@ -79,40 +91,50 @@ export const hex32Schema = v.pipe(
|
||||
),
|
||||
memoryBlock32Schema,
|
||||
]),
|
||||
v.transform<string | MemoryBlock32 | Hex32, Hex32>(
|
||||
(input: string | MemoryBlock32 | Hex32) => {
|
||||
if (typeof input === 'string') {
|
||||
return input as Hex32
|
||||
}
|
||||
return input.convertToHex() as Hex32
|
||||
},
|
||||
),
|
||||
v.transform<string | MemoryBlock32 | Hex32, Hex32>((input: string | MemoryBlock32 | Hex32) => {
|
||||
if (typeof input === 'string') {
|
||||
return input as Hex32
|
||||
}
|
||||
return input.convertToHex() as Hex32
|
||||
}),
|
||||
)
|
||||
|
||||
export type Hex32Input = v.InferInput<typeof hex32Schema>
|
||||
|
||||
/**
|
||||
* type guard for iota message id
|
||||
* create with `v.parse(iotaMessageIdSchema, '822387692a7cfd3f07f25742e91e248af281d771ee03a432c2e178e5533f786c')`
|
||||
* iota message id is a hex string of length 64
|
||||
* type guard for hiero id
|
||||
* create with `v.parse(hieroIdSchema, '0.0.2')`
|
||||
* hiero id is a hiero id of the form 0.0.2
|
||||
*/
|
||||
declare const validIotaMessageId: unique symbol
|
||||
export type IotaMessageId = Hex32 & { [validIotaMessageId]: true };
|
||||
declare const validHieroId: unique symbol
|
||||
export type HieroId = string & { [validHieroId]: true }
|
||||
|
||||
export const iotaMessageIdSchema = v.pipe(
|
||||
v.union([
|
||||
v.pipe(
|
||||
v.string('expect string type'),
|
||||
v.hexadecimal('expect hexadecimal string'),
|
||||
v.length(64, 'expect string length = 64'),
|
||||
),
|
||||
memoryBlock32Schema,
|
||||
]),
|
||||
v.transform<string | MemoryBlock32, IotaMessageId>(
|
||||
(input: string | MemoryBlock32) => v.parse(hex32Schema, input) as IotaMessageId,
|
||||
),
|
||||
export const hieroIdSchema = v.pipe(
|
||||
v.string('expect hiero id type, three 64 Bit Numbers separated by . for example 0.0.2'),
|
||||
v.regex(/^[0-9]+\.[0-9]+\.[0-9]+$/),
|
||||
v.transform<string, HieroId>((input: string) => input as HieroId),
|
||||
)
|
||||
|
||||
export type HieroIdInput = v.InferInput<typeof hieroIdSchema>
|
||||
|
||||
/**
|
||||
* type guard for hiero transaction id
|
||||
* create with `v.parse(hieroTransactionIdSchema, '0.0.141760-1755138896-607329203')`
|
||||
* hiero transaction id is a hiero transaction id of the form 0.0.141760-1755138896-607329203
|
||||
* basically it is a Hiero id with a timestamp seconds-nanoseconds since 1970-01-01T00:00:00Z
|
||||
* seconds is int64, nanoseconds int32
|
||||
*/
|
||||
declare const validHieroTransactionId: unique symbol
|
||||
export type HieroTransactionId = string & { [validHieroTransactionId]: true }
|
||||
|
||||
export const hieroTransactionIdSchema = v.pipe(
|
||||
v.string('expect hiero transaction id type, for example 0.0.141760-1755138896-607329203'),
|
||||
v.regex(/^[0-9]+\.[0-9]+\.[0-9]+-[0-9]+-[0-9]+$/),
|
||||
v.transform<string, HieroTransactionId>((input: string) => input as HieroTransactionId),
|
||||
)
|
||||
|
||||
export type HieroTransactionIdInput = v.InferInput<typeof hieroTransactionIdSchema>
|
||||
|
||||
/**
|
||||
* type guard for memo
|
||||
* create with `v.parse(memoSchema, 'memo')`
|
||||
@ -122,38 +144,36 @@ export const MEMO_MIN_CHARS = 5
|
||||
export const MEMO_MAX_CHARS = 255
|
||||
|
||||
declare const validMemo: unique symbol
|
||||
export type Memo = string & { [validMemo]: true };
|
||||
export type Memo = string & { [validMemo]: true }
|
||||
|
||||
export const memoSchema = v.pipe(
|
||||
v.string('expect string type'),
|
||||
v.string('expect string type'),
|
||||
v.maxLength(MEMO_MAX_CHARS, `expect string length <= ${MEMO_MAX_CHARS}`),
|
||||
v.minLength(MEMO_MIN_CHARS, `expect string length >= ${MEMO_MIN_CHARS}`),
|
||||
v.transform<string, Memo>(
|
||||
(input: string) => input as Memo,
|
||||
),
|
||||
v.transform<string, Memo>((input: string) => input as Memo),
|
||||
)
|
||||
|
||||
/**
|
||||
* type guard for timeout duration
|
||||
* create with `v.parse(timeoutDurationSchema, 123)`
|
||||
* timeout duration is a number in seconds inside bounds
|
||||
* timeout duration is a number in seconds inside bounds
|
||||
* [1 hour, 3 months]
|
||||
* for Transaction Links / Deferred Transactions
|
||||
* seconds starting from createdAt Date in which the transaction link can be redeemed
|
||||
*/
|
||||
const LINKED_TRANSACTION_TIMEOUT_DURATION_MIN = 60*60
|
||||
const LINKED_TRANSACTION_TIMEOUT_DURATION_MAX = 60*60*24*31*3
|
||||
const LINKED_TRANSACTION_TIMEOUT_DURATION_MIN = 60 * 60
|
||||
const LINKED_TRANSACTION_TIMEOUT_DURATION_MAX = 60 * 60 * 24 * 31 * 3
|
||||
|
||||
declare const validTimeoutDuration: unique symbol
|
||||
export type TimeoutDuration = DurationSeconds & { [validTimeoutDuration]: true };
|
||||
export type TimeoutDuration = DurationSeconds & { [validTimeoutDuration]: true }
|
||||
|
||||
export const timeoutDurationSchema = v.pipe(
|
||||
v.number('expect number type'),
|
||||
v.minValue(LINKED_TRANSACTION_TIMEOUT_DURATION_MIN, 'expect number >= 1 hour'),
|
||||
v.maxValue(LINKED_TRANSACTION_TIMEOUT_DURATION_MAX, 'expect number <= 3 months'),
|
||||
v.transform<number, TimeoutDuration>(
|
||||
(input: number) => new DurationSeconds(input) as TimeoutDuration,
|
||||
),
|
||||
v.number('expect number type'),
|
||||
v.minValue(LINKED_TRANSACTION_TIMEOUT_DURATION_MIN, 'expect number >= 1 hour'),
|
||||
v.maxValue(LINKED_TRANSACTION_TIMEOUT_DURATION_MAX, 'expect number <= 3 months'),
|
||||
v.transform<number, TimeoutDuration>(
|
||||
(input: number) => new DurationSeconds(input) as TimeoutDuration,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
@ -162,14 +182,12 @@ export const timeoutDurationSchema = v.pipe(
|
||||
* amount is a string representing a positive decimal number, compatible with decimal.js
|
||||
*/
|
||||
declare const validAmount: unique symbol
|
||||
export type Amount = string & { [validAmount]: true };
|
||||
export type Amount = string & { [validAmount]: true }
|
||||
|
||||
export const amountSchema = v.pipe(
|
||||
v.string('expect string type'),
|
||||
v.string('expect string type'),
|
||||
v.regex(/^[0-9]+(\.[0-9]+)?$/, 'expect positive number'),
|
||||
v.transform<string, Amount>(
|
||||
(input: string) => input as Amount,
|
||||
),
|
||||
v.transform<string, Amount>((input: string) => input as Amount),
|
||||
)
|
||||
|
||||
/**
|
||||
@ -179,11 +197,11 @@ export const amountSchema = v.pipe(
|
||||
* GradidoUnit is native implemented in gradido-blockchain-js in c++ and has functions for decay calculation
|
||||
*/
|
||||
declare const validGradidoAmount: unique symbol
|
||||
export type GradidoAmount = GradidoUnit & { [validGradidoAmount]: true };
|
||||
export type GradidoAmount = GradidoUnit & { [validGradidoAmount]: true }
|
||||
|
||||
export const gradidoAmountSchema = v.pipe(
|
||||
amountSchema,
|
||||
v.transform<Amount, GradidoAmount>(
|
||||
(input: Amount) => GradidoUnit.fromString(input) as GradidoAmount,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@ -1,77 +1,75 @@
|
||||
import { initTRPC, TRPCError } from '@trpc/server'
|
||||
import * as v from 'valibot'
|
||||
import { identifierAccountSchema } from '../schemas/account.schema'
|
||||
import { KeyPairIdentifierLogic } from '../data/KeyPairIdentifier.logic'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../config/const'
|
||||
import { KeyPairCalculation } from '../interactions/keyPairCalculation/KeyPairCalculation.context'
|
||||
import { getAddressType } from '../client/GradidoNode/api'
|
||||
import { Uuidv4Hash } from '../data/Uuidv4Hash'
|
||||
import { hex32Schema } from '../schemas/typeGuard.schema'
|
||||
import { Elysia, status } from 'elysia'
|
||||
import { AddressType_NONE } from 'gradido-blockchain-js'
|
||||
import { getLogger } from 'log4js'
|
||||
import * as v from 'valibot'
|
||||
import { getAddressType } from '../client/GradidoNode/api'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../config/const'
|
||||
import { KeyPairIdentifierLogic } from '../data/KeyPairIdentifier.logic'
|
||||
import { KeyPairCalculation } from '../interactions/keyPairCalculation/KeyPairCalculation.context'
|
||||
import { SendToIotaContext } from '../interactions/sendToIota/SendToIota.context'
|
||||
import { IdentifierAccount, identifierAccountSchema } from '../schemas/account.schema'
|
||||
import {
|
||||
accountIdentifierSeedSchema,
|
||||
accountIdentifierUserSchema,
|
||||
existSchema,
|
||||
} from './input.schema'
|
||||
|
||||
export const t = initTRPC.create()
|
||||
const publicProcedure = t.procedure
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.server`)
|
||||
|
||||
export const appRouter = t.router({
|
||||
isAccountExist: publicProcedure
|
||||
.input(identifierAccountSchema)
|
||||
.output(v.boolean())
|
||||
.query(async ({ input: userIdentifier }) => {
|
||||
const accountKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(userIdentifier))
|
||||
const publicKey = accountKeyPair.getPublicKey()
|
||||
if (!publicKey) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: "couldn't calculate account key pair",
|
||||
})
|
||||
}
|
||||
|
||||
// ask gradido node server for account type, if type !== NONE account exist
|
||||
const addressType = await getAddressType(
|
||||
v.parse(hex32Schema, publicKey.get()),
|
||||
new Uuidv4Hash(userIdentifier.communityUuid),
|
||||
)
|
||||
logger.info('isAccountExist')
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug('params', userIdentifier)
|
||||
}
|
||||
return addressType !== AddressType_NONE
|
||||
}),
|
||||
|
||||
sendTransaction: publicProcedure
|
||||
.input(transactionDraftSchema)
|
||||
.output(v.instanceof(TransactionResult))
|
||||
.mutation(async ({ input: transactionDraft }) => {
|
||||
try {
|
||||
return await SendToIotaContext(transactionDraft)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
if (error instanceof TransactionError) {
|
||||
return new TransactionResult(error)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
|
||||
async sendTransaction(
|
||||
@Arg('data')
|
||||
transactionDraft: TransactionDraft,
|
||||
): Promise<TransactionResult> {
|
||||
try {
|
||||
return await SendToIotaContext(transactionDraft)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
if (error instanceof TransactionError) {
|
||||
return new TransactionResult(error)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
async function isAccountExist(identifierAccount: IdentifierAccount): Promise<boolean> {
|
||||
const startTime = Date.now()
|
||||
const accountKeyPair = await KeyPairCalculation(new KeyPairIdentifierLogic(identifierAccount))
|
||||
const publicKey = accountKeyPair.getPublicKey()
|
||||
if (!publicKey) {
|
||||
throw status(404, "couldn't calculate account key pair")
|
||||
}
|
||||
*/
|
||||
|
||||
// ask gradido node server for account type, if type !== NONE account exist
|
||||
const addressType = await getAddressType(
|
||||
publicKey.convertToHex(),
|
||||
identifierAccount.communityTopicId,
|
||||
)
|
||||
const endTime = Date.now()
|
||||
logger.info(
|
||||
`isAccountExist: ${addressType !== AddressType_NONE}, time used: ${endTime - startTime}ms`,
|
||||
)
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug('params', identifierAccount)
|
||||
}
|
||||
return addressType !== AddressType_NONE
|
||||
}
|
||||
|
||||
export const appRoutes = new Elysia()
|
||||
.get(
|
||||
'/isAccountExist/:communityTopicId/:userUuid/:accountNr',
|
||||
async ({ params: { communityTopicId, userUuid, accountNr } }) => {
|
||||
const accountIdentifier = v.parse(identifierAccountSchema, {
|
||||
communityTopicId,
|
||||
account: { userUuid, accountNr },
|
||||
})
|
||||
return { exists: await isAccountExist(accountIdentifier) }
|
||||
},
|
||||
// validation schemas
|
||||
{ params: accountIdentifierUserSchema, response: existSchema },
|
||||
)
|
||||
.get(
|
||||
'/isAccountExist/:communityTopicId/:seed',
|
||||
async ({ params: { communityTopicId, seed } }) => {
|
||||
const accountIdentifier = v.parse(identifierAccountSchema, {
|
||||
communityTopicId,
|
||||
seed: { seed },
|
||||
})
|
||||
return { exists: await isAccountExist(accountIdentifier) }
|
||||
},
|
||||
// validation schemas
|
||||
{ params: accountIdentifierSeedSchema, response: existSchema },
|
||||
)
|
||||
.post(
|
||||
'/sendTransaction',
|
||||
async ({ body }) => {
|
||||
const transactionDraft = v.parse(transactionDraftSchema, body)
|
||||
return await SendToIotaContext(transactionDraft)
|
||||
},
|
||||
// validation schemas
|
||||
{ body: transactionDraftSchema, response: v.instanceof(TransactionResult) },
|
||||
)
|
||||
|
||||
19
dlt-connector/src/server/input.schema.ts
Normal file
19
dlt-connector/src/server/input.schema.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { TypeBoxFromValibot } from '@sinclair/typemap'
|
||||
import { t } from 'elysia'
|
||||
import { hieroIdSchema, uuidv4Schema } from '../schemas/typeGuard.schema'
|
||||
|
||||
export const accountIdentifierUserSchema = t.Object({
|
||||
communityTopicId: TypeBoxFromValibot(hieroIdSchema),
|
||||
userUuid: TypeBoxFromValibot(uuidv4Schema),
|
||||
accountNr: t.Number().positive(),
|
||||
})
|
||||
|
||||
// identifier for a gradido account created by transaction link / deferred transfer
|
||||
export const accountIdentifierSeedSchema = t.Object({
|
||||
communityTopicId: TypeBoxFromValibot(hieroIdSchema),
|
||||
seed: TypeBoxFromValibot(uuidv4Schema),
|
||||
})
|
||||
|
||||
export const existSchema = t.Object({
|
||||
exists: t.Boolean(),
|
||||
})
|
||||
@ -1,6 +1,6 @@
|
||||
import { hardenDerivationIndex, HARDENED_KEY_BITMASK } from './derivationHelper'
|
||||
// only for IDE, bun don't need this to work
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import { HARDENED_KEY_BITMASK, hardenDerivationIndex } from './derivationHelper'
|
||||
|
||||
describe('utils', () => {
|
||||
it('test bitmask for hardened keys', () => {
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import net from 'node:net'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../config/const'
|
||||
import { CONFIG } from '../config'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../config/const'
|
||||
|
||||
export async function isPortOpen(
|
||||
url: string,
|
||||
url: string,
|
||||
timeoutMs: number = CONFIG.CONNECT_TIMEOUT_MS,
|
||||
): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const socket = new net.Socket()
|
||||
const { hostname, port } = new URL(url)
|
||||
|
||||
|
||||
// auto-destroy socket after timeout
|
||||
const timer = setTimeout(() => {
|
||||
socket.destroy()
|
||||
@ -35,17 +35,19 @@ export async function isPortOpen(
|
||||
})
|
||||
}
|
||||
|
||||
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
export async function isPortOpenRetry(
|
||||
url: string,
|
||||
url: string,
|
||||
timeoutMs: number = CONFIG.CONNECT_TIMEOUT_MS,
|
||||
delayMs: number = CONFIG.CONNECT_RETRY_DELAY_MS,
|
||||
retries: number = CONFIG.CONNECT_RETRY_COUNT,
|
||||
): Promise<boolean> {
|
||||
for (let i = 0; i < retries; i++) {
|
||||
if (await isPortOpen(url, timeoutMs)) return true
|
||||
if (await isPortOpen(url, timeoutMs)) {
|
||||
return true
|
||||
}
|
||||
await wait(delayMs)
|
||||
}
|
||||
throw new Error(`${url} port is not open after ${retries} retries`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import {
|
||||
ConfirmedTransaction,
|
||||
MemoryBlock,
|
||||
InteractionDeserialize,
|
||||
DeserializeType_CONFIRMED_TRANSACTION,
|
||||
import {
|
||||
AddressType,
|
||||
AddressType_COMMUNITY_AUF,
|
||||
AddressType_COMMUNITY_GMW,
|
||||
AddressType_COMMUNITY_HUMAN,
|
||||
AddressType_COMMUNITY_PROJECT,
|
||||
AddressType_CRYPTO_ACCOUNT,
|
||||
AddressType_SUBACCOUNT,
|
||||
AddressType_DEFERRED_TRANSFER,
|
||||
AddressType_NONE,
|
||||
AddressType,
|
||||
AddressType_SUBACCOUNT,
|
||||
ConfirmedTransaction,
|
||||
DeserializeType_CONFIRMED_TRANSACTION,
|
||||
InteractionDeserialize,
|
||||
MemoryBlock,
|
||||
} from 'gradido-blockchain-js'
|
||||
import { AccountType } from '../enum/AccountType'
|
||||
|
||||
@ -44,17 +44,22 @@ const accountToAddressMap: Record<AccountType, AddressType> = {
|
||||
[AccountType.NONE]: AddressType_NONE,
|
||||
}
|
||||
|
||||
const addressToAccountMap: Record<AddressType, AccountType> = Object.entries(accountToAddressMap).reduce((acc, [accKey, addrVal]) => {
|
||||
acc[addrVal] = String(accKey) as AccountType
|
||||
return acc;
|
||||
}, {} as Record<AddressType, AccountType>)
|
||||
const addressToAccountMap: Record<AddressType, AccountType> = Object.entries(
|
||||
accountToAddressMap,
|
||||
).reduce(
|
||||
(acc, [accKey, addrVal]) => {
|
||||
acc[addrVal] = String(accKey) as AccountType
|
||||
return acc
|
||||
},
|
||||
{} as Record<AddressType, AccountType>,
|
||||
)
|
||||
|
||||
export function isAddressType(val: unknown): val is AddressType {
|
||||
return typeof val === 'number' && Object.keys(addressToAccountMap).includes(val.toString())
|
||||
}
|
||||
|
||||
export function isAccountType(val: unknown): val is AccountType {
|
||||
return Object.values(AccountType).includes(val as AccountType);
|
||||
return Object.values(AccountType).includes(val as AccountType)
|
||||
}
|
||||
|
||||
export function toAddressType(input: AccountType | AddressType): AddressType {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user