mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
introduce valibot, elysia.js, trpc and drop apollo/graphql-server
This commit is contained in:
parent
bdc26c524b
commit
6e2269a499
5
bun.lock
5
bun.lock
@ -184,12 +184,15 @@
|
||||
"dependencies": {
|
||||
"database": "*",
|
||||
"esbuild": "^0.25.2",
|
||||
"jose": "^4.14.4",
|
||||
"log4js": "^6.9.1",
|
||||
"shared": "*",
|
||||
"zod": "^3.25.61",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@types/node": "^17.0.21",
|
||||
"type-graphql": "^1.1.1",
|
||||
"typescript": "^4.9.5",
|
||||
},
|
||||
},
|
||||
@ -295,6 +298,7 @@
|
||||
"await-semaphore": "0.1.3",
|
||||
"class-validator": "^0.13.2",
|
||||
"config-schema": "*",
|
||||
"core": "*",
|
||||
"cors": "2.8.5",
|
||||
"database": "*",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
@ -423,6 +427,7 @@
|
||||
"dependencies": {
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"esbuild": "^0.25.2",
|
||||
"jose": "^4.14.4",
|
||||
"log4js": "^6.9.1",
|
||||
"zod": "^3.25.61",
|
||||
},
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"name": "dlt-connector",
|
||||
"dependencies": {
|
||||
"@iota/client": "^2.2.4",
|
||||
"gradido-blockchain-js": "git+https://github.com/gradido/gradido-blockchain-js#217d03b",
|
||||
"gradido-blockchain-js": "git+https://github.com/gradido/gradido-blockchain-js#9a5f392",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0",
|
||||
@ -232,7 +232,7 @@
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"gradido-blockchain-js": ["gradido-blockchain-js@github:gradido/gradido-blockchain-js#217d03b", { "dependencies": { "bindings": "^1.5.0", "nan": "^2.20.0", "node-addon-api": "^7.1.1", "node-gyp-build": "^4.8.1", "prebuildify": "git+https://github.com/einhornimmond/prebuildify#cmake_js" } }, "gradido-gradido-blockchain-js-217d03b"],
|
||||
"gradido-blockchain-js": ["gradido-blockchain-js@github:gradido/gradido-blockchain-js#9a5f392", { "dependencies": { "bindings": "^1.5.0", "nan": "^2.20.0", "node-addon-api": "^7.1.1", "node-gyp-build": "^4.8.1", "prebuildify": "git+https://github.com/einhornimmond/prebuildify#65d94455fab86b902c0d59bb9c06ac70470e56b2" } }, "gradido-gradido-blockchain-js-9a5f392"],
|
||||
|
||||
"graphql": ["graphql@16.11.0", "", {}, "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw=="],
|
||||
|
||||
@ -356,7 +356,7 @@
|
||||
|
||||
"prebuild-install": ["prebuild-install@6.1.4", "", { "dependencies": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", "node-abi": "^2.21.0", "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^3.0.3", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ=="],
|
||||
|
||||
"prebuildify": ["prebuildify@github:einhornimmond/prebuildify#91f4e76", { "dependencies": { "cmake-js": "^7.2.1", "execspawn": "^1.0.1", "minimist": "^1.2.5", "mkdirp-classic": "^0.5.3", "node-abi": "^3.3.0", "npm-run-path": "^3.1.0", "npm-which": "^3.0.1", "pump": "^3.0.0", "tar-fs": "^2.1.0" }, "bin": { "prebuildify": "./bin.js" } }, "einhornimmond-prebuildify-91f4e76"],
|
||||
"prebuildify": ["prebuildify@github:einhornimmond/prebuildify#65d9445", { "dependencies": { "cmake-js": "^7.2.1", "execspawn": "^1.0.1", "minimist": "^1.2.5", "mkdirp-classic": "^0.5.3", "node-abi": "^3.3.0", "npm-run-path": "^3.1.0", "npm-which": "^3.0.1", "pump": "^3.0.0", "tar-fs": "^2.1.0" }, "bin": { "prebuildify": "./bin.js" } }, "einhornimmond-prebuildify-65d9445"],
|
||||
|
||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
"lint:fix": "biome check --error-on-warnings . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"gradido-blockchain-js": "git+https://github.com/gradido/gradido-blockchain-js#217d03b",
|
||||
"gradido-blockchain-js": "git+https://github.com/gradido/gradido-blockchain-js#9a5f392",
|
||||
"@iota/client": "^2.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
167
dlt-connector/src/client/GradidoNode/api.ts
Normal file
167
dlt-connector/src/client/GradidoNode/api.ts
Normal file
@ -0,0 +1,167 @@
|
||||
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 { Uuidv4Hash } from '../../data/Uuidv4Hash'
|
||||
import { Hex32, Hex32Input, hex32Schema } from '../../schemas/typeGuard.schema'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`)
|
||||
|
||||
/**
|
||||
* getTransactions
|
||||
* 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
|
||||
* @returns list of confirmed transactions
|
||||
* @throws GradidoNodeRequestError
|
||||
* @example
|
||||
* ```
|
||||
* const transactions = await getTransactions({
|
||||
* fromTransactionId: 1,
|
||||
* maxResultCount: 100,
|
||||
* topic: communityUuid,
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
async function getTransactions(input: TransactionsRangeInput): Promise<ConfirmedTransaction[]> {
|
||||
const parameter = { ...v.parse(transactionsRangeSchema, input), format: 'base64' }
|
||||
const result = await rpcCallResolved<{transactions: string[]}>('getTransactions', parameter)
|
||||
return result.transactions.map((transactionBase64) =>
|
||||
v.parse(confirmedTransactionSchema, transactionBase64),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* getTransaction
|
||||
* get a specific confirmed transaction from a specific community
|
||||
* @param transactionIdentifier
|
||||
* @returns the confirmed transaction or undefined if transaction is not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
async function getTransaction(transactionIdentifier: TransactionIdentifierInput)
|
||||
: Promise<ConfirmedTransaction | undefined> {
|
||||
const parameter = {
|
||||
...v.parse(transactionIdentifierSchema, transactionIdentifier),
|
||||
format: 'base64',
|
||||
}
|
||||
const response = await rpcCall<{ transaction: string }>('gettransaction', parameter)
|
||||
if (response.isSuccess()) {
|
||||
return v.parse(confirmedTransactionSchema, response.result.transaction)
|
||||
}
|
||||
if (response.isError()) {
|
||||
if (response.error.code === GradidoNodeErrorCodes.TRANSACTION_NOT_FOUND) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
|
||||
/**
|
||||
* getLastTransaction
|
||||
* get the last confirmed transaction from a specific community
|
||||
* @param iotaTopic the community topic
|
||||
* @returns the last confirmed transaction or undefined if blockchain for community is empty or not found
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
|
||||
async function getLastTransaction(iotaTopic: Uuidv4Hash): Promise<ConfirmedTransaction | undefined> {
|
||||
const response = await rpcCall<{ transaction: string }>('getlasttransaction', {
|
||||
format: 'base64',
|
||||
topic: iotaTopic.getAsHexString(),
|
||||
})
|
||||
if (response.isSuccess()) {
|
||||
return v.parse(confirmedTransactionSchema, response.result.transaction)
|
||||
}
|
||||
if (response.isError()) {
|
||||
if (response.error.code === GradidoNodeErrorCodes.GRADIDO_NODE_ERROR) {
|
||||
return undefined
|
||||
}
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getAddressType
|
||||
* 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
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
|
||||
async function getAddressType(pubkey: Hex32Input, iotaTopic: Uuidv4Hash): Promise<AddressType> {
|
||||
const parameter = {
|
||||
pubkey: v.parse(hex32Schema, pubkey),
|
||||
communityId: iotaTopic.getAsHexString(),
|
||||
}
|
||||
const response = await rpcCallResolved<{ addressType: string }>('getaddresstype', parameter)
|
||||
return v.parse(addressTypeSchema, response.addressType)
|
||||
}
|
||||
|
||||
/**
|
||||
* findUserByNameHash
|
||||
* find a user by name hash
|
||||
* @param nameHash the name hash of the user
|
||||
* @param iotaTopic the community topic
|
||||
* @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> {
|
||||
const parameter = {
|
||||
nameHash: nameHash.getAsHexString(),
|
||||
communityId: iotaTopic.getAsHexString(),
|
||||
}
|
||||
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) {
|
||||
logger.debug(`call findUserByNameHash, return with error: ${response.error.message}`)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* getTransactionsForAccount
|
||||
* 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
|
||||
* @throws GradidoNodeRequestError
|
||||
*/
|
||||
async function getTransactionsForAccount(
|
||||
transactionRange: TransactionsRangeInput,
|
||||
pubkey: Hex32Input,
|
||||
): Promise<ConfirmedTransaction[]> {
|
||||
const parameter = {
|
||||
...v.parse(transactionsRangeSchema, transactionRange),
|
||||
pubkey: v.parse(hex32Schema, pubkey),
|
||||
format: 'base64',
|
||||
}
|
||||
const response = await rpcCallResolved<{transactions: string[]}>('listtransactionsforaddress', parameter)
|
||||
return response.transactions.map((transactionBase64) =>
|
||||
v.parse(confirmedTransactionSchema, transactionBase64),
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
getTransaction,
|
||||
getLastTransaction,
|
||||
getTransactions,
|
||||
getAddressType,
|
||||
getTransactionsForAccount,
|
||||
findUserByNameHash,
|
||||
}
|
||||
@ -1,33 +1,36 @@
|
||||
import * as v from 'valibot'
|
||||
import { uuid4ToTopicSchema } from '../../schemas/typeConverter.schema'
|
||||
import { hex32Schema, iotaMessageIdSchema } from '../../schemas/typeGuard.schema'
|
||||
|
||||
export enum TransactionFormatType {
|
||||
BASE64 = 'base64',
|
||||
JSON = 'json',
|
||||
}
|
||||
|
||||
export const transactionFormatTypeSchema = v.nullish(
|
||||
v.enum(TransactionFormatType),
|
||||
TransactionFormatType.BASE64
|
||||
)
|
||||
|
||||
export type TransactionFormatTypeInput = v.InferInput<typeof transactionFormatTypeSchema>
|
||||
|
||||
export const getTransactionsInputSchema = v.object({
|
||||
format: transactionFormatTypeSchema,
|
||||
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),
|
||||
// default value is 100, max 100 transactions
|
||||
maxResultCount: v.undefinedable(v.pipe(v.number(), v.minValue(1, 'expect number >= 1')), 100),
|
||||
communityId: uuid4ToTopicSchema,
|
||||
topic: hex32Schema,
|
||||
})
|
||||
|
||||
export type GetTransactionsInputType = v.InferInput<typeof getTransactionsInputSchema>
|
||||
export type TransactionsRangeInput = v.InferInput<typeof transactionsRangeSchema>
|
||||
|
||||
export const getTransactionInputSchema = v.object({
|
||||
transactionIdentifier: v.object({
|
||||
iotaTopic: uuid4ToTopicSchema,
|
||||
transactionNr: v.number(),
|
||||
iotaMessageId: v.string(),
|
||||
|
||||
// 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
|
||||
),
|
||||
iotaMessageId: v.nullish(iotaMessageIdSchema, undefined),
|
||||
topic: hex32Schema,
|
||||
}),
|
||||
})
|
||||
v.custom((value: any) => {
|
||||
const setFieldsCount = Number(value.transactionNr !== undefined) + Number(value.iotaMessageId !== undefined)
|
||||
if (setFieldsCount !== 1) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 'expect transactionNr or iotaMessageId not both')
|
||||
)
|
||||
|
||||
export type TransactionIdentifierInput = v.InferInput<typeof transactionIdentifierSchema>
|
||||
export type TransactionIdentifier = v.InferOutput<typeof transactionIdentifierSchema>
|
||||
|
||||
|
||||
@ -1,174 +0,0 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { AddressType, ConfirmedTransaction, MemoryBlock, stringToAddressType } from 'gradido-blockchain-js'
|
||||
import JsonRpcClient from 'jsonrpc-ts-client'
|
||||
import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc'
|
||||
|
||||
import { CONFIG } from '../../config'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import * as v from 'valibot'
|
||||
import { confirmedTransactionFromBase64Schema } from '../../schemas/typeConverter.schema'
|
||||
import { isPortOpenRetry } from '../../utils/network'
|
||||
import { TransactionIdentifierInput, transactionIdentifierSchema } from '../../schemas/transaction.schema'
|
||||
import { GradidoNodeErrorCodes } from '../../enum/GradidoNodeErrorCodes'
|
||||
import { GetTransactionsInputType, TransactionFormatTypeInput, getTransactionsInputSchema, transactionFormatTypeSchema } from './input.schema'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`)
|
||||
|
||||
const client = new JsonRpcClient({
|
||||
url: CONFIG.NODE_SERVER_URL,
|
||||
})
|
||||
|
||||
|
||||
interface ConfirmedTransactionList {
|
||||
transactions: string[]
|
||||
timeUsed: string
|
||||
}
|
||||
|
||||
interface ConfirmedTransactionResponse {
|
||||
transaction: string
|
||||
timeUsed: string
|
||||
}
|
||||
|
||||
interface AddressTypeResult {
|
||||
addressType: string
|
||||
}
|
||||
|
||||
interface FindUserResponse {
|
||||
pubkey: string
|
||||
timeUsed: string
|
||||
}
|
||||
|
||||
export class GradidoNodeRequestError<T> extends Error {
|
||||
private response?: JsonRpcEitherResponse<T>
|
||||
constructor(message: string, response?: JsonRpcEitherResponse<T>) {
|
||||
super(message)
|
||||
this.name = 'GradidoNodeRequestError'
|
||||
this.response = response
|
||||
}
|
||||
getResponse(): JsonRpcEitherResponse<T> | undefined {
|
||||
return this.response
|
||||
}
|
||||
}
|
||||
|
||||
function resolveResponse<T, R>(response: JsonRpcEitherResponse<T>, onSuccess: (result: T) => R): R {
|
||||
if (response.isSuccess()) {
|
||||
return onSuccess(response.result)
|
||||
} else if (response.isError()) {
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
throw new GradidoNodeRequestError('no success and no error')
|
||||
}
|
||||
|
||||
async function getTransactions(input: GetTransactionsInputType): Promise<ConfirmedTransaction[]> {
|
||||
const parameter = v.parse(getTransactionsInputSchema, input)
|
||||
logger.debug('call getTransactions with ', parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
const response = await client.exec<ConfirmedTransactionList>('getTransactions', parameter)
|
||||
return resolveResponse(response, (result: ConfirmedTransactionList) => {
|
||||
logger.info(`call getTransactions, used ${result.timeUsed}`)
|
||||
return result.transactions.map((transactionBase64) =>
|
||||
v.parse(confirmedTransactionFromBase64Schema, transactionBase64),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async function getTransaction(
|
||||
transactionIdentifier: TransactionIdentifierInput,
|
||||
format: TransactionFormatTypeInput
|
||||
)
|
||||
: Promise<ConfirmedTransaction | undefined> {
|
||||
const parameter = {
|
||||
...v.parse(transactionIdentifierSchema, transactionIdentifier),
|
||||
format: v.parse(transactionFormatTypeSchema, format),
|
||||
}
|
||||
logger.debug('call gettransaction with ', parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
const response = await client.exec<ConfirmedTransactionResponse>('gettransaction', parameter)
|
||||
return resolveResponse(response, (result: ConfirmedTransactionResponse) => {
|
||||
logger.info(`call gettransaction, used ${result.timeUsed}`)
|
||||
return result.transaction && result.transaction !== ''
|
||||
? v.parse(confirmedTransactionFromBase64Schema, result.transaction)
|
||||
: undefined
|
||||
})
|
||||
}
|
||||
|
||||
async function getLastTransaction(iotaTopic: string): Promise<ConfirmedTransaction | undefined> {
|
||||
const parameter = {
|
||||
format: 'base64',
|
||||
communityId: iotaTopic,
|
||||
}
|
||||
logger.debug('call getlasttransaction with ', parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
const response = await client.exec<ConfirmedTransactionResponse>('getlasttransaction', parameter)
|
||||
return resolveResponse(response, (result: ConfirmedTransactionResponse) => {
|
||||
logger.info(`call getlasttransaction, used ${result.timeUsed}`)
|
||||
return result.transaction && result.transaction !== ''
|
||||
? v.parse(confirmedTransactionFromBase64Schema, result.transaction)
|
||||
: undefined
|
||||
})
|
||||
}
|
||||
|
||||
async function getAddressType(pubkey: Buffer, iotaTopic: string): Promise<AddressType | undefined> {
|
||||
const parameter = {
|
||||
pubkey: pubkey.toString('hex'),
|
||||
communityId: iotaTopic,
|
||||
}
|
||||
logger.debug('call getaddresstype with ', parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
const response = await client.exec<AddressTypeResult>('getaddresstype', parameter)
|
||||
return resolveResponse(response, (result: AddressTypeResult) => {
|
||||
logger.info(`call getaddresstype`)
|
||||
return stringToAddressType(result.addressType)
|
||||
})
|
||||
}
|
||||
|
||||
async function findUserByNameHash(nameHash: MemoryBlock, iotaTopic: string): Promise<MemoryBlock | undefined> {
|
||||
const parameter = {
|
||||
nameHash: nameHash.convertToHex(),
|
||||
communityId: iotaTopic,
|
||||
}
|
||||
logger.debug('call findUserByNameHash with ', parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
const response = await client.exec<FindUserResponse>('findUserByNameHash', parameter)
|
||||
if (response.isError() && response.error.code === GradidoNodeErrorCodes.JSON_RPC_ERROR_ADDRESS_NOT_FOUND) {
|
||||
return undefined
|
||||
}
|
||||
return resolveResponse(response, (result: FindUserResponse) => {
|
||||
logger.info(`call findUserByNameHash, used ${result.timeUsed}`)
|
||||
return result.pubkey && result.pubkey !== '' ? MemoryBlock.fromHex(result.pubkey) : undefined
|
||||
})
|
||||
}
|
||||
|
||||
async function getTransactionsForAccount(
|
||||
pubkey: MemoryBlock,
|
||||
iotaTopic: string,
|
||||
maxResultCount = 0,
|
||||
firstTransactionNr = 1,
|
||||
): Promise<ConfirmedTransaction[] | undefined> {
|
||||
const parameter = {
|
||||
pubkey: pubkey.convertToHex(),
|
||||
format: 'base64',
|
||||
firstTransactionNr,
|
||||
maxResultCount,
|
||||
communityId: iotaTopic,
|
||||
}
|
||||
logger.debug('call listtransactionsforaddress with ', parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
const response = await client.exec<ConfirmedTransactionList>('listtransactionsforaddress', parameter)
|
||||
return resolveResponse(response, (result: ConfirmedTransactionList) => {
|
||||
logger.info(`call listtransactionsforaddress, used ${result.timeUsed}`)
|
||||
return result.transactions.map((transactionBase64) =>
|
||||
v.parse(confirmedTransactionFromBase64Schema, transactionBase64),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
getTransaction,
|
||||
getLastTransaction,
|
||||
getTransactions,
|
||||
getAddressType,
|
||||
getTransactionsForAccount,
|
||||
findUserByNameHash,
|
||||
}
|
||||
54
dlt-connector/src/client/GradidoNode/jsonrpc.ts
Normal file
54
dlt-connector/src/client/GradidoNode/jsonrpc.ts
Normal file
@ -0,0 +1,54 @@
|
||||
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'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode`)
|
||||
|
||||
export const client = new JsonRpcClient({
|
||||
url: CONFIG.NODE_SERVER_URL,
|
||||
})
|
||||
|
||||
export class GradidoNodeRequestError<T> extends Error {
|
||||
private response?: JsonRpcEitherResponse<T>
|
||||
constructor(message: string, response?: JsonRpcEitherResponse<T>) {
|
||||
super(message)
|
||||
this.name = 'GradidoNodeRequestError'
|
||||
this.response = response
|
||||
}
|
||||
getResponse(): JsonRpcEitherResponse<T> | undefined {
|
||||
return this.response
|
||||
}
|
||||
}
|
||||
|
||||
// return result on success or throw error
|
||||
export function resolveResponse<T, R>(response: JsonRpcEitherResponse<T>, onSuccess: (result: T) => R): R {
|
||||
if (response.isSuccess()) {
|
||||
return onSuccess(response.result)
|
||||
} else if (response.isError()) {
|
||||
throw new GradidoNodeRequestError(response.error.message, response)
|
||||
}
|
||||
throw new GradidoNodeRequestError('no success and no error')
|
||||
}
|
||||
|
||||
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>> {
|
||||
logger.debug('call %s with %s', method, parameter)
|
||||
await isPortOpenRetry(CONFIG.NODE_SERVER_URL)
|
||||
return client.exec<T>(method, parameter)
|
||||
}
|
||||
|
||||
// template rpcCall, check first if port is open before executing json rpc 2.0 request, throw error on failure, return result on success
|
||||
export async function rpcCallResolved<T>(method: string, parameter: any): Promise<T> {
|
||||
const response = await rpcCall<WithTimeUsed<T>>(method, parameter)
|
||||
return resolveResponse(response, (result: WithTimeUsed<T>) => {
|
||||
if (result.timeUsed) {
|
||||
logger.info(`call %s, used ${result.timeUsed}`, method)
|
||||
}
|
||||
return result as T
|
||||
})
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
import { gql, GraphQLClient } from 'graphql-request'
|
||||
import { SignJWT } from 'jose'
|
||||
|
||||
import { CONFIG } from '../config'
|
||||
import { communitySchema, type Community } from '../schemas/rpcParameter.schema'
|
||||
import { CONFIG } from '../../config'
|
||||
import { communitySchema, type Community } from './community.schema'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../config/const'
|
||||
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
|
||||
import * as v from 'valibot'
|
||||
|
||||
const homeCommunity = gql`
|
||||
@ -1,10 +1,10 @@
|
||||
import { communitySchema } from './rpcParameter.schema'
|
||||
import { uuidv4Schema } from './typeGuard.schema'
|
||||
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'
|
||||
|
||||
describe('rpcParameter.schema', () => {
|
||||
describe('community.schema', () => {
|
||||
it('community', () => {
|
||||
expect(v.parse(communitySchema, {
|
||||
uuid: '4f28e081-5c39-4dde-b6a4-3bde71de8d65',
|
||||
@ -1,6 +1,6 @@
|
||||
import * as v from 'valibot'
|
||||
import { uuidv4Schema } from './typeGuard.schema'
|
||||
import { dateSchema } from './typeConverter.schema'
|
||||
import { uuidv4Schema } from '../../schemas/typeGuard.schema'
|
||||
import { dateSchema } from '../../schemas/typeConverter.schema'
|
||||
|
||||
/**
|
||||
* Schema Definitions for rpc call parameter, when dlt-connector is called from backend
|
||||
33
dlt-connector/src/data/Uuidv4Hash.ts
Normal file
33
dlt-connector/src/data/Uuidv4Hash.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { MemoryBlock } from 'gradido-blockchain-js'
|
||||
import { Uuidv4, Hex32, hex32Schema, MemoryBlock32, memoryBlock32Schema } from '../schemas/typeGuard.schema'
|
||||
import * as v from 'valibot'
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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)
|
||||
*/
|
||||
export class Uuidv4Hash {
|
||||
uuidv4Hash: MemoryBlock32
|
||||
// used for caching hex string representation of uuidv4Hash
|
||||
uuidv4HashString: Hex32 | undefined
|
||||
|
||||
constructor(uuidv4: Uuidv4) {
|
||||
this.uuidv4Hash = v.parse(memoryBlock32Schema, MemoryBlock.fromHex(uuidv4.replace(/-/g, '')).calculateHash())
|
||||
}
|
||||
|
||||
getAsMemoryBlock(): MemoryBlock32 {
|
||||
return this.uuidv4Hash
|
||||
}
|
||||
|
||||
getAsHexString(): Hex32 {
|
||||
if (!this.uuidv4HashString) {
|
||||
this.uuidv4HashString = v.parse(hex32Schema, this.uuidv4Hash)
|
||||
}
|
||||
return this.uuidv4HashString
|
||||
}
|
||||
}
|
||||
@ -10,5 +10,6 @@ export enum AccountType {
|
||||
COMMUNITY_AUF = 'COMMUNITY_AUF', // community compensation and environment founds account
|
||||
COMMUNITY_PROJECT = 'COMMUNITY_PROJECT', // no creations allowed
|
||||
SUBACCOUNT = 'SUBACCOUNT', // no creations allowed
|
||||
CRYPTO_ACCOUNT = 'CRYPTO_ACCOUNT', // user control his keys, no creations
|
||||
CRYPTO_ACCOUNT = 'CRYPTO_ACCOUNT', // user control his keys, no creations,
|
||||
DEFERRED_TRANSFER = 'DEFERRED_TRANSFER', // no creations allowed
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
AddressType_CRYPTO_ACCOUNT,
|
||||
AddressType_NONE,
|
||||
AddressType_SUBACCOUNT,
|
||||
AddressType_DEFERRED_TRANSFER,
|
||||
} from 'gradido-blockchain-js'
|
||||
|
||||
export enum AddressType {
|
||||
@ -16,4 +17,5 @@ export enum AddressType {
|
||||
CRYPTO_ACCOUNT = AddressType_CRYPTO_ACCOUNT,
|
||||
NONE = AddressType_NONE,
|
||||
SUBACCOUNT = AddressType_SUBACCOUNT,
|
||||
DEFERRED_TRANSFER = AddressType_DEFERRED_TRANSFER,
|
||||
}
|
||||
@ -4,11 +4,13 @@ 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/BackendClient'
|
||||
import { BackendClient } from './client/backend/BackendClient'
|
||||
import { KeyPairCacheManager } from './KeyPairCacheManager'
|
||||
import { communityUuidToTopicSchema } from './schemas/rpcParameter.schema'
|
||||
import { parse } from 'valibot'
|
||||
import { getTransaction } from './client/GradidoNode/jsonrpc.api'
|
||||
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 * as v from 'valibot'
|
||||
|
||||
async function main() {
|
||||
// configure log4js
|
||||
@ -16,21 +18,12 @@ async function main() {
|
||||
const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8'))
|
||||
configure(options)
|
||||
const logger = getLogger('dlt')
|
||||
// TODO: replace with schema validation
|
||||
if (CONFIG.IOTA_HOME_COMMUNITY_SEED) {
|
||||
try {
|
||||
const seed = MemoryBlock.fromHex(CONFIG.IOTA_HOME_COMMUNITY_SEED)
|
||||
if (seed.size() < 32) {
|
||||
throw new Error('seed need to be greater than 32 Bytes')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
'IOTA_HOME_COMMUNITY_SEED must be a valid hex string, at least 64 characters long',
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
// check if valid seed for root key pair generation is present
|
||||
if (!v.safeParse(keyGenerationSeedSchema, CONFIG.IOTA_HOME_COMMUNITY_SEED).success) {
|
||||
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),
|
||||
@ -46,19 +39,19 @@ async function main() {
|
||||
await isPortOpenRetry(CONFIG.BACKEND_SERVER_URL)
|
||||
const homeCommunity = await backend.getHomeCommunityDraft()
|
||||
KeyPairCacheManager.getInstance().setHomeCommunityUUID(homeCommunity.uuid)
|
||||
logger.info('home community topic: %s', parse(communityUuidToTopicSchema, homeCommunity.uuid))
|
||||
const topic = new Uuidv4Hash(homeCommunity.uuid).getAsHexString()
|
||||
logger.info('home community topic: %s', topic)
|
||||
logger.info('gradido node server: %s', CONFIG.NODE_SERVER_URL)
|
||||
// ask gradido node if community blockchain was created
|
||||
try {
|
||||
const topic = parse(communityUuidToTopicSchema, homeCommunity.uuid)
|
||||
if (!await getTransaction(1, topic)) {
|
||||
if (!await getTransaction({ transactionNr: 1, topic })) {
|
||||
// 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(CONFIG.DLT_CONNECTOR_PORT, () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
import { communityUuidToTopicSchema } from '../../schemas/rpcParameter.schema'
|
||||
import { communityUuidToTopicSchema } from '../../client/backend/community.schema'
|
||||
import * as v from 'valibot'
|
||||
|
||||
export abstract class AbstractRemoteKeyPairRole {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
|
||||
import { getTransaction } from '../../client/GradidoNode/jsonrpc.api'
|
||||
import { getTransaction } from '../../client/GradidoNode/api'
|
||||
|
||||
import { AbstractRemoteKeyPairRole } from './AbstractRemoteKeyPair.role'
|
||||
import { GradidoNodeInvalidTransactionError, GradidoNodeMissingTransactionError } from '../../errors'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
|
||||
import { findUserByNameHash } from '../../client/GradidoNode/jsonrpc.api'
|
||||
import { findUserByNameHash } from '../../client/GradidoNode/api'
|
||||
import { IdentifierAccount } from '../../schemas/account.schema'
|
||||
import { GradidoNodeMissingUserError, ParameterError } from '../../errors'
|
||||
import { AbstractRemoteKeyPairRole } from './AbstractRemoteKeyPair.role'
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
import { Community, CommunityInput, communitySchema } from '../../schemas/rpcParameter.schema'
|
||||
import { Community, CommunityInput, communitySchema } from '../../client/backend/community.schema'
|
||||
import * as v from 'valibot'
|
||||
|
||||
export class CommunityRootTransactionRole extends AbstractTransactionRole {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
|
||||
import * as v from 'valibot'
|
||||
import { uuidv4Schema } from './typeConverter.schema'
|
||||
import { uuidv4Schema } from './typeGuard.schema'
|
||||
|
||||
// use code from transaction links
|
||||
export const identifierSeedSchema = v.object({
|
||||
|
||||
11
dlt-connector/src/schemas/base.schema.ts
Normal file
11
dlt-connector/src/schemas/base.schema.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import * as v from 'valibot'
|
||||
import { MemoryBlock } from 'gradido-blockchain-js'
|
||||
|
||||
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),
|
||||
),
|
||||
)
|
||||
@ -4,29 +4,9 @@ import { identifierAccountSchema } from './account.schema'
|
||||
import { InputTransactionType } from '../enum/InputTransactionType'
|
||||
import { accountTypeToAddressTypeSchema } from './typeConverter.schema'
|
||||
|
||||
// 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(0, 'expect number >= 0')),
|
||||
0
|
||||
),
|
||||
iotaMessageId: v.nullish(iotaMessageIdSchema, undefined),
|
||||
communityId: uuid4ToTopicSchema,
|
||||
}),
|
||||
v.custom((value: any) => {
|
||||
const setFieldsCount = Number(value.transactionNr !== 0) + Number(value.iotaMessageId !== undefined)
|
||||
if (setFieldsCount !== 1) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, 'expect transactionNr or iotaMessageId not both')
|
||||
)
|
||||
export type TransactionIdentifierInput = v.InferInput<typeof transactionIdentifierSchema>
|
||||
export type TransactionIdentifier = v.InferOutput<typeof transactionIdentifierSchema>
|
||||
|
||||
export const transactionSchema = v.object({
|
||||
user: identifierAccountSchema,
|
||||
user: identifierAccountScmhema,
|
||||
linkedUser: v.nullish(identifierAccountSchema, undefined),
|
||||
amount: v.nullish(amountToGradidoUnitSchema, undefined),
|
||||
memo: v.nullish(memoSchema, undefined),
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
|
||||
import { accountTypeSchema, addressTypeSchema, confirmedTransactionFromBase64Schema } from './typeConverter.schema'
|
||||
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 } from 'gradido-blockchain-js'
|
||||
import { AddressType_COMMUNITY_AUF, AddressType_COMMUNITY_PROJECT } from 'gradido-blockchain-js'
|
||||
import { AccountType } from '../enum/AccountType'
|
||||
|
||||
describe('basic.schema', () => {
|
||||
@ -24,6 +24,10 @@ describe('basic.schema', () => {
|
||||
})
|
||||
|
||||
describe('AddressType and AccountType', () => {
|
||||
it('AddressType from string', () => {
|
||||
const addressType = v.parse(addressTypeSchema, 'COMMUNITY_AUF')
|
||||
expect(addressType).toBe(AddressType_COMMUNITY_AUF)
|
||||
})
|
||||
it('AddressType from AddressType', () => {
|
||||
const addressType = v.parse(addressTypeSchema, AddressType_COMMUNITY_AUF)
|
||||
expect(addressType).toBe(AddressType_COMMUNITY_AUF)
|
||||
@ -42,8 +46,8 @@ describe('basic.schema', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('confirmedTransactionFromBase64Schema', () => {
|
||||
const confirmedTransaction = v.parse(confirmedTransactionFromBase64Schema, 'CAcSAgoAGgYIwvK5/wUiAzMuNCogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
|
||||
it('confirmedTransactionSchema', () => {
|
||||
const confirmedTransaction = v.parse(confirmedTransactionSchema, 'CAcSAgoAGgYIwvK5/wUiAzMuNCogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
|
||||
expect(confirmedTransaction.getId()).toBe(7)
|
||||
expect(confirmedTransaction.getConfirmedAt().getSeconds()).toBe(1609464130)
|
||||
expect(confirmedTransaction.getVersionNumber()).toBe('3.4')
|
||||
|
||||
@ -1,20 +1,10 @@
|
||||
import {
|
||||
AddressType as AddressType,
|
||||
AddressType_COMMUNITY_AUF,
|
||||
AddressType_COMMUNITY_GMW,
|
||||
AddressType_COMMUNITY_HUMAN,
|
||||
AddressType_COMMUNITY_PROJECT,
|
||||
AddressType_CRYPTO_ACCOUNT,
|
||||
AddressType_NONE,
|
||||
AddressType_SUBACCOUNT,
|
||||
ConfirmedTransaction,
|
||||
DeserializeType_CONFIRMED_TRANSACTION,
|
||||
InteractionDeserialize,
|
||||
MemoryBlock,
|
||||
} from 'gradido-blockchain-js'
|
||||
import { AccountType } from '../enum/AccountType'
|
||||
// import { AddressType as AddressTypeWrapper } from '../enum/AddressType'
|
||||
import * as v from 'valibot'
|
||||
import { confirmedTransactionFromBase64, isAddressType, toAddressType, toAccountType } from '../utils/typeConverter'
|
||||
|
||||
/**
|
||||
* dateSchema for creating a date from string or Date object
|
||||
@ -42,45 +32,14 @@ export const dateSchema = v.pipe(
|
||||
* AddressType is defined in gradido-blockchain C++ Code
|
||||
* AccountType is the enum defined in TypeScript but with the same options
|
||||
* addressTypeSchema and accountTypeSchema are for easy handling and conversion between both
|
||||
*/
|
||||
|
||||
const accountToAddressMap: Record<AccountType, AddressType> = {
|
||||
[AccountType.COMMUNITY_AUF]: AddressType_COMMUNITY_AUF,
|
||||
[AccountType.COMMUNITY_GMW]: AddressType_COMMUNITY_GMW,
|
||||
[AccountType.COMMUNITY_HUMAN]: AddressType_COMMUNITY_HUMAN,
|
||||
[AccountType.COMMUNITY_PROJECT]: AddressType_COMMUNITY_PROJECT,
|
||||
[AccountType.CRYPTO_ACCOUNT]: AddressType_CRYPTO_ACCOUNT,
|
||||
[AccountType.SUBACCOUNT]: AddressType_SUBACCOUNT,
|
||||
[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>)
|
||||
|
||||
function isAddressType(val: unknown): val is AddressType {
|
||||
return typeof val === 'number' && Object.keys(addressToAccountMap).includes(val.toString())
|
||||
}
|
||||
|
||||
function isAccountType(val: unknown): val is AccountType {
|
||||
return Object.values(AccountType).includes(val as AccountType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for address type, can also convert from account type (if used with v.parse)
|
||||
*/
|
||||
export const addressTypeSchema = v.pipe(
|
||||
v.union([
|
||||
v.enum(AccountType, 'expect account type'),
|
||||
v.custom<AddressType>((val): val is AddressType => isAddressType(val), 'expect AddressType'),
|
||||
v.custom<AddressType>(isAddressType, 'expect AddressType'),
|
||||
]),
|
||||
v.transform<AccountType | AddressType, AddressType>((value) => {
|
||||
if (isAddressType(value)) {
|
||||
return value;
|
||||
}
|
||||
return accountToAddressMap[value as AccountType] ?? AddressType_NONE
|
||||
}),
|
||||
v.transform<AccountType | AddressType, AddressType>((value) => toAddressType(value)),
|
||||
)
|
||||
|
||||
/**
|
||||
@ -91,34 +50,24 @@ export const accountTypeSchema = v.pipe(
|
||||
v.custom<AddressType>(isAddressType, 'expect AddressType'),
|
||||
v.enum(AccountType, 'expect AccountType'),
|
||||
]),
|
||||
v.transform<AddressType | AccountType, AccountType>((value) => {
|
||||
if (isAccountType(value)) {
|
||||
return value;
|
||||
}
|
||||
return addressToAccountMap[value as AddressType] ?? AccountType.NONE;
|
||||
}),
|
||||
v.transform<AddressType | AccountType, AccountType>((value) => toAccountType(value)),
|
||||
)
|
||||
|
||||
const confirmedTransactionFromBase64 = (base64: string): ConfirmedTransaction => {
|
||||
const deserializer = new InteractionDeserialize(
|
||||
MemoryBlock.fromBase64(base64),
|
||||
DeserializeType_CONFIRMED_TRANSACTION,
|
||||
)
|
||||
deserializer.run()
|
||||
const confirmedTransaction = deserializer.getConfirmedTransaction()
|
||||
if (!confirmedTransaction) {
|
||||
throw new Error("invalid data, couldn't deserialize")
|
||||
}
|
||||
return confirmedTransaction
|
||||
}
|
||||
|
||||
export const confirmedTransactionFromBase64Schema = v.pipe(
|
||||
v.pipe(
|
||||
v.string('expect confirmed Transaction base64 as string type'),
|
||||
v.base64('expect to be valid base64')
|
||||
),
|
||||
v.transform<string, ConfirmedTransaction>(
|
||||
(base64: string) => confirmedTransactionFromBase64(base64),
|
||||
export const confirmedTransactionSchema = v.pipe(
|
||||
v.union([
|
||||
v.instance(ConfirmedTransaction, 'expect ConfirmedTransaction'),
|
||||
v.pipe(
|
||||
v.string('expect confirmed Transaction base64 as string type'),
|
||||
v.base64('expect to be valid base64')
|
||||
),
|
||||
]),
|
||||
v.transform<string | ConfirmedTransaction, ConfirmedTransaction>(
|
||||
(data: string | ConfirmedTransaction) => {
|
||||
if (data instanceof ConfirmedTransaction) {
|
||||
return data
|
||||
}
|
||||
return confirmedTransactionFromBase64(data)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@ -1,67 +1,37 @@
|
||||
import { describe, it, expect } from 'bun:test'
|
||||
import { uuidv4Schema, topicIndexSchema, uuid4HashSchema, memoSchema } from './typeGuard.schema'
|
||||
import { uuidv4Schema, memoSchema } from './typeGuard.schema'
|
||||
import * as v from 'valibot'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { MemoryBlock } from 'gradido-blockchain-js'
|
||||
|
||||
describe('typeGuard.schema', () => {
|
||||
describe('Uuidv4', () => {
|
||||
const uuidv4String = uuidv4()
|
||||
const uuidv4Hash = MemoryBlock.fromHex(uuidv4String.replace(/-/g, '')).calculateHash()
|
||||
|
||||
it('from string to uuidv4', () => {
|
||||
const uuidv4Value = v.parse(uuidv4Schema, uuidv4String)
|
||||
expect(uuidv4Value.toString()).toBe(uuidv4String)
|
||||
})
|
||||
|
||||
it('from uuidv4 to hash', () => {
|
||||
const uuidv4Value = v.parse(uuidv4Schema, uuidv4String)
|
||||
const uuidv4HashParsed = v.parse(uuid4HashSchema, uuidv4Value)
|
||||
expect(uuidv4HashParsed.copyAsString()).toBe(uuidv4Hash.copyAsString())
|
||||
})
|
||||
|
||||
it('from uuidv4 string to hash', () => {
|
||||
const uuidv4HashParsed = v.parse(uuid4HashSchema, uuidv4String)
|
||||
expect(uuidv4HashParsed.copyAsString()).toBe(uuidv4Hash.copyAsString())
|
||||
})
|
||||
|
||||
it('from uuidv4 hash to topicIndex (hash in hex format', () => {
|
||||
const uuidv4HashParsed = v.parse(uuid4HashSchema, uuidv4String)
|
||||
const topicIndex = v.parse(topicIndexSchema, uuidv4HashParsed)
|
||||
expect(topicIndex.toString()).toBe(uuidv4Hash.convertToHex())
|
||||
})
|
||||
|
||||
it('from uuidv4 to topicIndex (hash in hex format)', () => {
|
||||
const uuidv4Value = v.parse(uuidv4Schema, uuidv4String)
|
||||
const topicIndex = v.parse(topicIndexSchema, uuidv4Value)
|
||||
expect(topicIndex.toString()).toBe(uuidv4Hash.convertToHex())
|
||||
})
|
||||
|
||||
it('from uuidv4 string to topicIndex (hash in hex format)', () => {
|
||||
const topicIndex = v.parse(topicIndexSchema, uuidv4String)
|
||||
expect(topicIndex.toString()).toBe(uuidv4Hash.convertToHex())
|
||||
})
|
||||
})
|
||||
describe('Basic Type Schemas for transactions', () => {
|
||||
describe('Memo', () => {
|
||||
it('min length', () => {
|
||||
const memoValue = 'memo1'
|
||||
const memoValueParsed = v.parse(memoSchema, memoValue)
|
||||
expect(memoValueParsed.toString()).toBe(memoValue)
|
||||
})
|
||||
it('max length', () => {
|
||||
const memoValue = 's'.repeat(255)
|
||||
const memoValueParsed = v.parse(memoSchema, memoValue)
|
||||
expect(memoValueParsed.toString()).toBe(memoValue)
|
||||
})
|
||||
it('to short', () => {
|
||||
const memoValue = 'memo'
|
||||
expect(() => v.parse(memoSchema, memoValue)).toThrow(new Error('expect string length >= 5'))
|
||||
})
|
||||
it('to long', () => {
|
||||
const memoValue = 's'.repeat(256)
|
||||
expect(() => v.parse(memoSchema, memoValue)).toThrow(new Error('expect string length <= 255'))
|
||||
})
|
||||
describe('Memo', () => {
|
||||
it('min length', () => {
|
||||
const memoValue = 'memo1'
|
||||
const memoValueParsed = v.parse(memoSchema, memoValue)
|
||||
expect(memoValueParsed.toString()).toBe(memoValue)
|
||||
})
|
||||
it('max length', () => {
|
||||
const memoValue = 's'.repeat(255)
|
||||
const memoValueParsed = v.parse(memoSchema, memoValue)
|
||||
expect(memoValueParsed.toString()).toBe(memoValue)
|
||||
})
|
||||
it('to short', () => {
|
||||
const memoValue = 'memo'
|
||||
expect(() => v.parse(memoSchema, memoValue)).toThrow(new Error('expect string length >= 5'))
|
||||
})
|
||||
it('to long', () => {
|
||||
const memoValue = 's'.repeat(256)
|
||||
expect(() => v.parse(memoSchema, memoValue)).toThrow(new Error('expect string length <= 255'))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -13,6 +13,7 @@
|
||||
* `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 { validate, version } from 'uuid'
|
||||
@ -27,48 +28,89 @@ import { MemoryBlock, DurationSeconds, GradidoUnit } from 'gradido-blockchain-js
|
||||
declare const validUuidv4: unique symbol
|
||||
export type Uuidv4 = string & { [validUuidv4]: true };
|
||||
|
||||
export const uuidv4Schema = v.custom<Uuidv4>((value) =>
|
||||
(typeof value === 'string' && validate(value) && version(value) === 4),
|
||||
'uuid v4 expected'
|
||||
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,
|
||||
),
|
||||
)
|
||||
|
||||
export type Uuidv4Input = v.InferInput<typeof uuidv4Schema>
|
||||
|
||||
/**
|
||||
* type guard for memory block size 32
|
||||
* create with `v.parse(memoryBlock32Schema, MemoryBlock.fromHex('39568d7e148a0afee7f27a67dbf7d4e87d1fdec958e2680df98a469690ffc1a2'))`
|
||||
* memoryBlock32 is a non-empty MemoryBlock with size 32
|
||||
*/
|
||||
|
||||
declare const validMemoryBlock32: unique symbol
|
||||
export type MemoryBlock32 = MemoryBlock & { [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,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* type guard for uuid v4 hash
|
||||
* const uuidv4Value: Uuidv4 = v.parse(uuidv4Schema, 'uuid')
|
||||
* create with `v.parse(uuidv4HashSchema, uuidv4Value)`
|
||||
* uuidv4Hash is uuidv4 value hashed with BLAKE2b as Binary Type MemoryBlock from gradido-blockchain similar to Node.js Buffer Type,
|
||||
* used for iota topic
|
||||
* type guard for hex string of length 64 (binary size = 32)
|
||||
* create with `v.parse(hex32Schema, '39568d7e148a0afee7f27a67dbf7d4e87d1fdec958e2680df98a469690ffc1a2')`
|
||||
* or `v.parse(hex32Schema, MemoryBlock.fromHex('39568d7e148a0afee7f27a67dbf7d4e87d1fdec958e2680df98a469690ffc1a2'))`
|
||||
* hex32 is a hex string of length 64 (binary size = 32)
|
||||
*/
|
||||
declare const validUuidv4Hash: unique symbol
|
||||
export type Uuidv4Hash = MemoryBlock & { [validUuidv4Hash]: true };
|
||||
declare const validHex32: unique symbol
|
||||
export type Hex32 = string & { [validHex32]: true };
|
||||
|
||||
export const uuid4HashSchema = v.pipe(
|
||||
uuidv4Schema,
|
||||
v.transform<Uuidv4, Uuidv4Hash>(
|
||||
(input: Uuidv4) => MemoryBlock.fromHex(input.replace(/-/g, '')).calculateHash() as Uuidv4Hash,
|
||||
)
|
||||
export const hex32Schema = 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 | Hex32, Hex32>(
|
||||
(input: string | MemoryBlock32 | Hex32) => {
|
||||
if (typeof input === 'string') {
|
||||
return input as Hex32
|
||||
}
|
||||
return input.convertToHex() as Hex32
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* type guard for topic index
|
||||
* const uuidv4Value: Uuidv4 = v.parse(uuidv4Schema, 'uuid')
|
||||
* const uuidv4Hash: Uuidv4Hash = v.parse(uuid4HashSchema, uuidv4Value)
|
||||
* create with `v.parse(topicIndexSchema, uuidv4Hash)`
|
||||
* topicIndex is uuidv4Hash value converted to hex string used for iota topic
|
||||
* The beauty of valibot allow also parse a uuidv4 string directly to topicIndex
|
||||
* const topic: TopicIndex = v.parse(topicIndexSchema, 'uuid')
|
||||
*/
|
||||
declare const validTopicIndex: unique symbol
|
||||
export type TopicIndex = string & { [validTopicIndex]: true };
|
||||
export type Hex32Input = v.InferInput<typeof hex32Schema>
|
||||
|
||||
export const topicIndexSchema = v.pipe(
|
||||
v.union([uuidv4Schema, v.custom((val): val is Uuidv4Hash => val instanceof MemoryBlock)]),
|
||||
v.transform<any, TopicIndex>((input) => {
|
||||
const hash = typeof input === 'string'
|
||||
? MemoryBlock.fromHex(input.replace(/-/g, '')).calculateHash()
|
||||
: input;
|
||||
return hash.convertToHex() as TopicIndex;
|
||||
})
|
||||
/**
|
||||
* type guard for iota message id
|
||||
* create with `v.parse(iotaMessageIdSchema, '822387692a7cfd3f07f25742e91e248af281d771ee03a432c2e178e5533f786c')`
|
||||
* iota message id is a hex string of length 64
|
||||
*/
|
||||
declare const validIotaMessageId: unique symbol
|
||||
export type IotaMessageId = Hex32 & { [validIotaMessageId]: 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,
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
@ -133,7 +175,8 @@ export const amountSchema = v.pipe(
|
||||
/**
|
||||
* type guard for gradido amount
|
||||
* create with `v.parse(gradidoAmountSchema, '123')`
|
||||
* gradido amount is a string representing a positive decimal number, compatible with decimal.js
|
||||
* gradido amount is a GradidoUnit representing a positive gradido amount stored intern as integer with 4 decimal places
|
||||
* 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 };
|
||||
|
||||
77
dlt-connector/src/server/index.ts
Normal file
77
dlt-connector/src/server/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
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 { AddressType_NONE } from 'gradido-blockchain-js'
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
72
dlt-connector/src/utils/typeConverter.ts
Normal file
72
dlt-connector/src/utils/typeConverter.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import {
|
||||
ConfirmedTransaction,
|
||||
MemoryBlock,
|
||||
InteractionDeserialize,
|
||||
DeserializeType_CONFIRMED_TRANSACTION,
|
||||
AddressType_COMMUNITY_AUF,
|
||||
AddressType_COMMUNITY_GMW,
|
||||
AddressType_COMMUNITY_HUMAN,
|
||||
AddressType_COMMUNITY_PROJECT,
|
||||
AddressType_CRYPTO_ACCOUNT,
|
||||
AddressType_SUBACCOUNT,
|
||||
AddressType_DEFERRED_TRANSFER,
|
||||
AddressType_NONE,
|
||||
AddressType,
|
||||
} from 'gradido-blockchain-js'
|
||||
import { AccountType } from '../enum/AccountType'
|
||||
|
||||
export const confirmedTransactionFromBase64 = (base64: string): ConfirmedTransaction => {
|
||||
const confirmedTransactionBinaryPtr = MemoryBlock.createPtr(MemoryBlock.fromBase64(base64))
|
||||
const deserializer = new InteractionDeserialize(
|
||||
confirmedTransactionBinaryPtr,
|
||||
DeserializeType_CONFIRMED_TRANSACTION,
|
||||
)
|
||||
deserializer.run()
|
||||
const confirmedTransaction = deserializer.getConfirmedTransaction()
|
||||
if (!confirmedTransaction) {
|
||||
throw new Error("invalid data, couldn't deserialize")
|
||||
}
|
||||
return confirmedTransaction
|
||||
}
|
||||
|
||||
/**
|
||||
* AddressType is defined in gradido-blockchain C++ Code
|
||||
* AccountType is the enum defined in TypeScript but with the same options
|
||||
*/
|
||||
const accountToAddressMap: Record<AccountType, AddressType> = {
|
||||
[AccountType.COMMUNITY_AUF]: AddressType_COMMUNITY_AUF,
|
||||
[AccountType.COMMUNITY_GMW]: AddressType_COMMUNITY_GMW,
|
||||
[AccountType.COMMUNITY_HUMAN]: AddressType_COMMUNITY_HUMAN,
|
||||
[AccountType.COMMUNITY_PROJECT]: AddressType_COMMUNITY_PROJECT,
|
||||
[AccountType.CRYPTO_ACCOUNT]: AddressType_CRYPTO_ACCOUNT,
|
||||
[AccountType.SUBACCOUNT]: AddressType_SUBACCOUNT,
|
||||
[AccountType.DEFERRED_TRANSFER]: AddressType_DEFERRED_TRANSFER,
|
||||
[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>)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
export function toAddressType(input: AccountType | AddressType): AddressType {
|
||||
if (isAddressType(input)) {
|
||||
return input
|
||||
}
|
||||
return accountToAddressMap[input as AccountType] ?? AddressType_NONE
|
||||
}
|
||||
|
||||
export function toAccountType(input: AccountType | AddressType): AccountType {
|
||||
if (isAccountType(input)) {
|
||||
return input
|
||||
}
|
||||
return addressToAccountMap[input as AddressType] ?? AccountType.NONE
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user