finalize refactoring, change for using hiero instead of iota

This commit is contained in:
einhornimmond 2025-08-16 16:38:41 +02:00
parent 6e2269a499
commit eb29dde8c3
49 changed files with 2146 additions and 872 deletions

154
dlt-connector/biome.json Normal file
View 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

View File

@ -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",

View File

@ -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()

View File

@ -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),
)

View File

@ -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>

View File

@ -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
})
}
}

View 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
}
}

View File

@ -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(),
)

View File

@ -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'),
})
})
})

View File

@ -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
}
}
`

View File

@ -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,
}

View 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',
)

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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,
}
}

View File

@ -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.
}
}

View File

@ -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',
}

View File

@ -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) {

View File

@ -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}`)
})

View File

@ -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>
}

View File

@ -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()
}

View File

@ -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())

View File

@ -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")

View File

@ -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
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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)
}
})
})

View File

@ -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
}

View File

@ -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(),

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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>

View File

@ -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)),
)

View File

@ -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!),
})
})
})
})
})

View File

@ -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
>

View File

@ -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')

View File

@ -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(
},
),
)

View File

@ -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'),
)
})
})
})
})
})

View File

@ -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,
),
)
)

View File

@ -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) },
)

View 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(),
})

View File

@ -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', () => {

View File

@ -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`)
}
}

View File

@ -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 {