mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
first draft
This commit is contained in:
parent
53b2fe33a0
commit
2ff7adef52
1
dlt-connector/@types/bip32-ed25519/index.d.ts
vendored
Normal file
1
dlt-connector/@types/bip32-ed25519/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'bip32-ed25519'
|
||||
@ -16,10 +16,11 @@
|
||||
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --forceExit --detectOpenHandles"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/protobufjs": "^1.2.7",
|
||||
"@apollo/server": "^4.7.5",
|
||||
"@apollo/utils.fetcher": "^3.0.0",
|
||||
"@iota/client": "^2.2.4",
|
||||
"bip32-ed25519": "^0.0.4",
|
||||
"bip39": "^3.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"class-validator": "^0.14.0",
|
||||
"cors": "^2.8.5",
|
||||
@ -32,6 +33,7 @@
|
||||
"graphql-scalars": "^1.22.2",
|
||||
"log4js": "^6.7.1",
|
||||
"nodemon": "^2.0.20",
|
||||
"protobufjs": "^7.2.5",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sodium-native": "^4.0.4",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { create as createCommunity, getAllTopics, isExist } from './Community'
|
||||
import { TestDB } from '@test/TestDB'
|
||||
import { getDataSource } from '@/typeorm/DataSource'
|
||||
import { Community } from '@entity/Community'
|
||||
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
|
||||
|
||||
jest.mock('@typeorm/DataSource', () => ({
|
||||
getDataSource: () => TestDB.instance.dbConnect,
|
||||
}))
|
||||
|
||||
describe('controller/Community', () => {
|
||||
beforeAll(async () => {
|
||||
await TestDB.instance.setupTestDB()
|
||||
// apolloTestServer = await createApolloTestServer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await TestDB.instance.teardownTestDB()
|
||||
})
|
||||
|
||||
describe('createCommunity', () => {
|
||||
it('valid community', async () => {
|
||||
const communityDraft = new CommunityDraft()
|
||||
communityDraft.foreign = false
|
||||
communityDraft.createdAt = '2022-05-01T17:00:12.128Z'
|
||||
communityDraft.uuid = '3d813cbb-47fb-32ba-91df-831e1593ac29'
|
||||
|
||||
const iotaTopic = iotaTopicFromCommunityUUID(communityDraft.uuid)
|
||||
expect(iotaTopic).toEqual('204ef6aed15fbf0f9da5819e88f8eea8e3adbe1e2c2d43280780a4b8c2d32b56')
|
||||
|
||||
const createdAtDate = new Date(communityDraft.createdAt)
|
||||
const communityEntity = createCommunity(communityDraft)
|
||||
expect(communityEntity).toMatchObject({
|
||||
iotaTopic,
|
||||
createdAt: createdAtDate,
|
||||
foreign: false,
|
||||
})
|
||||
await getDataSource().manager.save(communityEntity)
|
||||
})
|
||||
})
|
||||
|
||||
describe('list communities', () => {
|
||||
it('get all topics', async () => {
|
||||
expect(await getAllTopics()).toMatchObject([
|
||||
'204ef6aed15fbf0f9da5819e88f8eea8e3adbe1e2c2d43280780a4b8c2d32b56',
|
||||
])
|
||||
})
|
||||
|
||||
it('isExist with communityDraft', async () => {
|
||||
const communityDraft = new CommunityDraft()
|
||||
communityDraft.foreign = false
|
||||
communityDraft.createdAt = '2022-05-01T17:00:12.128Z'
|
||||
communityDraft.uuid = '3d813cbb-47fb-32ba-91df-831e1593ac29'
|
||||
expect(await isExist(communityDraft)).toBe(true)
|
||||
})
|
||||
|
||||
it('createdAt with ms precision', async () => {
|
||||
const list = await Community.findOne({ where: { foreign: false } })
|
||||
expect(list).toMatchObject({
|
||||
createdAt: new Date('2022-05-01T17:00:12.128Z'),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,28 +0,0 @@
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
|
||||
import { Community } from '@entity/Community'
|
||||
|
||||
export const isExist = async (community: CommunityDraft | string): Promise<boolean> => {
|
||||
const iotaTopic =
|
||||
community instanceof CommunityDraft ? iotaTopicFromCommunityUUID(community.uuid) : community
|
||||
const result = await Community.find({
|
||||
where: { iotaTopic },
|
||||
})
|
||||
return result.length > 0
|
||||
}
|
||||
|
||||
export const create = (community: CommunityDraft, topic?: string): Community => {
|
||||
const communityEntity = Community.create()
|
||||
communityEntity.iotaTopic = topic ?? iotaTopicFromCommunityUUID(community.uuid)
|
||||
communityEntity.createdAt = new Date(community.createdAt)
|
||||
communityEntity.foreign = community.foreign
|
||||
if (!community.foreign) {
|
||||
// TODO: generate keys
|
||||
}
|
||||
return communityEntity
|
||||
}
|
||||
|
||||
export const getAllTopics = async (): Promise<string[]> => {
|
||||
const communities = await Community.find({ select: { iotaTopic: true } })
|
||||
return communities.map((community) => community.iotaTopic)
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import { GradidoTransaction } from '@/proto/3_3/GradidoTransaction'
|
||||
import { TransactionBody } from '@/proto/3_3/TransactionBody'
|
||||
|
||||
export const create = (body: TransactionBody): GradidoTransaction => {
|
||||
const transaction = new GradidoTransaction({
|
||||
bodyBytes: Buffer.from(TransactionBody.encode(body).finish()),
|
||||
})
|
||||
// TODO: add correct signature(s)
|
||||
return transaction
|
||||
}
|
||||
22
dlt-connector/src/controller/KeyManager.test.ts
Normal file
22
dlt-connector/src/controller/KeyManager.test.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39'
|
||||
import { generateFromSeed, toPublic } from 'bip32-ed25519'
|
||||
|
||||
describe('controller/KeyManager', () => {
|
||||
describe('test crypto lib', () => {
|
||||
it('key length', () => {
|
||||
const mnemonic = entropyToMnemonic(
|
||||
'aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899',
|
||||
)
|
||||
expect(mnemonic).toEqual(
|
||||
'primary taxi danger target useless ancient match hammer fever crisp timber crew produce toy jeans that abandon math mimic master filter design carbon carbon',
|
||||
)
|
||||
const seed = mnemonicToSeedSync(mnemonic)
|
||||
// private key 64 Bytes + 32 Byte ChainCode
|
||||
const extendPrivkey = generateFromSeed(seed)
|
||||
expect(extendPrivkey).toHaveLength(96)
|
||||
// public key 32 Bytes + 32 Bytes ChainCode
|
||||
expect(toPublic(extendPrivkey)).toHaveLength(64)
|
||||
})
|
||||
})
|
||||
})
|
||||
123
dlt-connector/src/controller/KeyManager.ts
Normal file
123
dlt-connector/src/controller/KeyManager.ts
Normal file
@ -0,0 +1,123 @@
|
||||
// eslint-disable-next-line camelcase
|
||||
import { randombytes_buf } from 'sodium-native'
|
||||
import { CONFIG } from '../config'
|
||||
import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39'
|
||||
// https://www.npmjs.com/package/bip32-ed25519?activeTab=code
|
||||
import {
|
||||
generateFromSeed,
|
||||
derivePrivate,
|
||||
sign as ed25519Sign,
|
||||
verify as ed25519Verify,
|
||||
} from 'bip32-ed25519'
|
||||
import { logger } from '@/server/logger'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { KeyPair } from '@/data/KeyPair'
|
||||
import { GradidoTransaction } from '@/data/proto/3_3/GradidoTransaction'
|
||||
import { CommunityRepository } from '@/data/Community.repository'
|
||||
import { SignaturePair } from '@/data/proto/3_3/SignaturePair'
|
||||
|
||||
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
|
||||
// and ../federation/client/FederationClientFactory.ts
|
||||
/**
|
||||
* A Singleton class defines the `getInstance` method that lets clients access
|
||||
* the unique singleton instance.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||
export class KeyManager {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
private static instance: KeyManager
|
||||
private homeCommunityRootKeys: KeyPair | null = null
|
||||
|
||||
/**
|
||||
* The Singleton's constructor should always be private to prevent direct
|
||||
* construction calls with the `new` operator.
|
||||
*/
|
||||
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* The static method that controls the access to the singleton instance.
|
||||
*
|
||||
* This implementation let you subclass the Singleton class while keeping
|
||||
* just one instance of each subclass around.
|
||||
*/
|
||||
public static getInstance(): KeyManager {
|
||||
if (!KeyManager.instance) {
|
||||
KeyManager.instance = new KeyManager()
|
||||
}
|
||||
return KeyManager.instance
|
||||
}
|
||||
|
||||
public async init(): Promise<boolean> {
|
||||
try {
|
||||
this.homeCommunityRootKeys = await CommunityRepository.loadHomeCommunityKeyPair()
|
||||
return true
|
||||
} catch (error) {
|
||||
logger.error('error by init key manager', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public static generateKeyPair(): KeyPair {
|
||||
const mnemonic = KeyManager.generateMnemonic(CONFIG.IOTA_HOME_COMMUNITY_SEED ?? undefined)
|
||||
// logger.info('passphrase for key pair: ' + mnemonic)
|
||||
const seed = mnemonicToSeedSync(mnemonic)
|
||||
return new KeyPair(generateFromSeed(seed))
|
||||
}
|
||||
|
||||
public setHomeCommunityKeyPair(keyPair: KeyPair) {
|
||||
this.homeCommunityRootKeys = keyPair
|
||||
}
|
||||
|
||||
public sign(transaction: GradidoTransaction, keys?: KeyPair[]) {
|
||||
let localKeys: KeyPair[] = []
|
||||
|
||||
if (!keys && this.homeCommunityRootKeys) {
|
||||
localKeys.push(this.homeCommunityRootKeys)
|
||||
} else if (keys) {
|
||||
localKeys = keys
|
||||
}
|
||||
if (!localKeys.length) {
|
||||
throw new LogError('no key pair for signing')
|
||||
}
|
||||
localKeys.forEach((keyPair: KeyPair) => {
|
||||
const signature = ed25519Sign(transaction.bodyBytes, keyPair.getExtendPrivateKey())
|
||||
const sigPair = new SignaturePair({ pubKey: keyPair.publicKey, signature })
|
||||
logger.debug('sign transaction', {
|
||||
signature: signature.toString('hex'),
|
||||
publicKey: keyPair.publicKey.toString('hex'),
|
||||
bodyBytes: transaction.bodyBytes.toString('hex'),
|
||||
})
|
||||
transaction.sigMap.sigPair.push(sigPair)
|
||||
})
|
||||
}
|
||||
|
||||
public getHomeCommunityPublicKey(): Buffer | undefined {
|
||||
if (!this.homeCommunityRootKeys) return undefined
|
||||
return this.homeCommunityRootKeys.publicKey
|
||||
}
|
||||
|
||||
public derive(path: number[], parentKeys?: KeyPair): KeyPair {
|
||||
const extendedPrivateKey = parentKeys
|
||||
? parentKeys.getExtendPrivateKey()
|
||||
: this.homeCommunityRootKeys?.getExtendPrivateKey()
|
||||
if (!extendedPrivateKey) {
|
||||
throw new LogError('missing parent or root key pair')
|
||||
}
|
||||
return new KeyPair(
|
||||
path.reduce(
|
||||
(extendPrivateKey: Buffer, node: number) => derivePrivate(extendPrivateKey, node),
|
||||
extendedPrivateKey,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
static generateMnemonic(seed?: Buffer | string): string {
|
||||
if (seed) {
|
||||
return entropyToMnemonic(seed)
|
||||
}
|
||||
const entropy = Buffer.alloc(256)
|
||||
randombytes_buf(entropy)
|
||||
return entropyToMnemonic(entropy)
|
||||
}
|
||||
}
|
||||
@ -1,162 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { create, determineCrossGroupType, determineOtherGroup } from './TransactionBody'
|
||||
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { CrossGroupType } from '@/graphql/enum/CrossGroupType'
|
||||
import { TransactionType } from '@/graphql/enum/TransactionType'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
describe('test controller/TransactionBody', () => {
|
||||
describe('test create ', () => {
|
||||
const senderUser = new UserIdentifier()
|
||||
const recipientUser = new UserIdentifier()
|
||||
it('test with contribution transaction', () => {
|
||||
const transactionDraft = new TransactionDraft()
|
||||
transactionDraft.senderUser = senderUser
|
||||
transactionDraft.recipientUser = recipientUser
|
||||
transactionDraft.type = TransactionType.CREATION
|
||||
transactionDraft.amount = new Decimal(1000)
|
||||
transactionDraft.createdAt = '2022-01-02T19:10:34.121'
|
||||
transactionDraft.targetDate = '2021-12-01T10:05:00.191'
|
||||
const body = create(transactionDraft)
|
||||
|
||||
expect(body.creation).toBeDefined()
|
||||
expect(body).toMatchObject({
|
||||
createdAt: {
|
||||
seconds: 1641150634,
|
||||
nanoSeconds: 121000000,
|
||||
},
|
||||
versionNumber: '3.3',
|
||||
type: CrossGroupType.LOCAL,
|
||||
otherGroup: '',
|
||||
creation: {
|
||||
recipient: {
|
||||
amount: '1000',
|
||||
},
|
||||
targetDate: {
|
||||
seconds: 1638353100,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
it('test with local send transaction send part', () => {
|
||||
const transactionDraft = new TransactionDraft()
|
||||
transactionDraft.senderUser = senderUser
|
||||
transactionDraft.recipientUser = recipientUser
|
||||
transactionDraft.type = TransactionType.SEND
|
||||
transactionDraft.amount = new Decimal(1000)
|
||||
transactionDraft.createdAt = '2022-01-02T19:10:34.121'
|
||||
const body = create(transactionDraft)
|
||||
|
||||
expect(body.transfer).toBeDefined()
|
||||
expect(body).toMatchObject({
|
||||
createdAt: {
|
||||
seconds: 1641150634,
|
||||
nanoSeconds: 121000000,
|
||||
},
|
||||
versionNumber: '3.3',
|
||||
type: CrossGroupType.LOCAL,
|
||||
otherGroup: '',
|
||||
transfer: {
|
||||
sender: {
|
||||
amount: '1000',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('test with local send transaction receive part', () => {
|
||||
const transactionDraft = new TransactionDraft()
|
||||
transactionDraft.senderUser = senderUser
|
||||
transactionDraft.recipientUser = recipientUser
|
||||
transactionDraft.type = TransactionType.RECEIVE
|
||||
transactionDraft.amount = new Decimal(1000)
|
||||
transactionDraft.createdAt = '2022-01-02T19:10:34.121'
|
||||
const body = create(transactionDraft)
|
||||
|
||||
expect(body.transfer).toBeDefined()
|
||||
expect(body).toMatchObject({
|
||||
createdAt: {
|
||||
seconds: 1641150634,
|
||||
nanoSeconds: 121000000,
|
||||
},
|
||||
versionNumber: '3.3',
|
||||
type: CrossGroupType.LOCAL,
|
||||
otherGroup: '',
|
||||
transfer: {
|
||||
sender: {
|
||||
amount: '1000',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('test determineCrossGroupType', () => {
|
||||
const transactionDraft = new TransactionDraft()
|
||||
transactionDraft.senderUser = new UserIdentifier()
|
||||
transactionDraft.recipientUser = new UserIdentifier()
|
||||
|
||||
it('local transaction', () => {
|
||||
expect(determineCrossGroupType(transactionDraft)).toEqual(CrossGroupType.LOCAL)
|
||||
})
|
||||
|
||||
it('test with with invalid input', () => {
|
||||
transactionDraft.recipientUser.communityUuid = 'a72a4a4a-aa12-4f6c-b3d8-7cc65c67e24a'
|
||||
expect(() => determineCrossGroupType(transactionDraft)).toThrow(
|
||||
new TransactionError(
|
||||
TransactionErrorType.NOT_IMPLEMENTED_YET,
|
||||
'cannot determine CrossGroupType',
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
it('inbound transaction (send to sender community)', () => {
|
||||
transactionDraft.type = TransactionType.SEND
|
||||
expect(determineCrossGroupType(transactionDraft)).toEqual(CrossGroupType.INBOUND)
|
||||
})
|
||||
|
||||
it('outbound transaction (send to recipient community)', () => {
|
||||
transactionDraft.type = TransactionType.RECEIVE
|
||||
expect(determineCrossGroupType(transactionDraft)).toEqual(CrossGroupType.OUTBOUND)
|
||||
})
|
||||
})
|
||||
|
||||
describe('test determineOtherGroup', () => {
|
||||
const transactionDraft = new TransactionDraft()
|
||||
transactionDraft.senderUser = new UserIdentifier()
|
||||
transactionDraft.recipientUser = new UserIdentifier()
|
||||
|
||||
it('for inbound transaction, other group is from recipient, missing community id for recipient', () => {
|
||||
expect(() => determineOtherGroup(CrossGroupType.INBOUND, transactionDraft)).toThrowError(
|
||||
new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'missing recipient user community id for cross group transaction',
|
||||
),
|
||||
)
|
||||
})
|
||||
it('for inbound transaction, other group is from recipient', () => {
|
||||
transactionDraft.recipientUser.communityUuid = 'b8e9f00a-5a56-4b23-8c44-6823ac9e0d2d'
|
||||
expect(determineOtherGroup(CrossGroupType.INBOUND, transactionDraft)).toEqual(
|
||||
'b8e9f00a-5a56-4b23-8c44-6823ac9e0d2d',
|
||||
)
|
||||
})
|
||||
|
||||
it('for outbound transaction, other group is from sender, missing community id for sender', () => {
|
||||
expect(() => determineOtherGroup(CrossGroupType.OUTBOUND, transactionDraft)).toThrowError(
|
||||
new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'missing sender user community id for cross group transaction',
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
it('for outbound transaction, other group is from sender', () => {
|
||||
transactionDraft.senderUser.communityUuid = 'a72a4a4a-aa12-4f6c-b3d8-7cc65c67e24a'
|
||||
expect(determineOtherGroup(CrossGroupType.OUTBOUND, transactionDraft)).toEqual(
|
||||
'a72a4a4a-aa12-4f6c-b3d8-7cc65c67e24a',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
33
dlt-connector/src/data/Account.repository.ts
Normal file
33
dlt-connector/src/data/Account.repository.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
|
||||
import { getDataSource } from '@/typeorm/DataSource'
|
||||
import { Account } from '@entity/Account'
|
||||
import { User } from '@entity/User'
|
||||
import { In } from 'typeorm'
|
||||
|
||||
export const AccountRepository = getDataSource()
|
||||
.getRepository(Account)
|
||||
.extend({
|
||||
findAccountsByPublicKeys(publicKeys: Buffer[]): Promise<Account[]> {
|
||||
return Account.findBy({ derive2Pubkey: In(publicKeys) })
|
||||
},
|
||||
|
||||
async findAccountByPublicKey(publicKey: Buffer | undefined): Promise<Account | undefined> {
|
||||
if (!publicKey) return undefined
|
||||
return (await Account.findOneBy({ derive2Pubkey: Buffer.from(publicKey) })) ?? undefined
|
||||
},
|
||||
|
||||
async findAccountByUserIdentifier({
|
||||
uuid,
|
||||
accountNr,
|
||||
}: UserIdentifier): Promise<Account | undefined> {
|
||||
const user = await User.findOne({
|
||||
where: { gradidoID: uuid, accounts: { derivationIndex: accountNr ?? 1 } },
|
||||
relations: { accounts: true },
|
||||
})
|
||||
if (user && user.accounts?.length === 1) {
|
||||
const account = user.accounts[0]
|
||||
account.user = user
|
||||
return account
|
||||
}
|
||||
},
|
||||
})
|
||||
74
dlt-connector/src/data/Community.repository.ts
Normal file
74
dlt-connector/src/data/Community.repository.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { CommunityArg } from '@/graphql/arg/CommunityArg'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { getDataSource } from '@/typeorm/DataSource'
|
||||
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
|
||||
import { Community } from '@entity/Community'
|
||||
import { FindOptionsSelect, In, IsNull, Not } from 'typeorm'
|
||||
import { KeyPair } from './KeyPair'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
export const CommunityRepository = getDataSource()
|
||||
.getRepository(Community)
|
||||
.extend({
|
||||
async isExist(community: CommunityDraft | string): Promise<boolean> {
|
||||
const iotaTopic =
|
||||
community instanceof CommunityDraft ? iotaTopicFromCommunityUUID(community.uuid) : community
|
||||
const result = await Community.find({
|
||||
where: { iotaTopic },
|
||||
})
|
||||
return result.length > 0
|
||||
},
|
||||
|
||||
async findByCommunityArg({ uuid, foreign, confirmed }: CommunityArg): Promise<Community[]> {
|
||||
return await Community.find({
|
||||
where: {
|
||||
...(uuid && { iotaTopic: iotaTopicFromCommunityUUID(uuid) }),
|
||||
...(foreign && { foreign }),
|
||||
...(confirmed && { confirmedAt: Not(IsNull()) }),
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
async findByCommunityUuid(communityUuid: string): Promise<Community | null> {
|
||||
return await Community.findOneBy({ iotaTopic: iotaTopicFromCommunityUUID(communityUuid) })
|
||||
},
|
||||
|
||||
async findByIotaTopic(iotaTopic: string): Promise<Community | null> {
|
||||
return await Community.findOneBy({ iotaTopic })
|
||||
},
|
||||
|
||||
findCommunitiesByTopics(topics: string[]): Promise<Community[]> {
|
||||
return Community.findBy({ iotaTopic: In(topics) })
|
||||
},
|
||||
|
||||
async getCommunityForUserIdentifier(
|
||||
identifier: UserIdentifier,
|
||||
): Promise<Community | undefined> {
|
||||
if (!identifier.communityUuid) {
|
||||
throw new TransactionError(TransactionErrorType.MISSING_PARAMETER, 'community uuid not set')
|
||||
}
|
||||
return (
|
||||
(await Community.findOneBy({
|
||||
iotaTopic: iotaTopicFromCommunityUUID(identifier.communityUuid),
|
||||
})) ?? undefined
|
||||
)
|
||||
},
|
||||
|
||||
findAll(select: FindOptionsSelect<Community>): Promise<Community[]> {
|
||||
return Community.find({ select })
|
||||
},
|
||||
|
||||
async loadHomeCommunityKeyPair(): Promise<KeyPair> {
|
||||
const community = await Community.findOneOrFail({
|
||||
where: { foreign: false },
|
||||
select: { rootChaincode: true, rootPubkey: true, rootPrivkey: true },
|
||||
})
|
||||
if (!community.rootChaincode || !community.rootPrivkey) {
|
||||
throw new LogError('Missing chaincode or private key for home community')
|
||||
}
|
||||
return new KeyPair(community)
|
||||
},
|
||||
})
|
||||
38
dlt-connector/src/data/KeyPair.ts
Normal file
38
dlt-connector/src/data/KeyPair.ts
Normal file
@ -0,0 +1,38 @@
|
||||
// https://www.npmjs.com/package/bip32-ed25519?activeTab=code
|
||||
import { toPublic } from 'bip32-ed25519'
|
||||
import { Community } from '@entity/Community'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
export class KeyPair {
|
||||
/**
|
||||
* @param input: Buffer = extended private key, returned from bip32-ed25519 generateFromSeed
|
||||
* @param input: Community = community entity with keys loaded from db
|
||||
*
|
||||
*/
|
||||
public constructor(input: Buffer | Community) {
|
||||
if (input instanceof Buffer) {
|
||||
this.privateKey = input.subarray(0, 64)
|
||||
this.chainCode = input.subarray(64, 96)
|
||||
this.publicKey = toPublic(input).subarray(0, 32)
|
||||
} else if (input instanceof Community) {
|
||||
if (!input.rootPrivkey || !input.rootChaincode || !input.rootPubkey) {
|
||||
throw new LogError('missing private key or chaincode or public key in commmunity entity')
|
||||
}
|
||||
this.privateKey = input.rootPrivkey
|
||||
this.publicKey = input.rootPubkey
|
||||
this.chainCode = input.rootChaincode
|
||||
}
|
||||
}
|
||||
|
||||
public getExtendPrivateKey(): Buffer {
|
||||
return Buffer.concat([this.privateKey, this.chainCode])
|
||||
}
|
||||
|
||||
public getExtendPublicKey(): Buffer {
|
||||
return Buffer.concat([this.publicKey, this.chainCode])
|
||||
}
|
||||
|
||||
publicKey: Buffer
|
||||
chainCode: Buffer
|
||||
privateKey: Buffer
|
||||
}
|
||||
147
dlt-connector/src/data/Transaction.builder.ts
Normal file
147
dlt-connector/src/data/Transaction.builder.ts
Normal file
@ -0,0 +1,147 @@
|
||||
import { GradidoTransaction } from '@/data/proto/3_3/GradidoTransaction'
|
||||
import { TransactionBody } from '@/data/proto/3_3/TransactionBody'
|
||||
import { bodyBytesToTransactionBody, transactionBodyToBodyBytes } from '@/utils/typeConverter'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { AccountRepository } from './Account.repository'
|
||||
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
|
||||
import { CommunityRepository } from './Community.repository'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { Account } from '@entity/Account'
|
||||
import { Community } from '@entity/Community'
|
||||
|
||||
export class TransactionBuilder {
|
||||
private transaction: Transaction
|
||||
|
||||
// https://refactoring.guru/design-patterns/builder/typescript/example
|
||||
/**
|
||||
* A fresh builder instance should contain a blank product object, which is
|
||||
* used in further assembly.
|
||||
*/
|
||||
constructor() {
|
||||
this.reset()
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.transaction = Transaction.create()
|
||||
}
|
||||
|
||||
/**
|
||||
* Concrete Builders are supposed to provide their own methods for
|
||||
* retrieving results. That's because various types of builders may create
|
||||
* entirely different products that don't follow the same interface.
|
||||
* Therefore, such methods cannot be declared in the base Builder interface
|
||||
* (at least in a statically typed programming language).
|
||||
*
|
||||
* Usually, after returning the end result to the client, a builder instance
|
||||
* is expected to be ready to start producing another product. That's why
|
||||
* it's a usual practice to call the reset method at the end of the
|
||||
* `getProduct` method body. However, this behavior is not mandatory, and
|
||||
* you can make your builders wait for an explicit reset call from the
|
||||
* client code before disposing of the previous result.
|
||||
*/
|
||||
public build(): Transaction {
|
||||
const result = this.transaction
|
||||
this.reset()
|
||||
return result
|
||||
}
|
||||
|
||||
// return transaction without calling reset
|
||||
public getTransaction(): Transaction {
|
||||
return this.transaction
|
||||
}
|
||||
|
||||
public setSigningAccount(signingAccount: Account): TransactionBuilder {
|
||||
this.transaction.signingAccount = signingAccount
|
||||
return this
|
||||
}
|
||||
|
||||
public setRecipientAccount(recipientAccount: Account): TransactionBuilder {
|
||||
this.transaction.recipientAccount = recipientAccount
|
||||
return this
|
||||
}
|
||||
|
||||
public setSenderCommunity(senderCommunity: Community): TransactionBuilder {
|
||||
this.transaction.senderCommunity = senderCommunity
|
||||
return this
|
||||
}
|
||||
|
||||
public setRecipientCommunity(recipientCommunity?: Community): TransactionBuilder {
|
||||
if (!this.transaction.senderCommunity) {
|
||||
throw new LogError('Please set sender community first!')
|
||||
}
|
||||
|
||||
this.transaction.recipientCommunity =
|
||||
recipientCommunity &&
|
||||
this.transaction.senderCommunity &&
|
||||
this.transaction.senderCommunity.id !== recipientCommunity.id
|
||||
? recipientCommunity
|
||||
: undefined
|
||||
return this
|
||||
}
|
||||
|
||||
public setSignature(signature: Buffer): TransactionBuilder {
|
||||
this.transaction.signature = signature
|
||||
return this
|
||||
}
|
||||
|
||||
public async setSenderCommunityFromSenderUser(
|
||||
senderUser: UserIdentifier,
|
||||
): Promise<TransactionBuilder> {
|
||||
// get sender community
|
||||
const senderCommunity = await CommunityRepository.getCommunityForUserIdentifier(senderUser)
|
||||
if (!senderCommunity) {
|
||||
throw new LogError("couldn't find sender community for transaction")
|
||||
}
|
||||
return this.setSenderCommunity(senderCommunity)
|
||||
}
|
||||
|
||||
public async setRecipientCommunityFromRecipientUser(
|
||||
recipientUser: UserIdentifier,
|
||||
): Promise<TransactionBuilder> {
|
||||
// get recipient community
|
||||
const recipientCommunity = await CommunityRepository.getCommunityForUserIdentifier(
|
||||
recipientUser,
|
||||
)
|
||||
return this.setRecipientCommunity(recipientCommunity)
|
||||
}
|
||||
|
||||
public async fromGradidoTransactionSearchForAccounts(
|
||||
gradidoTransaction: GradidoTransaction,
|
||||
): Promise<TransactionBuilder> {
|
||||
this.transaction.bodyBytes = Buffer.from(gradidoTransaction.bodyBytes)
|
||||
const transactionBody = bodyBytesToTransactionBody(this.transaction.bodyBytes)
|
||||
this.fromTransactionBody(transactionBody)
|
||||
|
||||
const firstSigPair = gradidoTransaction.getFirstSignature()
|
||||
// TODO: adapt if transactions with more than one signatures where added
|
||||
|
||||
// get recipient and signer accounts if not already set
|
||||
this.transaction.signingAccount ??= await AccountRepository.findAccountByPublicKey(
|
||||
firstSigPair.pubKey,
|
||||
)
|
||||
this.transaction.recipientAccount ??= await AccountRepository.findAccountByPublicKey(
|
||||
transactionBody.getRecipientPublicKey(),
|
||||
)
|
||||
this.transaction.signature = Buffer.from(firstSigPair.signature)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public fromGradidoTransaction(gradidoTransaction: GradidoTransaction): TransactionBuilder {
|
||||
this.transaction.bodyBytes = Buffer.from(gradidoTransaction.bodyBytes)
|
||||
const transactionBody = bodyBytesToTransactionBody(this.transaction.bodyBytes)
|
||||
this.fromTransactionBody(transactionBody)
|
||||
|
||||
const firstSigPair = gradidoTransaction.getFirstSignature()
|
||||
// TODO: adapt if transactions with more than one signatures where added
|
||||
this.transaction.signature = Buffer.from(firstSigPair.signature)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public fromTransactionBody(transactionBody: TransactionBody): TransactionBuilder {
|
||||
transactionBody.fillTransactionRecipe(this.transaction)
|
||||
this.transaction.bodyBytes ??= transactionBodyToBodyBytes(transactionBody)
|
||||
return this
|
||||
}
|
||||
}
|
||||
48
dlt-connector/src/data/Transaction.repository.ts
Normal file
48
dlt-connector/src/data/Transaction.repository.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { getDataSource } from '@/typeorm/DataSource'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { IsNull } from 'typeorm'
|
||||
|
||||
// https://www.artima.com/articles/the-dci-architecture-a-new-vision-of-object-oriented-programming
|
||||
export const TransactionRepository = getDataSource()
|
||||
.getRepository(Transaction)
|
||||
.extend({
|
||||
findBySignature(signature: Buffer): Promise<Transaction | null> {
|
||||
return this.findOneBy({ signature: Buffer.from(signature) })
|
||||
},
|
||||
findByMessageId(iotaMessageId: string): Promise<Transaction | null> {
|
||||
return this.findOneBy({ iotaMessageId: Buffer.from(iotaMessageId, 'hex') })
|
||||
},
|
||||
async getNextPendingTransaction(): Promise<Transaction | null> {
|
||||
return await this.findOne({
|
||||
where: { iotaMessageId: IsNull() },
|
||||
order: { createdAt: 'ASC' },
|
||||
relations: { signingAccount: true },
|
||||
})
|
||||
},
|
||||
async findExistingTransactionAndMissingMessageIds(messageIDsHex: string[]): Promise<{
|
||||
existingTransactions: Transaction[]
|
||||
missingMessageIdsHex: string[]
|
||||
}> {
|
||||
const existingTransactions = await this.createQueryBuilder('Transaction')
|
||||
.where('HEX(Transaction.iota_message_id) IN (:...messageIDs)', {
|
||||
messageIDs: messageIDsHex,
|
||||
})
|
||||
.leftJoinAndSelect('Transaction.recipientAccount', 'RecipientAccount')
|
||||
.leftJoinAndSelect('RecipientAccount.user', 'RecipientUser')
|
||||
.leftJoinAndSelect('Transaction.signingAccount', 'SigningAccount')
|
||||
.leftJoinAndSelect('SigningAccount.user', 'SigningUser')
|
||||
.getMany()
|
||||
|
||||
const foundMessageIds = existingTransactions
|
||||
.map((recipe) => recipe.iotaMessageId?.toString('hex'))
|
||||
.filter((messageId) => !!messageId)
|
||||
// find message ids for which we don't already have a transaction recipe
|
||||
const missingMessageIdsHex = messageIDsHex.filter(
|
||||
(id: string) => !foundMessageIds.includes(id),
|
||||
)
|
||||
return { existingTransactions, missingMessageIdsHex }
|
||||
},
|
||||
async removeConfirmedTransaction(transactions: Transaction[]): Promise<Transaction[]> {
|
||||
return transactions.filter((transaction: Transaction) => transaction.runningHash.length === 0)
|
||||
},
|
||||
})
|
||||
42
dlt-connector/src/data/account.factory.ts
Normal file
42
dlt-connector/src/data/account.factory.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { KeyManager } from '@/controller/KeyManager'
|
||||
import { KeyPair } from '@/data/KeyPair'
|
||||
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
|
||||
import { hardenDerivationIndex } from '@/utils/derivationHelper'
|
||||
import { Account } from '@entity/Account'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
const GMW_ACCOUNT_DERIVATION_INDEX = 1
|
||||
const AUF_ACCOUNT_DERIVATION_INDEX = 2
|
||||
|
||||
export const createAccount = (
|
||||
keyPair: KeyPair,
|
||||
createdAt: Date,
|
||||
derivationIndex: number,
|
||||
type: AddressType,
|
||||
): Account => {
|
||||
const account = Account.create()
|
||||
account.derivationIndex = derivationIndex
|
||||
account.derive2Pubkey = KeyManager.getInstance().derive([derivationIndex], keyPair).publicKey
|
||||
account.type = type.valueOf()
|
||||
account.createdAt = createdAt
|
||||
account.balance = new Decimal(0)
|
||||
return account
|
||||
}
|
||||
|
||||
export const createGmwAccount = (keyPair: KeyPair, createdAt: Date): Account => {
|
||||
return createAccount(
|
||||
keyPair,
|
||||
createdAt,
|
||||
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
|
||||
AddressType.COMMUNITY_GMW,
|
||||
)
|
||||
}
|
||||
|
||||
export const createAufAccount = (keyPair: KeyPair, createdAt: Date): Account => {
|
||||
return createAccount(
|
||||
keyPair,
|
||||
createdAt,
|
||||
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
|
||||
AddressType.COMMUNITY_AUF,
|
||||
)
|
||||
}
|
||||
31
dlt-connector/src/data/community.factory.ts
Normal file
31
dlt-connector/src/data/community.factory.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { KeyManager } from '@/controller/KeyManager'
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
|
||||
import { Community } from '@entity/Community'
|
||||
import { createAufAccount, createGmwAccount } from './account.factory'
|
||||
|
||||
export const createCommunity = (communityDraft: CommunityDraft, topic?: string): Community => {
|
||||
const communityEntity = Community.create()
|
||||
communityEntity.iotaTopic = topic ?? iotaTopicFromCommunityUUID(communityDraft.uuid)
|
||||
communityEntity.createdAt = new Date(communityDraft.createdAt)
|
||||
communityEntity.foreign = communityDraft.foreign
|
||||
return communityEntity
|
||||
}
|
||||
|
||||
export const createHomeCommunity = (communityDraft: CommunityDraft, topic?: string): Community => {
|
||||
// create community entity
|
||||
const community = createCommunity(communityDraft, topic)
|
||||
|
||||
// generate key pair for signing transactions and deriving all keys for community
|
||||
const keyPair = KeyManager.generateKeyPair()
|
||||
community.rootPubkey = keyPair.publicKey
|
||||
community.rootPrivkey = keyPair.privateKey
|
||||
community.rootChaincode = keyPair.chainCode
|
||||
// we should only have one home community per server
|
||||
KeyManager.getInstance().setHomeCommunityKeyPair(keyPair)
|
||||
|
||||
// create auf account and gmw account
|
||||
community.aufAccount = createAufAccount(keyPair, community.createdAt)
|
||||
community.gmwAccount = createGmwAccount(keyPair, community.createdAt)
|
||||
return community
|
||||
}
|
||||
40
dlt-connector/src/data/proto/3_3/CommunityRoot.ts
Normal file
40
dlt-connector/src/data/proto/3_3/CommunityRoot.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { TransactionBase } from '../TransactionBase'
|
||||
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
|
||||
import { Field, Message } from 'protobufjs'
|
||||
import { Community } from '@entity/Community'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class CommunityRoot extends Message<CommunityRoot> implements TransactionBase {
|
||||
public constructor(community?: Community) {
|
||||
if (community) {
|
||||
super({
|
||||
rootPubkey: community.rootPubkey,
|
||||
gmwPubkey: community.gmwAccount?.derive2Pubkey,
|
||||
aufPubkey: community.aufAccount?.derive2Pubkey,
|
||||
})
|
||||
} else {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
@Field.d(1, 'bytes')
|
||||
public rootPubkey: Buffer
|
||||
|
||||
// community public budget account
|
||||
@Field.d(2, 'bytes')
|
||||
public gmwPubkey: Buffer
|
||||
|
||||
// community compensation and environment founds account
|
||||
@Field.d(3, 'bytes')
|
||||
public aufPubkey: Buffer
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public validate(_level: TransactionValidationLevel): boolean {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
||||
public fillTransactionRecipe(recipe: Transaction): void {}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
import { Field, Message } from 'protobufjs'
|
||||
import { GradidoTransaction } from './GradidoTransaction'
|
||||
import { TimestampSeconds } from './TimestampSeconds'
|
||||
import { base64ToBuffer } from '@/utils/typeConverter'
|
||||
import Long from 'long'
|
||||
|
||||
/*
|
||||
id will be set by Node server
|
||||
@ -10,9 +12,13 @@ import { TimestampSeconds } from './TimestampSeconds'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class GradidoConfirmedTransaction extends Message<GradidoConfirmedTransaction> {
|
||||
export class ConfirmedTransaction extends Message<ConfirmedTransaction> {
|
||||
static fromBase64(base64: string): ConfirmedTransaction {
|
||||
return ConfirmedTransaction.decode(new Uint8Array(base64ToBuffer(base64)))
|
||||
}
|
||||
|
||||
@Field.d(1, 'uint64')
|
||||
id: number
|
||||
id: Long
|
||||
|
||||
@Field.d(2, 'GradidoTransaction')
|
||||
transaction: GradidoTransaction
|
||||
53
dlt-connector/src/data/proto/3_3/GradidoCreation.ts
Normal file
53
dlt-connector/src/data/proto/3_3/GradidoCreation.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
import { TimestampSeconds } from './TimestampSeconds'
|
||||
import { TransferAmount } from './TransferAmount'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { TransactionBase } from '../TransactionBase'
|
||||
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { Account } from '@entity/Account'
|
||||
|
||||
// need signature from group admin or
|
||||
// percent of group users another than the receiver
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class GradidoCreation extends Message<GradidoCreation> implements TransactionBase {
|
||||
constructor(transaction?: TransactionDraft, recipientAccount?: Account) {
|
||||
if (transaction) {
|
||||
if (!transaction.targetDate) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'missing targetDate for contribution',
|
||||
)
|
||||
}
|
||||
super({
|
||||
recipient: new TransferAmount({
|
||||
amount: transaction.amount.toString(),
|
||||
pubkey: recipientAccount?.derive2Pubkey,
|
||||
}),
|
||||
targetDate: new TimestampSeconds(new Date(transaction.targetDate)),
|
||||
})
|
||||
} else {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
@Field.d(1, TransferAmount)
|
||||
public recipient: TransferAmount
|
||||
|
||||
@Field.d(3, 'TimestampSeconds')
|
||||
public targetDate: TimestampSeconds
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public validate(level: TransactionValidationLevel): boolean {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
public fillTransactionRecipe(recipe: Transaction): void {
|
||||
recipe.amount = new Decimal(this.recipient.amount ?? 0)
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,11 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
import { GradidoTransfer } from './GradidoTransfer'
|
||||
import { TimestampSeconds } from './TimestampSeconds'
|
||||
import { TransactionBase } from '../TransactionBase'
|
||||
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
// transaction type for chargeable transactions
|
||||
// for transaction for people which haven't a account already
|
||||
@ -10,8 +14,11 @@ import { TimestampSeconds } from './TimestampSeconds'
|
||||
// seed must be long enough to prevent brute force, maybe base64 encoded
|
||||
// to own account
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class GradidoDeferredTransfer extends Message<GradidoDeferredTransfer> {
|
||||
export class GradidoDeferredTransfer
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
extends Message<GradidoDeferredTransfer>
|
||||
implements TransactionBase
|
||||
{
|
||||
// amount is amount with decay for time span between transaction was received and timeout
|
||||
// useable amount can be calculated
|
||||
// recipient address don't need to be registered in blockchain with register address
|
||||
@ -28,4 +35,13 @@ export class GradidoDeferredTransfer extends Message<GradidoDeferredTransfer> {
|
||||
|
||||
// split for n recipient
|
||||
// max gradido per recipient? or per transaction with cool down?
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public validate(level: TransactionValidationLevel): boolean {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
public fillTransactionRecipe(recipe: Transaction): void {
|
||||
recipe.amount = new Decimal(this.transfer.sender.amount ?? 0)
|
||||
}
|
||||
}
|
||||
43
dlt-connector/src/data/proto/3_3/GradidoTransaction.ts
Normal file
43
dlt-connector/src/data/proto/3_3/GradidoTransaction.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
import { SignatureMap } from './SignatureMap'
|
||||
import { TransactionBody } from './TransactionBody'
|
||||
import { SignaturePair } from './SignaturePair'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class GradidoTransaction extends Message<GradidoTransaction> {
|
||||
constructor(body?: TransactionBody) {
|
||||
if (body) {
|
||||
super({
|
||||
sigMap: new SignatureMap(),
|
||||
bodyBytes: Buffer.from(TransactionBody.encode(body).finish()),
|
||||
})
|
||||
} else {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
@Field.d(1, SignatureMap)
|
||||
public sigMap: SignatureMap
|
||||
|
||||
// inspired by Hedera
|
||||
// bodyBytes are the payload for signature
|
||||
// bodyBytes are serialized TransactionBody
|
||||
@Field.d(2, 'bytes')
|
||||
public bodyBytes: Buffer
|
||||
|
||||
// if it is a cross group transaction the parent message
|
||||
// id from outbound transaction or other by cross
|
||||
@Field.d(3, 'bytes')
|
||||
public parentMessageId?: Buffer
|
||||
|
||||
getFirstSignature(): SignaturePair {
|
||||
const sigPair = this.sigMap.sigPair
|
||||
if (sigPair.length !== 1) {
|
||||
throw new LogError("signature count don't like expected")
|
||||
}
|
||||
return sigPair[0]
|
||||
}
|
||||
}
|
||||
48
dlt-connector/src/data/proto/3_3/GradidoTransfer.ts
Normal file
48
dlt-connector/src/data/proto/3_3/GradidoTransfer.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
import { TransferAmount } from './TransferAmount'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { TransactionBase } from '../TransactionBase'
|
||||
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { Account } from '@entity/Account'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class GradidoTransfer extends Message<GradidoTransfer> implements TransactionBase {
|
||||
constructor(
|
||||
transaction?: TransactionDraft,
|
||||
signingAccount?: Account,
|
||||
recipientAccount?: Account,
|
||||
coinOrigin?: string,
|
||||
) {
|
||||
if (transaction) {
|
||||
super({
|
||||
sender: new TransferAmount({
|
||||
amount: transaction.amount.toString(),
|
||||
pubkey: signingAccount?.derive2Pubkey,
|
||||
communityId: coinOrigin,
|
||||
}),
|
||||
recipient: recipientAccount?.derive2Pubkey,
|
||||
})
|
||||
} else {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
@Field.d(1, TransferAmount)
|
||||
public sender: TransferAmount
|
||||
|
||||
@Field.d(2, 'bytes')
|
||||
public recipient: Buffer
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public validate(level: TransactionValidationLevel): boolean {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
public fillTransactionRecipe(recipe: Transaction): void {
|
||||
recipe.amount = new Decimal(this.sender?.amount ?? 0)
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,14 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { TransactionBase } from '../TransactionBase'
|
||||
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
|
||||
import { Field, Message } from 'protobufjs'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
|
||||
// connect group together
|
||||
// only CrossGroupType CROSS (in TransactionBody)
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class GroupFriendsUpdate extends Message<GroupFriendsUpdate> {
|
||||
export class GroupFriendsUpdate extends Message<GroupFriendsUpdate> implements TransactionBase {
|
||||
// if set to true, colors of this both groups are trait as the same
|
||||
// on creation user get coins still in there color
|
||||
// on transfer into another group which a connection exist,
|
||||
@ -12,4 +16,12 @@ export class GroupFriendsUpdate extends Message<GroupFriendsUpdate> {
|
||||
// (if fusion between src coin and dst coin is enabled)
|
||||
@Field.d(1, 'bool')
|
||||
public colorFusion: boolean
|
||||
|
||||
public validate(level: TransactionValidationLevel): boolean {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
public fillTransactionRecipe(recipe: Transaction): void {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
}
|
||||
33
dlt-connector/src/data/proto/3_3/RegisterAddress.ts
Normal file
33
dlt-connector/src/data/proto/3_3/RegisterAddress.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
|
||||
import { TransactionBase } from '../TransactionBase'
|
||||
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class RegisterAddress extends Message<RegisterAddress> implements TransactionBase {
|
||||
@Field.d(1, 'bytes')
|
||||
public userPubkey: Buffer
|
||||
|
||||
@Field.d(2, AddressType)
|
||||
public addressType: AddressType
|
||||
|
||||
@Field.d(3, 'bytes')
|
||||
public nameHash: Buffer
|
||||
|
||||
@Field.d(4, 'bytes')
|
||||
public accountPubkey: Buffer
|
||||
|
||||
@Field.d(5, 'uint32')
|
||||
public derivationIndex?: number
|
||||
|
||||
public validate(level: TransactionValidationLevel): boolean {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
public fillTransactionRecipe(_recipe: Transaction): void {}
|
||||
}
|
||||
@ -1,10 +1,14 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
import { SignaturePair } from './SignaturePair'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class SignatureMap extends Message<SignatureMap> {
|
||||
constructor() {
|
||||
super({ sigPair: [] })
|
||||
}
|
||||
|
||||
@Field.d(1, SignaturePair, 'repeated')
|
||||
public sigPair: SignaturePair
|
||||
public sigPair: SignaturePair[]
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
@ -8,4 +8,8 @@ export class SignaturePair extends Message<SignaturePair> {
|
||||
|
||||
@Field.d(2, 'bytes')
|
||||
public signature: Buffer
|
||||
|
||||
public validate(): boolean {
|
||||
return this.pubKey.length === 32 && this.signature.length === 64
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
@ -1,4 +1,4 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
129
dlt-connector/src/data/proto/3_3/TransactionBody.ts
Normal file
129
dlt-connector/src/data/proto/3_3/TransactionBody.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import { Field, Message, OneOf } from 'protobufjs'
|
||||
|
||||
import { CrossGroupType } from './enum/CrossGroupType'
|
||||
|
||||
import { Timestamp } from './Timestamp'
|
||||
import { GradidoTransfer } from './GradidoTransfer'
|
||||
import { GradidoCreation } from './GradidoCreation'
|
||||
import { GradidoDeferredTransfer } from './GradidoDeferredTransfer'
|
||||
import { GroupFriendsUpdate } from './GroupFriendsUpdate'
|
||||
import { RegisterAddress } from './RegisterAddress'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { determineCrossGroupType, determineOtherGroup } from '../transactionBody.logic'
|
||||
import { CommunityRoot } from './CommunityRoot'
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { TransactionType } from '@/graphql/enum/TransactionType'
|
||||
import { TransactionBase } from '../TransactionBase'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { timestampToDate } from '@/utils/typeConverter'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class TransactionBody extends Message<TransactionBody> {
|
||||
public constructor(transaction?: TransactionDraft | CommunityDraft) {
|
||||
if (transaction) {
|
||||
let type = CrossGroupType.LOCAL
|
||||
let otherGroup = ''
|
||||
if (transaction instanceof TransactionDraft) {
|
||||
type = determineCrossGroupType(transaction)
|
||||
otherGroup = determineOtherGroup(type, transaction)
|
||||
}
|
||||
|
||||
super({
|
||||
memo: 'Not implemented yet',
|
||||
createdAt: new Timestamp(new Date(transaction.createdAt)),
|
||||
versionNumber: '3.3',
|
||||
type,
|
||||
otherGroup,
|
||||
})
|
||||
} else {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
@Field.d(1, 'string')
|
||||
public memo: string
|
||||
|
||||
@Field.d(2, Timestamp)
|
||||
public createdAt: Timestamp
|
||||
|
||||
@Field.d(3, 'string')
|
||||
public versionNumber: string
|
||||
|
||||
@Field.d(4, CrossGroupType)
|
||||
public type: CrossGroupType
|
||||
|
||||
@Field.d(5, 'string')
|
||||
public otherGroup: string
|
||||
|
||||
@OneOf.d(
|
||||
'gradidoTransfer',
|
||||
'gradidoCreation',
|
||||
'groupFriendsUpdate',
|
||||
'registerAddress',
|
||||
'gradidoDeferredTransfer',
|
||||
'communityRoot',
|
||||
)
|
||||
public data: string
|
||||
|
||||
@Field.d(6, 'GradidoTransfer')
|
||||
transfer?: GradidoTransfer
|
||||
|
||||
@Field.d(7, 'GradidoCreation')
|
||||
creation?: GradidoCreation
|
||||
|
||||
@Field.d(8, 'GroupFriendsUpdate')
|
||||
groupFriendsUpdate?: GroupFriendsUpdate
|
||||
|
||||
@Field.d(9, 'RegisterAddress')
|
||||
registerAddress?: RegisterAddress
|
||||
|
||||
@Field.d(10, 'GradidoDeferredTransfer')
|
||||
deferredTransfer?: GradidoDeferredTransfer
|
||||
|
||||
@Field.d(11, 'CommunityRoot')
|
||||
communityRoot?: CommunityRoot
|
||||
|
||||
public getTransactionType(): TransactionType | undefined {
|
||||
if (this.transfer) return TransactionType.GRADIDO_TRANSFER
|
||||
else if (this.creation) return TransactionType.GRADIDO_CREATION
|
||||
else if (this.groupFriendsUpdate) return TransactionType.GROUP_FRIENDS_UPDATE
|
||||
else if (this.registerAddress) return TransactionType.REGISTER_ADDRESS
|
||||
else if (this.deferredTransfer) return TransactionType.GRADIDO_DEFERRED_TRANSFER
|
||||
else if (this.communityRoot) return TransactionType.COMMUNITY_ROOT
|
||||
}
|
||||
|
||||
public getTransactionBase(): TransactionBase | undefined {
|
||||
if (this.transfer) return this.transfer
|
||||
if (this.creation) return this.creation
|
||||
if (this.groupFriendsUpdate) return this.groupFriendsUpdate
|
||||
if (this.registerAddress) return this.registerAddress
|
||||
if (this.deferredTransfer) return this.deferredTransfer
|
||||
if (this.communityRoot) return this.communityRoot
|
||||
}
|
||||
|
||||
public fillTransactionRecipe(recipe: Transaction): void {
|
||||
recipe.createdAt = timestampToDate(this.createdAt)
|
||||
recipe.protocolVersion = this.versionNumber
|
||||
const transactionType = this.getTransactionType()
|
||||
if (!transactionType) {
|
||||
throw new LogError("invalid TransactionBody couldn't determine transaction type")
|
||||
}
|
||||
recipe.type = transactionType.valueOf()
|
||||
this.getTransactionBase()?.fillTransactionRecipe(recipe)
|
||||
}
|
||||
|
||||
public getRecipientPublicKey(): Buffer | undefined {
|
||||
if (this.transfer) {
|
||||
return this.transfer.recipient
|
||||
}
|
||||
if (this.creation) {
|
||||
return this.creation.recipient.pubkey
|
||||
}
|
||||
if (this.deferredTransfer) {
|
||||
return this.deferredTransfer.transfer.recipient
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
import { Field, Message } from 'protobufjs'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
19
dlt-connector/src/data/proto/3_3/enum/AddressType.ts
Normal file
19
dlt-connector/src/data/proto/3_3/enum/AddressType.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export enum AddressType {
|
||||
NONE = 0, // if no address was found
|
||||
COMMUNITY_HUMAN = 1, // creation account for human
|
||||
COMMUNITY_GMW = 2, // community public budget account
|
||||
COMMUNITY_AUF = 3, // community compensation and environment founds account
|
||||
COMMUNITY_PROJECT = 4, // no creations allowed
|
||||
SUBACCOUNT = 5, // no creations allowed
|
||||
CRYPTO_ACCOUNT = 6, // user control his keys, no creations
|
||||
}
|
||||
|
||||
export function getAddressTypeEnumValue(typeString: string): AddressType | undefined {
|
||||
// Iterate through all enum values
|
||||
for (const key in AddressType) {
|
||||
if (AddressType[key] === typeString) {
|
||||
return AddressType[key] as unknown as AddressType
|
||||
}
|
||||
}
|
||||
return undefined // If the string is not found
|
||||
}
|
||||
7
dlt-connector/src/data/proto/3_3/enum/CrossGroupType.ts
Normal file
7
dlt-connector/src/data/proto/3_3/enum/CrossGroupType.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export enum CrossGroupType {
|
||||
LOCAL = 0,
|
||||
INBOUND = 1,
|
||||
OUTBOUND = 2,
|
||||
// for cross group transaction which haven't a direction like group friend update
|
||||
// CROSS = 3,
|
||||
}
|
||||
@ -1,6 +1,9 @@
|
||||
import { TransactionValidationLevel } from '@/graphql/enum/TransactionValidationLevel'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
|
||||
export abstract class TransactionBase {
|
||||
// validate if transaction is valid, maybe expensive because depending on level several transactions will be fetched from db
|
||||
public abstract validate(level: TransactionValidationLevel): boolean
|
||||
|
||||
public abstract fillTransactionRecipe(recipe: Transaction): void
|
||||
}
|
||||
95
dlt-connector/src/data/proto/TransactionBody.builder.ts
Normal file
95
dlt-connector/src/data/proto/TransactionBody.builder.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { TransactionBody } from './3_3/TransactionBody'
|
||||
import { Account } from '@entity/Account'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { InputTransactionType } from '@/graphql/enum/InputTransactionType'
|
||||
import { GradidoCreation } from './3_3/GradidoCreation'
|
||||
import { GradidoTransfer } from './3_3/GradidoTransfer'
|
||||
import { Community } from '@entity/Community'
|
||||
import { CommunityRoot } from './3_3/CommunityRoot'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
export class TransactionBodyBuilder {
|
||||
private signingAccount?: Account
|
||||
private recipientAccount?: Account
|
||||
private body: TransactionBody | undefined
|
||||
|
||||
// https://refactoring.guru/design-patterns/builder/typescript/example
|
||||
/**
|
||||
* A fresh builder instance should contain a blank product object, which is
|
||||
* used in further assembly.
|
||||
*/
|
||||
constructor() {
|
||||
this.reset()
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.body = undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Concrete Builders are supposed to provide their own methods for
|
||||
* retrieving results. That's because various types of builders may create
|
||||
* entirely different products that don't follow the same interface.
|
||||
* Therefore, such methods cannot be declared in the base Builder interface
|
||||
* (at least in a statically typed programming language).
|
||||
*
|
||||
* Usually, after returning the end result to the client, a builder instance
|
||||
* is expected to be ready to start producing another product. That's why
|
||||
* it's a usual practice to call the reset method at the end of the
|
||||
* `getProduct` method body. However, this behavior is not mandatory, and
|
||||
* you can make your builders wait for an explicit reset call from the
|
||||
* client code before disposing of the previous result.
|
||||
*/
|
||||
public build(): TransactionBody {
|
||||
const result = this.body
|
||||
if (!result) {
|
||||
throw new LogError(
|
||||
'cannot build Transaction Body, missing information, please call at least fromTransactionDraft or fromCommunityDraft',
|
||||
)
|
||||
}
|
||||
this.reset()
|
||||
return result
|
||||
}
|
||||
|
||||
public setSigningAccount(signingAccount: Account): TransactionBodyBuilder {
|
||||
this.signingAccount = signingAccount
|
||||
return this
|
||||
}
|
||||
|
||||
public setRecipientAccount(recipientAccount: Account): TransactionBodyBuilder {
|
||||
this.recipientAccount = recipientAccount
|
||||
return this
|
||||
}
|
||||
|
||||
public fromTransactionDraft(transactionDraft: TransactionDraft): TransactionBodyBuilder {
|
||||
this.body = new TransactionBody(transactionDraft)
|
||||
// TODO: load pubkeys for sender and recipient user from db
|
||||
switch (transactionDraft.type) {
|
||||
case InputTransactionType.CREATION:
|
||||
this.body.creation = new GradidoCreation(transactionDraft, this.recipientAccount)
|
||||
this.body.data = 'gradidoCreation'
|
||||
break
|
||||
case InputTransactionType.SEND:
|
||||
case InputTransactionType.RECEIVE:
|
||||
this.body.transfer = new GradidoTransfer(
|
||||
transactionDraft,
|
||||
this.signingAccount,
|
||||
this.recipientAccount,
|
||||
)
|
||||
this.body.data = 'gradidoTransfer'
|
||||
break
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
public fromCommunityDraft(
|
||||
communityDraft: CommunityDraft,
|
||||
community: Community,
|
||||
): TransactionBodyBuilder {
|
||||
this.body = new TransactionBody(communityDraft)
|
||||
this.body.communityRoot = new CommunityRoot(community)
|
||||
this.body.data = 'communityRoot'
|
||||
return this
|
||||
}
|
||||
}
|
||||
@ -1,28 +1,8 @@
|
||||
import { CrossGroupType } from '@/graphql/enum/CrossGroupType'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { TransactionType } from '@/graphql/enum/TransactionType'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { CrossGroupType } from './3_3/enum/CrossGroupType'
|
||||
import { InputTransactionType } from '@/graphql/enum/InputTransactionType'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { GradidoCreation } from '@/proto/3_3/GradidoCreation'
|
||||
import { GradidoTransfer } from '@/proto/3_3/GradidoTransfer'
|
||||
import { TransactionBody } from '@/proto/3_3/TransactionBody'
|
||||
|
||||
export const create = (transaction: TransactionDraft): TransactionBody => {
|
||||
const body = new TransactionBody(transaction)
|
||||
// TODO: load pubkeys for sender and recipient user from db
|
||||
switch (transaction.type) {
|
||||
case TransactionType.CREATION:
|
||||
body.creation = new GradidoCreation(transaction)
|
||||
body.data = 'gradidoCreation'
|
||||
break
|
||||
case TransactionType.SEND:
|
||||
case TransactionType.RECEIVE:
|
||||
body.transfer = new GradidoTransfer(transaction)
|
||||
body.data = 'gradidoTransfer'
|
||||
break
|
||||
}
|
||||
return body
|
||||
}
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
|
||||
export const determineCrossGroupType = ({
|
||||
senderUser,
|
||||
@ -33,12 +13,12 @@ export const determineCrossGroupType = ({
|
||||
!recipientUser.communityUuid ||
|
||||
recipientUser.communityUuid === '' ||
|
||||
senderUser.communityUuid === recipientUser.communityUuid ||
|
||||
type === TransactionType.CREATION
|
||||
type === InputTransactionType.CREATION
|
||||
) {
|
||||
return CrossGroupType.LOCAL
|
||||
} else if (type === TransactionType.SEND) {
|
||||
} else if (type === InputTransactionType.SEND) {
|
||||
return CrossGroupType.INBOUND
|
||||
} else if (type === TransactionType.RECEIVE) {
|
||||
} else if (type === InputTransactionType.RECEIVE) {
|
||||
return CrossGroupType.OUTBOUND
|
||||
}
|
||||
throw new TransactionError(
|
||||
19
dlt-connector/src/graphql/arg/CommunityArg.ts
Normal file
19
dlt-connector/src/graphql/arg/CommunityArg.ts
Normal file
@ -0,0 +1,19 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
|
||||
import { IsBoolean, IsUUID } from 'class-validator'
|
||||
import { ArgsType, Field } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export class CommunityArg {
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsUUID('4')
|
||||
uuid?: string
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
@IsBoolean()
|
||||
foreign?: boolean
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
@IsBoolean()
|
||||
confirmed?: boolean
|
||||
}
|
||||
12
dlt-connector/src/graphql/enum/InputTransactionType.ts
Executable file
12
dlt-connector/src/graphql/enum/InputTransactionType.ts
Executable file
@ -0,0 +1,12 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum InputTransactionType {
|
||||
CREATION = 1,
|
||||
SEND = 2,
|
||||
RECEIVE = 3,
|
||||
}
|
||||
|
||||
registerEnumType(InputTransactionType, {
|
||||
name: 'InputTransactionType', // this one is mandatory
|
||||
description: 'Type of the transaction', // this one is optional
|
||||
})
|
||||
@ -5,6 +5,8 @@ export enum TransactionErrorType {
|
||||
MISSING_PARAMETER = 'Missing parameter',
|
||||
ALREADY_EXIST = 'Already exist',
|
||||
DB_ERROR = 'DB Error',
|
||||
PROTO_DECODE_ERROR = 'Proto Decode Error',
|
||||
PROTO_ENCODE_ERROR = 'Proto Encode Error',
|
||||
}
|
||||
|
||||
registerEnumType(TransactionErrorType, {
|
||||
|
||||
27
dlt-connector/src/graphql/enum/TransactionType.ts
Executable file → Normal file
27
dlt-connector/src/graphql/enum/TransactionType.ts
Executable file → Normal file
@ -1,12 +1,15 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum TransactionType {
|
||||
CREATION = 1,
|
||||
SEND = 2,
|
||||
RECEIVE = 3,
|
||||
}
|
||||
|
||||
registerEnumType(TransactionType, {
|
||||
name: 'TransactionType', // this one is mandatory
|
||||
description: 'Type of the transaction', // this one is optional
|
||||
})
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum TransactionType {
|
||||
GRADIDO_TRANSFER = 1,
|
||||
GRADIDO_CREATION = 2,
|
||||
GROUP_FRIENDS_UPDATE = 3,
|
||||
REGISTER_ADDRESS = 4,
|
||||
GRADIDO_DEFERRED_TRANSFER = 5,
|
||||
COMMUNITY_ROOT = 6,
|
||||
}
|
||||
|
||||
registerEnumType(TransactionType, {
|
||||
name: 'TransactionType', // this one is mandatory
|
||||
description: 'Type of the transaction', // this one is optional
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { TransactionType } from '@enum/TransactionType'
|
||||
import { InputTransactionType } from '@enum/InputTransactionType'
|
||||
import { InputType, Field } from 'type-graphql'
|
||||
import { UserIdentifier } from './UserIdentifier'
|
||||
import { isValidDateString } from '@validator/DateString'
|
||||
@ -24,9 +24,9 @@ export class TransactionDraft {
|
||||
@IsPositiveDecimal()
|
||||
amount: Decimal
|
||||
|
||||
@Field(() => TransactionType)
|
||||
@IsEnum(TransactionType)
|
||||
type: TransactionType
|
||||
@Field(() => InputTransactionType)
|
||||
@IsEnum(InputTransactionType)
|
||||
type: InputTransactionType
|
||||
|
||||
@Field(() => String)
|
||||
@isValidDateString()
|
||||
|
||||
36
dlt-connector/src/graphql/model/Community.ts
Normal file
36
dlt-connector/src/graphql/model/Community.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import { Community as CommunityEntity } from '@entity/Community'
|
||||
|
||||
@ObjectType()
|
||||
export class Community {
|
||||
constructor(entity: CommunityEntity) {
|
||||
this.id = entity.id
|
||||
this.iotaTopic = entity.iotaTopic
|
||||
if (entity.rootPubkey) {
|
||||
this.rootPublicKeyHex = entity.rootPubkey?.toString('hex')
|
||||
}
|
||||
this.foreign = entity.foreign
|
||||
this.createdAt = entity.createdAt.toString()
|
||||
if (entity.confirmedAt) {
|
||||
this.confirmedAt = entity.confirmedAt.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
iotaTopic: string
|
||||
|
||||
@Field(() => String)
|
||||
rootPublicKeyHex?: string
|
||||
|
||||
@Field(() => Boolean)
|
||||
foreign: boolean
|
||||
|
||||
@Field(() => String)
|
||||
createdAt: string
|
||||
|
||||
@Field(() => String)
|
||||
confirmedAt?: string
|
||||
}
|
||||
25
dlt-connector/src/graphql/model/TransactionRecipe.ts
Normal file
25
dlt-connector/src/graphql/model/TransactionRecipe.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Field, Int, ObjectType } from 'type-graphql'
|
||||
import { TransactionRecipe as TransactionRecipeEntity } from '@entity/TransactionRecipe'
|
||||
import { TransactionType } from '@enum/TransactionType'
|
||||
|
||||
@ObjectType()
|
||||
export class TransactionRecipe {
|
||||
public constructor({ id, createdAt, type, senderCommunity }: TransactionRecipeEntity) {
|
||||
this.id = id
|
||||
this.createdAt = createdAt.toString()
|
||||
this.type = type
|
||||
this.topic = senderCommunity.iotaTopic
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
createdAt: string
|
||||
|
||||
@Field(() => TransactionType)
|
||||
type: TransactionType
|
||||
|
||||
@Field(() => String)
|
||||
topic: string
|
||||
}
|
||||
@ -1,15 +1,16 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { TransactionError } from './TransactionError'
|
||||
import { TransactionRecipe } from './TransactionRecipe'
|
||||
|
||||
@ObjectType()
|
||||
export class TransactionResult {
|
||||
constructor(content?: TransactionError | string) {
|
||||
constructor(content?: TransactionError | TransactionRecipe) {
|
||||
this.succeed = true
|
||||
if (content instanceof TransactionError) {
|
||||
this.error = content
|
||||
this.succeed = false
|
||||
} else if (typeof content === 'string') {
|
||||
this.messageId = content
|
||||
} else if (content instanceof TransactionRecipe) {
|
||||
this.recipe = content
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,9 +19,9 @@ export class TransactionResult {
|
||||
error?: TransactionError
|
||||
|
||||
// if no error happend, the message id of the iota transaction
|
||||
@Field(() => String, { nullable: true })
|
||||
messageId?: string
|
||||
@Field(() => TransactionRecipe, { nullable: true })
|
||||
recipe?: TransactionRecipe
|
||||
|
||||
@Field(() => Boolean)
|
||||
succeed: boolean
|
||||
}
|
||||
}
|
||||
@ -1,53 +1,59 @@
|
||||
import { Resolver, Arg, Mutation } from 'type-graphql'
|
||||
import { Resolver, Query, Arg, Mutation, Args } from 'type-graphql'
|
||||
|
||||
import { CommunityDraft } from '@input/CommunityDraft'
|
||||
|
||||
import { TransactionResult } from '../model/TransactionResult'
|
||||
import { TransactionError } from '../model/TransactionError'
|
||||
import { create as createCommunity, isExist } from '@/controller/Community'
|
||||
import { TransactionErrorType } from '../enum/TransactionErrorType'
|
||||
import { logger } from '@/server/logger'
|
||||
import { TransactionResult } from '@model/TransactionResult'
|
||||
import { TransactionError } from '@model/TransactionError'
|
||||
import { TransactionErrorType } from '@enum/TransactionErrorType'
|
||||
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
|
||||
import { Community } from '@model/Community'
|
||||
import { CommunityArg } from '@arg/CommunityArg'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { logger } from '@/server/logger'
|
||||
import { CommunityRepository } from '@/data/Community.repository'
|
||||
import { addCommunity } from '@/interactions/backendToDb/community/community.context'
|
||||
|
||||
@Resolver()
|
||||
export class CommunityResolver {
|
||||
@Query(() => Community)
|
||||
async community(@Args() communityArg: CommunityArg): Promise<Community> {
|
||||
logger.info('community', communityArg)
|
||||
const result = await CommunityRepository.findByCommunityArg(communityArg)
|
||||
if (result.length === 0) {
|
||||
throw new LogError('cannot find community')
|
||||
} else if (result.length === 1) {
|
||||
return new Community(result[0])
|
||||
} else {
|
||||
throw new LogError('find multiple communities')
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => Boolean)
|
||||
async isCommunityExist(@Args() communityArg: CommunityArg): Promise<boolean> {
|
||||
logger.info('isCommunity', communityArg)
|
||||
return (await CommunityRepository.findByCommunityArg(communityArg)).length === 1
|
||||
}
|
||||
|
||||
@Query(() => [Community])
|
||||
async communities(@Args() communityArg: CommunityArg): Promise<Community[]> {
|
||||
logger.info('communities', communityArg)
|
||||
const result = await CommunityRepository.findByCommunityArg(communityArg)
|
||||
return result.map((communityEntity) => new Community(communityEntity))
|
||||
}
|
||||
|
||||
@Mutation(() => TransactionResult)
|
||||
async addCommunity(
|
||||
@Arg('data')
|
||||
communityDraft: CommunityDraft,
|
||||
): Promise<TransactionResult> {
|
||||
try {
|
||||
const topic = iotaTopicFromCommunityUUID(communityDraft.uuid)
|
||||
|
||||
// check if community was already written to db
|
||||
if (await isExist(topic)) {
|
||||
return new TransactionResult(
|
||||
new TransactionError(TransactionErrorType.ALREADY_EXIST, 'community already exist!'),
|
||||
)
|
||||
}
|
||||
const community = createCommunity(communityDraft, topic)
|
||||
|
||||
let result: TransactionResult
|
||||
|
||||
if (!communityDraft.foreign) {
|
||||
// TODO: CommunityRoot Transaction for blockchain
|
||||
}
|
||||
try {
|
||||
await community.save()
|
||||
result = new TransactionResult()
|
||||
} catch (err) {
|
||||
logger.error('error saving new community into db: %s', err)
|
||||
result = new TransactionResult(
|
||||
new TransactionError(TransactionErrorType.DB_ERROR, 'error saving community into db'),
|
||||
)
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
if (error instanceof TransactionError) {
|
||||
return new TransactionResult(error)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
logger.info('addCommunity', communityDraft)
|
||||
const topic = iotaTopicFromCommunityUUID(communityDraft.uuid)
|
||||
// check if community was already written to db
|
||||
if (await CommunityRepository.isExist(topic)) {
|
||||
return new TransactionResult(
|
||||
new TransactionError(TransactionErrorType.ALREADY_EXIST, 'community already exist!'),
|
||||
)
|
||||
}
|
||||
return await addCommunity(communityDraft, topic)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
import { Resolver, Query, Arg, Mutation } from 'type-graphql'
|
||||
|
||||
import { TransactionDraft } from '@input/TransactionDraft'
|
||||
|
||||
import { create as createTransactionBody } from '@controller/TransactionBody'
|
||||
import { create as createGradidoTransaction } from '@controller/GradidoTransaction'
|
||||
|
||||
import { sendMessage as iotaSendMessage } from '@/client/IotaClient'
|
||||
import { GradidoTransaction } from '@/proto/3_3/GradidoTransaction'
|
||||
import { TransactionResult } from '../model/TransactionResult'
|
||||
import { TransactionError } from '../model/TransactionError'
|
||||
|
||||
@ -29,11 +22,68 @@ export class TransactionResolver {
|
||||
transaction: TransactionDraft,
|
||||
): Promise<TransactionResult> {
|
||||
try {
|
||||
const body = createTransactionBody(transaction)
|
||||
const message = createGradidoTransaction(body)
|
||||
const messageBuffer = GradidoTransaction.encode(message).finish()
|
||||
const resultMessage = await iotaSendMessage(messageBuffer)
|
||||
return new TransactionResult(resultMessage.messageId)
|
||||
logger.info('sendTransaction call', transaction)
|
||||
const signingAccount = await findAccountByUserIdentifier(transaction.senderUser)
|
||||
if (!signingAccount) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.NOT_FOUND,
|
||||
"couldn't found sender user account in db",
|
||||
)
|
||||
}
|
||||
logger.info('signing account', signingAccount)
|
||||
|
||||
const recipientAccount = await findAccountByUserIdentifier(transaction.recipientUser)
|
||||
if (!recipientAccount) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.NOT_FOUND,
|
||||
"couldn't found recipient user account in db",
|
||||
)
|
||||
}
|
||||
logger.info('recipient account', recipientAccount)
|
||||
|
||||
const body = createTransactionBody(transaction, signingAccount, recipientAccount)
|
||||
logger.info('body', body)
|
||||
const gradidoTransaction = createGradidoTransaction(body)
|
||||
|
||||
const signingKeyPair = getKeyPair(signingAccount)
|
||||
if (!signingKeyPair) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.NOT_FOUND,
|
||||
"couldn't found signing key pair",
|
||||
)
|
||||
}
|
||||
logger.info('key pair for signing', signingKeyPair)
|
||||
|
||||
KeyManager.getInstance().sign(gradidoTransaction, [signingKeyPair])
|
||||
const recipeTransactionController = await TransactionRecipe.create({
|
||||
transaction: gradidoTransaction,
|
||||
senderUser: transaction.senderUser,
|
||||
recipientUser: transaction.recipientUser,
|
||||
signingAccount,
|
||||
recipientAccount,
|
||||
backendTransactionId: transaction.backendTransactionId,
|
||||
})
|
||||
try {
|
||||
await recipeTransactionController.getTransactionRecipeEntity().save()
|
||||
ConditionalSleepManager.getInstance().signal(TRANSMIT_TO_IOTA_SLEEP_CONDITION_KEY)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
if (error.code === 'ER_DUP_ENTRY' && body.type === CrossGroupType.LOCAL) {
|
||||
const existingRecipe = await findBySignature(
|
||||
gradidoTransaction.sigMap.sigPair[0].signature,
|
||||
)
|
||||
if (!existingRecipe) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.LOGIC_ERROR,
|
||||
"recipe cannot be added because signature exist but couldn't load this existing receipt",
|
||||
)
|
||||
}
|
||||
return new TransactionResult(new TransactionRecipeOutput(existingRecipe))
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
return new TransactionResult()
|
||||
} catch (error) {
|
||||
if (error instanceof TransactionError) {
|
||||
return new TransactionResult(error)
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { Community } from '@entity/Community'
|
||||
|
||||
export abstract class CommunityRole {
|
||||
abstract addCommunity(communityDraft: CommunityDraft, topic: string): Promise<Community>
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { Community } from '@entity/Community'
|
||||
import { CommunityRole } from './Community.role'
|
||||
import { logger } from '@/server/logger'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { createCommunity } from '@/data/community.factory'
|
||||
|
||||
export class ForeignCommunityRole extends CommunityRole {
|
||||
addCommunity(communityDraft: CommunityDraft, topic: string): Promise<Community> {
|
||||
const community = createCommunity(communityDraft, topic)
|
||||
try {
|
||||
return community.save()
|
||||
} catch (error) {
|
||||
logger.error('error saving new foreign community into db: %s', error)
|
||||
throw new TransactionError(TransactionErrorType.DB_ERROR, 'error saving community into db')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { Community } from '@entity/Community'
|
||||
import { CommunityRole } from './Community.role'
|
||||
import { getTransaction } from '@/client/GradidoNode'
|
||||
import { timestampSecondsToDate } from '@/utils/typeConverter'
|
||||
import { createHomeCommunity } from '@/data/community.factory'
|
||||
import { createCommunityRootTransactionRecipe } from '../transaction/transaction.context'
|
||||
import { QueryRunner } from 'typeorm'
|
||||
|
||||
export class HomeCommunityRole extends CommunityRole {
|
||||
public async addCommunity(communityDraft: CommunityDraft, topic: string): Promise<Community> {
|
||||
const community = createHomeCommunity(communityDraft, topic)
|
||||
|
||||
// check if a CommunityRoot Transaction exist already on iota blockchain
|
||||
const existingCommunityRootTransaction = await getTransaction(1, community.iotaTopic)
|
||||
if (existingCommunityRootTransaction) {
|
||||
community.confirmedAt = timestampSecondsToDate(existingCommunityRootTransaction.confirmedAt)
|
||||
return community.save()
|
||||
} else {
|
||||
createCommunityRootTransactionRecipe(communityDraft, community).storeAsTransaction(
|
||||
async (queryRunner: QueryRunner): Promise<void> => {
|
||||
await queryRunner.manager.save(community)
|
||||
},
|
||||
)
|
||||
}
|
||||
return community.save()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { TransactionResult } from '@/graphql/model/TransactionResult'
|
||||
import { ForeignCommunityRole } from './ForeignCommunity.role'
|
||||
import { HomeCommunityRole } from './HomeCommunity.role'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { TransactionsManager } from '@/controller/TransactionsManager'
|
||||
import { iotaTopicFromCommunityUUID } from '@/utils/typeConverter'
|
||||
|
||||
export const addCommunity = async (
|
||||
communityDraft: CommunityDraft,
|
||||
iotaTopic?: string,
|
||||
): Promise<TransactionResult> => {
|
||||
const communityRole = communityDraft.foreign
|
||||
? new ForeignCommunityRole()
|
||||
: new HomeCommunityRole()
|
||||
try {
|
||||
if (!iotaTopic) {
|
||||
iotaTopic = iotaTopicFromCommunityUUID(communityDraft.uuid)
|
||||
}
|
||||
await communityRole.addCommunity(communityDraft, iotaTopic)
|
||||
await TransactionsManager.getInstance().addTopic(iotaTopic)
|
||||
return new TransactionResult()
|
||||
} catch (error) {
|
||||
if (error instanceof TransactionError) {
|
||||
return new TransactionResult(error)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { TransactionRecipeRole } from './TransactionRecipe.role'
|
||||
import { Community } from '@entity/Community'
|
||||
import { TransactionBodyBuilder } from '@/data/proto/TransactionBody.builder'
|
||||
import { KeyPair } from '@/data/KeyPair'
|
||||
import { sign } from '@/utils/cryptoHelper'
|
||||
|
||||
export class CommunityRootTransactionRole extends TransactionRecipeRole {
|
||||
public createFromCommunityDraft(
|
||||
communityDraft: CommunityDraft,
|
||||
community: Community,
|
||||
): CommunityRootTransactionRole {
|
||||
// create proto transaction body
|
||||
const transactionBody = new TransactionBodyBuilder()
|
||||
.fromCommunityDraft(communityDraft, community)
|
||||
.build()
|
||||
// build transaction entity
|
||||
this.transactionBuilder.fromTransactionBody(transactionBody).setSenderCommunity(community)
|
||||
const transaction = this.transactionBuilder.getTransaction()
|
||||
// sign
|
||||
this.transactionBuilder.setSignature(sign(transaction.bodyBytes, new KeyPair(community)))
|
||||
return this
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
import { TransactionBuilder } from '@/data/Transaction.builder'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
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 '@/server/logger'
|
||||
import { getDataSource } from '@/typeorm/DataSource'
|
||||
import { QueryRunner } from 'typeorm'
|
||||
|
||||
export class TransactionRecipeRole {
|
||||
protected transactionBuilder: TransactionBuilder
|
||||
construct() {
|
||||
this.transactionBuilder = new TransactionBuilder()
|
||||
}
|
||||
|
||||
public createFromTransactionDraft(transactionDraft: TransactionDraft): TransactionRecipeRole {
|
||||
return this
|
||||
}
|
||||
|
||||
public async storeAsTransaction(
|
||||
transactionFunction: (queryRunner: QueryRunner) => Promise<void>,
|
||||
): Promise<TransactionResult> {
|
||||
const queryRunner = getDataSource().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction()
|
||||
|
||||
let result: TransactionResult
|
||||
try {
|
||||
const transactionRecipe = this.transactionBuilder.build()
|
||||
await transactionFunction(queryRunner)
|
||||
await queryRunner.manager.save(transactionRecipe)
|
||||
await queryRunner.commitTransaction()
|
||||
result = new TransactionResult(new TransactionRecipe(transactionRecipe))
|
||||
} catch (err) {
|
||||
logger.error('error saving new transaction recipe into db: %s', err)
|
||||
result = new TransactionResult(
|
||||
new TransactionError(
|
||||
TransactionErrorType.DB_ERROR,
|
||||
'error saving transaction recipe into db',
|
||||
),
|
||||
)
|
||||
await queryRunner.rollbackTransaction()
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { Community } from '@entity/Community'
|
||||
import { TransactionRecipeRole } from './TransactionRecipe.role'
|
||||
import { CommunityRootTransactionRole } from './CommunityRootTransaction.role'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
|
||||
export const createCommunityRootTransactionRecipe = (
|
||||
communityDraft: CommunityDraft,
|
||||
community: Community,
|
||||
): TransactionRecipeRole => {
|
||||
const communityRootTransactionRole = new CommunityRootTransactionRole()
|
||||
return communityRootTransactionRole.createFromCommunityDraft(communityDraft, community)
|
||||
}
|
||||
|
||||
export const createTransactionRecipe = (
|
||||
transactionDraft: TransactionDraft,
|
||||
): TransactionRecipeRole => {
|
||||
const transactionRecipeRole = new TransactionRecipeRole()
|
||||
return transactionRecipeRole.createFromTransactionDraft(transactionDraft)
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
|
||||
import { TimestampSeconds } from './TimestampSeconds'
|
||||
import { TransferAmount } from './TransferAmount'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
|
||||
// need signature from group admin or
|
||||
// percent of group users another than the receiver
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class GradidoCreation extends Message<GradidoCreation> {
|
||||
constructor(transaction: TransactionDraft) {
|
||||
if (!transaction.targetDate) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'missing targetDate for contribution',
|
||||
)
|
||||
}
|
||||
super({
|
||||
recipient: new TransferAmount({ amount: transaction.amount.toString() }),
|
||||
targetDate: new TimestampSeconds(new Date(transaction.targetDate)),
|
||||
})
|
||||
}
|
||||
|
||||
@Field.d(1, TransferAmount)
|
||||
public recipient: TransferAmount
|
||||
|
||||
@Field.d(3, 'TimestampSeconds')
|
||||
public targetDate: TimestampSeconds
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
|
||||
import { SignatureMap } from './SignatureMap'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class GradidoTransaction extends Message<GradidoTransaction> {
|
||||
@Field.d(1, SignatureMap)
|
||||
public sigMap: SignatureMap
|
||||
|
||||
// inspired by Hedera
|
||||
// bodyBytes are the payload for signature
|
||||
// bodyBytes are serialized TransactionBody
|
||||
@Field.d(2, 'bytes')
|
||||
public bodyBytes: Buffer
|
||||
|
||||
// if it is a cross group transaction the parent message
|
||||
// id from outbound transaction or other by cross
|
||||
@Field.d(3, 'bytes')
|
||||
public parentMessageId: Buffer
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
|
||||
import { TransferAmount } from './TransferAmount'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class GradidoTransfer extends Message<GradidoTransfer> {
|
||||
constructor(transaction: TransactionDraft, coinOrigin?: string) {
|
||||
super({
|
||||
sender: new TransferAmount({
|
||||
amount: transaction.amount.toString(),
|
||||
communityId: coinOrigin,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@Field.d(1, TransferAmount)
|
||||
public sender: TransferAmount
|
||||
|
||||
@Field.d(2, 'bytes')
|
||||
public recipient: Buffer
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import { Field, Message } from '@apollo/protobufjs'
|
||||
|
||||
import { AddressType } from '@enum/AddressType'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class RegisterAddress extends Message<RegisterAddress> {
|
||||
@Field.d(1, 'bytes')
|
||||
public userPubkey: Buffer
|
||||
|
||||
@Field.d(2, 'AddressType')
|
||||
public addressType: AddressType
|
||||
|
||||
@Field.d(3, 'bytes')
|
||||
public nameHash: Buffer
|
||||
|
||||
@Field.d(4, 'bytes')
|
||||
public subaccountPubkey: Buffer
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
import { Field, Message, OneOf } from '@apollo/protobufjs'
|
||||
|
||||
import { CrossGroupType } from '@/graphql/enum/CrossGroupType'
|
||||
|
||||
import { Timestamp } from './Timestamp'
|
||||
import { GradidoTransfer } from './GradidoTransfer'
|
||||
import { GradidoCreation } from './GradidoCreation'
|
||||
import { GradidoDeferredTransfer } from './GradidoDeferredTransfer'
|
||||
import { GroupFriendsUpdate } from './GroupFriendsUpdate'
|
||||
import { RegisterAddress } from './RegisterAddress'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { determineCrossGroupType, determineOtherGroup } from '@/controller/TransactionBody'
|
||||
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export class TransactionBody extends Message<TransactionBody> {
|
||||
public constructor(transaction: TransactionDraft) {
|
||||
const type = determineCrossGroupType(transaction)
|
||||
super({
|
||||
memo: 'Not implemented yet',
|
||||
createdAt: new Timestamp(new Date(transaction.createdAt)),
|
||||
versionNumber: '3.3',
|
||||
type,
|
||||
otherGroup: determineOtherGroup(type, transaction),
|
||||
})
|
||||
}
|
||||
|
||||
@Field.d(1, 'string')
|
||||
public memo: string
|
||||
|
||||
@Field.d(2, Timestamp)
|
||||
public createdAt: Timestamp
|
||||
|
||||
@Field.d(3, 'string')
|
||||
public versionNumber: string
|
||||
|
||||
@Field.d(4, CrossGroupType)
|
||||
public type: CrossGroupType
|
||||
|
||||
@Field.d(5, 'string')
|
||||
public otherGroup: string
|
||||
|
||||
@OneOf.d(
|
||||
'gradidoTransfer',
|
||||
'gradidoCreation',
|
||||
'groupFriendsUpdate',
|
||||
'registerAddress',
|
||||
'gradidoDeferredTransfer',
|
||||
)
|
||||
public data: string
|
||||
|
||||
@Field.d(6, 'GradidoTransfer')
|
||||
transfer?: GradidoTransfer
|
||||
|
||||
@Field.d(7, 'GradidoCreation')
|
||||
creation?: GradidoCreation
|
||||
|
||||
@Field.d(8, 'GroupFriendsUpdate')
|
||||
groupFriendsUpdate?: GroupFriendsUpdate
|
||||
|
||||
@Field.d(9, 'RegisterAddress')
|
||||
registerAddress?: RegisterAddress
|
||||
|
||||
@Field.d(10, 'GradidoDeferredTransfer')
|
||||
deferredTransfer?: GradidoDeferredTransfer
|
||||
}
|
||||
6
dlt-connector/src/utils/cryptoHelper.ts
Normal file
6
dlt-connector/src/utils/cryptoHelper.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { KeyPair } from '@/data/KeyPair'
|
||||
import { sign as ed25519Sign } from 'bip32-ed25519'
|
||||
|
||||
export const sign = (message: Buffer, keyPair: KeyPair): Buffer => {
|
||||
return ed25519Sign(message, keyPair.getExtendPrivateKey())
|
||||
}
|
||||
15
dlt-connector/src/utils/derivationHelper.test.ts
Normal file
15
dlt-connector/src/utils/derivationHelper.test.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import 'reflect-metadata'
|
||||
import { Timestamp } from '../data/proto/3_3/Timestamp'
|
||||
import { hardenDerivationIndex, HARDENED_KEY_BITMASK } from './derivationHelper'
|
||||
import { timestampToDate } from './typeConverter'
|
||||
|
||||
describe('utils', () => {
|
||||
it('test bitmask for hardened keys', () => {
|
||||
const derivationIndex = hardenDerivationIndex(1)
|
||||
expect(derivationIndex).toBeGreaterThan(HARDENED_KEY_BITMASK)
|
||||
})
|
||||
it('test TimestampToDate', () => {
|
||||
const date = new Date('2011-04-17T12:01:10.109')
|
||||
expect(timestampToDate(new Timestamp(date))).toEqual(date)
|
||||
})
|
||||
})
|
||||
17
dlt-connector/src/utils/derivationHelper.ts
Normal file
17
dlt-connector/src/utils/derivationHelper.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export const HARDENED_KEY_BITMASK = 0x80000000
|
||||
|
||||
/*
|
||||
* change derivation index from x => x'
|
||||
* for more infos to hardened keys look here:
|
||||
* https://en.bitcoin.it/wiki/BIP_0032
|
||||
*/
|
||||
export const hardenDerivationIndex = (derivationIndex: number): number => {
|
||||
/*
|
||||
TypeScript uses signed integers by default,
|
||||
but bip32-ed25519 expects an unsigned value for the derivation index.
|
||||
The >>> shifts the bits 0 places to the right, which effectively makes no change to the value,
|
||||
but forces TypeScript to treat derivationIndex as an unsigned value.
|
||||
Source: ChatGPT
|
||||
*/
|
||||
return (derivationIndex | HARDENED_KEY_BITMASK) >>> 0
|
||||
}
|
||||
12
dlt-connector/src/utils/typeConverter.test.ts
Normal file
12
dlt-connector/src/utils/typeConverter.test.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import 'reflect-metadata'
|
||||
import { Timestamp } from '@/data/proto/3_3/Timestamp'
|
||||
import { timestampToDate } from './typeConverter'
|
||||
|
||||
describe('utils/typeConverter', () => {
|
||||
it('timestampToDate', () => {
|
||||
const now = new Date('Thu, 05 Oct 2023 11:55:18 +0000')
|
||||
const timestamp = new Timestamp(now)
|
||||
expect(timestamp.seconds).toBe(Math.round(now.getTime() / 1000))
|
||||
expect(timestampToDate(timestamp)).toEqual(now)
|
||||
})
|
||||
})
|
||||
@ -1,5 +1,12 @@
|
||||
import { crypto_generichash as cryptoHash } from 'sodium-native'
|
||||
|
||||
import { Timestamp } from '@/data/proto/3_3/Timestamp'
|
||||
import { TimestampSeconds } from '@/data/proto/3_3/TimestampSeconds'
|
||||
import { TransactionBody } from '@/data/proto/3_3/TransactionBody'
|
||||
import { logger } from '@/server/logger'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
|
||||
export const uuid4ToBuffer = (uuid: string): Buffer => {
|
||||
// Remove dashes from the UUIDv4 string
|
||||
const cleanedUUID = uuid.replace(/-/g, '')
|
||||
@ -15,3 +22,41 @@ export const iotaTopicFromCommunityUUID = (communityUUID: string): string => {
|
||||
cryptoHash(hash, uuid4ToBuffer(communityUUID))
|
||||
return hash.toString('hex')
|
||||
}
|
||||
|
||||
export const timestampToDate = (timestamp: Timestamp): Date => {
|
||||
let milliseconds = timestamp.nanoSeconds / 1000000
|
||||
milliseconds += timestamp.seconds * 1000
|
||||
return new Date(milliseconds)
|
||||
}
|
||||
|
||||
export const timestampSecondsToDate = (timestamp: TimestampSeconds): Date => {
|
||||
return new Date(timestamp.seconds * 1000)
|
||||
}
|
||||
|
||||
export const base64ToBuffer = (base64: string): Buffer => {
|
||||
return Buffer.from(base64, 'base64')
|
||||
}
|
||||
|
||||
export const bodyBytesToTransactionBody = (bodyBytes: Buffer): TransactionBody => {
|
||||
try {
|
||||
return TransactionBody.decode(new Uint8Array(bodyBytes))
|
||||
} catch (error) {
|
||||
logger.error('error decoding body from gradido transaction: %s', error)
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.PROTO_DECODE_ERROR,
|
||||
'cannot decode body from gradido transaction',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const transactionBodyToBodyBytes = (transactionBody: TransactionBody): Buffer => {
|
||||
try {
|
||||
return Buffer.from(TransactionBody.encode(transactionBody).finish())
|
||||
} catch (error) {
|
||||
logger.error('error encoding transaction body to body bytes', error)
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.PROTO_ENCODE_ERROR,
|
||||
'cannot encode transaction body',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,8 +55,7 @@
|
||||
"@resolver/*": ["src/graphql/resolver/*"],
|
||||
"@scalar/*": ["src/graphql/scalar/*"],
|
||||
"@test/*": ["test/*"],
|
||||
"@proto/*" : ["src/proto/*"],
|
||||
"@controller/*": ["src/controller/*"],
|
||||
"@proto/*" : ["src/proto/*"],
|
||||
"@validator/*" : ["src/graphql/validator/*"],
|
||||
"@typeorm/*" : ["src/typeorm/*"],
|
||||
/* external */
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz#5da62cf64c3b4419dabfef4536b57a40c8ff0b47"
|
||||
integrity sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==
|
||||
|
||||
"@apollo/protobufjs@1.2.7", "@apollo/protobufjs@^1.2.7":
|
||||
"@apollo/protobufjs@1.2.7":
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.7.tgz#3a8675512817e4a046a897e5f4f16415f16a7d8a"
|
||||
integrity sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==
|
||||
@ -843,6 +843,11 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@noble/hashes@^1.2.0":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
|
||||
integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
@ -1125,6 +1130,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.7.0.tgz#c03de4572f114a940bc2ca909a33ddb2b925e470"
|
||||
integrity sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==
|
||||
|
||||
"@types/node@>=13.7.0":
|
||||
version "20.8.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.7.tgz#ad23827850843de973096edfc5abc9e922492a25"
|
||||
integrity sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==
|
||||
dependencies:
|
||||
undici-types "~5.25.1"
|
||||
|
||||
"@types/node@^18.11.18":
|
||||
version "18.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.0.tgz#bd19d5133a6e5e2d0152ec079ac27c120e7f1763"
|
||||
@ -1628,6 +1640,22 @@ binary-extensions@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||
|
||||
bip32-ed25519@^0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/bip32-ed25519/-/bip32-ed25519-0.0.4.tgz#218943e212c2d3152dfd6f3a929305e3fe86534c"
|
||||
integrity sha512-KfazzGVLwl70WZ1r98dO+8yaJRTGgWHL9ITn4bXHQi2mB4cT3Hjh53tXWUpEWE1zKCln7PbyX8Z337VapAOb5w==
|
||||
dependencies:
|
||||
bn.js "^5.1.1"
|
||||
elliptic "^6.4.1"
|
||||
hash.js "^1.1.7"
|
||||
|
||||
bip39@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3"
|
||||
integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==
|
||||
dependencies:
|
||||
"@noble/hashes" "^1.2.0"
|
||||
|
||||
bl@^4.0.3:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
|
||||
@ -1637,6 +1665,16 @@ bl@^4.0.3:
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
bn.js@^4.11.9:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
|
||||
bn.js@^5.1.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
|
||||
integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==
|
||||
|
||||
body-parser@1.19.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
@ -1711,6 +1749,11 @@ braces@^3.0.2, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
brorand@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||
integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==
|
||||
|
||||
browser-process-hrtime@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
|
||||
@ -2343,6 +2386,19 @@ electron-to-chromium@^1.4.530:
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.531.tgz#22966d894c4680726c17cf2908ee82ff5d26ac25"
|
||||
integrity sha512-H6gi5E41Rn3/mhKlPaT1aIMg/71hTAqn0gYEllSuw9igNWtvQwu185jiCZoZD29n7Zukgh7GVZ3zGf0XvkhqjQ==
|
||||
|
||||
elliptic@^6.4.1:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
dependencies:
|
||||
bn.js "^4.11.9"
|
||||
brorand "^1.1.0"
|
||||
hash.js "^1.0.0"
|
||||
hmac-drbg "^1.0.1"
|
||||
inherits "^2.0.4"
|
||||
minimalistic-assert "^1.0.1"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
emittery@^0.8.1:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860"
|
||||
@ -3338,11 +3394,28 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
|
||||
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
minimalistic-assert "^1.0.1"
|
||||
|
||||
highlight.js@^10.7.1:
|
||||
version "10.7.3"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
|
||||
integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
|
||||
|
||||
hmac-drbg@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==
|
||||
dependencies:
|
||||
hash.js "^1.0.3"
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
html-encoding-sniffer@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
|
||||
@ -4379,6 +4452,11 @@ long@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
|
||||
|
||||
long@^5.0.0:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
|
||||
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
|
||||
|
||||
lower-case@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
|
||||
@ -4494,6 +4572,16 @@ mimic-response@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
|
||||
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
|
||||
|
||||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimalistic-crypto-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
||||
integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
|
||||
|
||||
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
@ -5038,6 +5126,24 @@ prompts@^2.0.1:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
protobufjs@^7.2.5:
|
||||
version "7.2.5"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d"
|
||||
integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.2"
|
||||
"@protobufjs/base64" "^1.1.2"
|
||||
"@protobufjs/codegen" "^2.0.4"
|
||||
"@protobufjs/eventemitter" "^1.1.0"
|
||||
"@protobufjs/fetch" "^1.1.0"
|
||||
"@protobufjs/float" "^1.0.2"
|
||||
"@protobufjs/inquire" "^1.1.0"
|
||||
"@protobufjs/path" "^1.1.2"
|
||||
"@protobufjs/pool" "^1.1.0"
|
||||
"@protobufjs/utf8" "^1.1.0"
|
||||
"@types/node" ">=13.7.0"
|
||||
long "^5.0.0"
|
||||
|
||||
proxy-addr@~2.0.5, proxy-addr@~2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
|
||||
@ -6202,6 +6308,11 @@ undefsafe@^2.0.5:
|
||||
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
|
||||
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
|
||||
|
||||
undici-types@~5.25.1:
|
||||
version "5.25.3"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3"
|
||||
integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==
|
||||
|
||||
universalify@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
BaseEntity,
|
||||
} from 'typeorm'
|
||||
import { User } from '../User'
|
||||
import { TransactionRecipe } from '../TransactionRecipe'
|
||||
import { TransactionRecipe } from './TransactionRecipe'
|
||||
import { ConfirmedTransaction } from '../ConfirmedTransaction'
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
BaseEntity,
|
||||
} from 'typeorm'
|
||||
import { Account } from '../Account'
|
||||
import { TransactionRecipe } from '../TransactionRecipe'
|
||||
import { TransactionRecipe } from './TransactionRecipe'
|
||||
import { AccountCommunity } from '../AccountCommunity'
|
||||
|
||||
@Entity('communities')
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { Account } from '../Account'
|
||||
import { Account } from './Account'
|
||||
import { TransactionRecipe } from '../TransactionRecipe'
|
||||
|
||||
@Entity('confirmed_transactions')
|
||||
|
||||
@ -10,8 +10,8 @@ import {
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { Account } from '../Account'
|
||||
import { Community } from '../Community'
|
||||
import { Account } from './Account'
|
||||
import { Community } from './Community'
|
||||
import { ConfirmedTransaction } from '../ConfirmedTransaction'
|
||||
|
||||
@Entity('transaction_recipes')
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { Account } from '../Account'
|
||||
import { Account } from './Account'
|
||||
import { TransactionRecipe } from '../TransactionRecipe'
|
||||
|
||||
@Entity('confirmed_transactions')
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
BaseEntity,
|
||||
} from 'typeorm'
|
||||
import { User } from '../User'
|
||||
import { Transaction } from '../Transaction'
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { AccountCommunity } from '../AccountCommunity'
|
||||
|
||||
@Entity('accounts')
|
||||
export class Account extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@ManyToOne(() => User, (user) => user.accounts, { cascade: true, eager: true }) // Assuming you have a User entity with 'accounts' relation
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user?: User
|
||||
|
||||
// if user id is null, account belongs to community gmw or auf
|
||||
@Column({ name: 'user_id', type: 'int', unsigned: true, nullable: true })
|
||||
userId?: number
|
||||
|
||||
@Column({ name: 'derivation_index', type: 'int', unsigned: true, nullable: true })
|
||||
derivationIndex?: number
|
||||
|
||||
@Column({ name: 'derive2_pubkey', type: 'binary', length: 32, unique: true })
|
||||
derive2Pubkey: Buffer
|
||||
|
||||
@Column({ type: 'tinyint', unsigned: true })
|
||||
type: number
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'datetime',
|
||||
precision: 3,
|
||||
default: () => 'CURRENT_TIMESTAMP(3)',
|
||||
})
|
||||
createdAt: Date
|
||||
|
||||
@Column({ name: 'confirmed_at', type: 'datetime', nullable: true })
|
||||
confirmedAt?: Date
|
||||
|
||||
@Column({
|
||||
type: 'decimal',
|
||||
precision: 40,
|
||||
scale: 20,
|
||||
default: 0,
|
||||
transformer: DecimalTransformer,
|
||||
})
|
||||
balance: Decimal
|
||||
|
||||
@Column({
|
||||
name: 'balance_date',
|
||||
type: 'datetime',
|
||||
default: () => 'CURRENT_TIMESTAMP()',
|
||||
})
|
||||
balanceDate: Date
|
||||
|
||||
@Column({
|
||||
name: 'balance_created_at',
|
||||
type: 'decimal',
|
||||
precision: 40,
|
||||
scale: 20,
|
||||
default: 0,
|
||||
transformer: DecimalTransformer,
|
||||
})
|
||||
balanceCreatedAt: Decimal
|
||||
|
||||
@Column({
|
||||
name: 'balance_created_at_date',
|
||||
type: 'datetime',
|
||||
precision: 3,
|
||||
default: () => 'CURRENT_TIMESTAMP(3)',
|
||||
})
|
||||
balanceCreatedAtDate: Date
|
||||
|
||||
@OneToMany(() => AccountCommunity, (accountCommunity) => accountCommunity.account)
|
||||
@JoinColumn({ name: 'account_id' })
|
||||
accountCommunities: AccountCommunity[]
|
||||
|
||||
@OneToMany(() => Transaction, (transaction) => transaction.signingAccount)
|
||||
transactionSigning?: Transaction[]
|
||||
|
||||
@OneToMany(() => Transaction, (transaction) => transaction.recipientAccount)
|
||||
transactionRecipient?: Transaction[]
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
OneToMany,
|
||||
BaseEntity,
|
||||
} from 'typeorm'
|
||||
import { Account } from '../Account'
|
||||
import { Transaction } from '../Transaction'
|
||||
import { AccountCommunity } from '../AccountCommunity'
|
||||
|
||||
@Entity('communities')
|
||||
export class Community extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'iota_topic', collation: 'utf8mb4_unicode_ci', unique: true })
|
||||
iotaTopic: string
|
||||
|
||||
@Column({ name: 'root_pubkey', type: 'binary', length: 32, unique: true, nullable: true })
|
||||
rootPubkey?: Buffer
|
||||
|
||||
@Column({ name: 'root_privkey', type: 'binary', length: 64, nullable: true })
|
||||
rootPrivkey?: Buffer
|
||||
|
||||
@Column({ name: 'root_chaincode', type: 'binary', length: 32, nullable: true })
|
||||
rootChaincode?: Buffer
|
||||
|
||||
@Column({ type: 'tinyint', default: true })
|
||||
foreign: boolean
|
||||
|
||||
@Column({ name: 'gmw_account_id', type: 'int', unsigned: true, nullable: true })
|
||||
gmwAccountId?: number
|
||||
|
||||
@OneToOne(() => Account, { cascade: true })
|
||||
@JoinColumn({ name: 'gmw_account_id' })
|
||||
gmwAccount?: Account
|
||||
|
||||
@Column({ name: 'auf_account_id', type: 'int', unsigned: true, nullable: true })
|
||||
aufAccountId?: number
|
||||
|
||||
@OneToOne(() => Account, { cascade: true })
|
||||
@JoinColumn({ name: 'auf_account_id' })
|
||||
aufAccount?: Account
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'datetime',
|
||||
precision: 3,
|
||||
default: () => 'CURRENT_TIMESTAMP(3)',
|
||||
})
|
||||
createdAt: Date
|
||||
|
||||
@Column({ name: 'confirmed_at', type: 'datetime', nullable: true })
|
||||
confirmedAt?: Date
|
||||
|
||||
@OneToMany(() => AccountCommunity, (accountCommunity) => accountCommunity.community)
|
||||
@JoinColumn({ name: 'community_id' })
|
||||
accountCommunities: AccountCommunity[]
|
||||
|
||||
@OneToMany(() => Transaction, (recipe) => recipe.senderCommunity)
|
||||
transactionSender?: Transaction[]
|
||||
|
||||
@OneToMany(() => Transaction, (recipe) => recipe.recipientCommunity)
|
||||
transactionRecipient?: Transaction[]
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
BaseEntity,
|
||||
} from 'typeorm'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { Account } from '../Account'
|
||||
import { Community } from '../Community'
|
||||
|
||||
@Entity('transactions')
|
||||
export class Transaction extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('increment', { unsigned: true, type: 'bigint' })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'iota_message_id', type: 'binary', length: 32, nullable: true })
|
||||
iotaMessageId?: Buffer
|
||||
|
||||
@Column({ name: 'backend_transaction_id', type: 'bigint', unsigned: true, nullable: true })
|
||||
backendTransactionId?: number
|
||||
|
||||
@OneToOne(() => Transaction)
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
paringTransaction?: Transaction
|
||||
|
||||
@Column({ name: 'paring_transaction_id', type: 'bigint', unsigned: true, nullable: true })
|
||||
paringTransactionId?: number
|
||||
|
||||
// if transaction has a sender than it is also the sender account
|
||||
@ManyToOne(() => Account, (account) => account.transactionSigning)
|
||||
@JoinColumn({ name: 'signing_account_id' })
|
||||
signingAccount?: Account
|
||||
|
||||
@Column({ name: 'signing_account_id', type: 'int', unsigned: true, nullable: true })
|
||||
signingAccountId?: number
|
||||
|
||||
@ManyToOne(() => Account, (account) => account.transactionRecipient)
|
||||
@JoinColumn({ name: 'recipient_account_id' })
|
||||
recipientAccount?: Account
|
||||
|
||||
@Column({ name: 'recipient_account_id', type: 'int', unsigned: true, nullable: true })
|
||||
recipientAccountId?: number
|
||||
|
||||
@ManyToOne(() => Community, (community) => community.transactionSender, {
|
||||
eager: true,
|
||||
})
|
||||
@JoinColumn({ name: 'sender_community_id' })
|
||||
senderCommunity: Community
|
||||
|
||||
@Column({ name: 'sender_community_id', type: 'int', unsigned: true })
|
||||
senderCommunityId: number
|
||||
|
||||
@ManyToOne(() => Community, (community) => community.transactionRecipient)
|
||||
@JoinColumn({ name: 'recipient_community_id' })
|
||||
recipientCommunity?: Community
|
||||
|
||||
@Column({ name: 'recipient_community_id', type: 'int', unsigned: true, nullable: true })
|
||||
recipientCommunityId?: number
|
||||
|
||||
@Column({
|
||||
type: 'decimal',
|
||||
precision: 40,
|
||||
scale: 20,
|
||||
nullable: true,
|
||||
transformer: DecimalTransformer,
|
||||
})
|
||||
amount?: Decimal
|
||||
|
||||
@Column({
|
||||
name: 'account_balance_created_at',
|
||||
type: 'decimal',
|
||||
precision: 40,
|
||||
scale: 20,
|
||||
transformer: DecimalTransformer,
|
||||
})
|
||||
accountBalanceCreatedAt: Decimal
|
||||
|
||||
@Column({ type: 'tinyint' })
|
||||
type: number
|
||||
|
||||
@Column({ name: 'created_at', type: 'datetime', precision: 3 })
|
||||
createdAt: Date
|
||||
|
||||
@Column({ name: 'body_bytes', type: 'blob' })
|
||||
bodyBytes: Buffer
|
||||
|
||||
@Column({ type: 'binary', length: 64, unique: true })
|
||||
signature: Buffer
|
||||
|
||||
@Column({ name: 'protocol_version', type: 'varchar', length: 255, default: '1' })
|
||||
protocolVersion: string
|
||||
|
||||
@Column({ type: 'bigint' })
|
||||
nr: number
|
||||
|
||||
@Column({ name: 'running_hash', type: 'binary', length: 48 })
|
||||
runningHash: Buffer
|
||||
|
||||
@Column({
|
||||
name: 'account_balance',
|
||||
type: 'decimal',
|
||||
precision: 40,
|
||||
scale: 20,
|
||||
nullable: false,
|
||||
default: 0,
|
||||
transformer: DecimalTransformer,
|
||||
})
|
||||
accountBalanceConfirmedAt: Decimal
|
||||
|
||||
@Column({ name: 'iota_milestone', type: 'bigint', nullable: true })
|
||||
iotaMilestone?: number
|
||||
|
||||
@Column({ name: 'confirmed_at', type: 'datetime' })
|
||||
confirmedAt: Date
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export { Account } from './0002-refactor_add_community/Account'
|
||||
export { Account } from './0003-refactor_transaction_recipe/Account'
|
||||
|
||||
@ -1 +1 @@
|
||||
export { Community } from './0002-refactor_add_community/Community'
|
||||
export { Community } from './0003-refactor_transaction_recipe/Community'
|
||||
|
||||
1
dlt-database/entity/Transaction.ts
Normal file
1
dlt-database/entity/Transaction.ts
Normal file
@ -0,0 +1 @@
|
||||
export { Transaction } from './0003-refactor_transaction_recipe/Transaction'
|
||||
@ -1,19 +1,17 @@
|
||||
import { Account } from './Account'
|
||||
import { AccountCommunity } from './AccountCommunity'
|
||||
import { Community } from './Community'
|
||||
import { ConfirmedTransaction } from './ConfirmedTransaction'
|
||||
import { InvalidTransaction } from './InvalidTransaction'
|
||||
import { Migration } from './Migration'
|
||||
import { TransactionRecipe } from './TransactionRecipe'
|
||||
import { Transaction } from './Transaction'
|
||||
import { User } from './User'
|
||||
|
||||
export const entities = [
|
||||
AccountCommunity,
|
||||
Account,
|
||||
Community,
|
||||
ConfirmedTransaction,
|
||||
InvalidTransaction,
|
||||
Migration,
|
||||
TransactionRecipe,
|
||||
Transaction,
|
||||
User,
|
||||
]
|
||||
|
||||
97
dlt-database/migrations/0003-refactor_transaction_recipe.ts
Normal file
97
dlt-database/migrations/0003-refactor_transaction_recipe.ts
Normal file
@ -0,0 +1,97 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
// write upgrade logic as parameter of queryFn
|
||||
await queryFn(`DROP TABLE \`transaction_recipes\`;`)
|
||||
await queryFn(`DROP TABLE \`confirmed_transactions\`;`)
|
||||
|
||||
await queryFn(
|
||||
`ALTER TABLE \`accounts\` MODIFY COLUMN \`derivation_index\` int(10) unsigned NULL DEFAULT NULL;`,
|
||||
)
|
||||
await queryFn(
|
||||
`ALTER TABLE \`accounts\` ADD COLUMN \`account_balance_created_at\` decimal(40,20) NOT NULL DEFAULT 0 AFTER \`balance_date\`;`,
|
||||
)
|
||||
await queryFn(
|
||||
`ALTER TABLE \`accounts\` ADD COLUMN \`balance_created_at_date\` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) AFTER \`account_balance_created_at\`;`,
|
||||
)
|
||||
|
||||
await queryFn(
|
||||
`CREATE TABLE \`transactions\` (
|
||||
\`id\` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
\`iota_message_id\` varbinary(32) DEFAULT NULL,
|
||||
\`backend_transaction_id\` bigint unsigned DEFAULT NULL,
|
||||
\`paring_transaction_id\` bigint unsigned DEFAULT NULL,
|
||||
\`signing_account_id\` int unsigned DEFAULT NULL,
|
||||
\`recipient_account_id\` int unsigned DEFAULT NULL,
|
||||
\`sender_community_id\` int unsigned NOT NULL,
|
||||
\`recipient_community_id\` int unsigned DEFAULT NULL,
|
||||
\`amount\` decimal(40, 20) DEFAULT NULL,
|
||||
\`account_balance_created_at\` decimal(40, 20) NOT NULL,
|
||||
\`type\` tinyint NOT NULL,
|
||||
\`created_at\` datetime(3) NOT NULL,
|
||||
\`body_bytes\` blob NOT NULL,
|
||||
\`signature\` varbinary(64) NOT NULL,
|
||||
\`protocol_version\` varchar(255) NOT NULL DEFAULT '1',
|
||||
\`nr\` bigint NOT NULL,
|
||||
\`running_hash\` varbinary(48) NOT NULL,
|
||||
\`account_balance\` decimal(40, 20) NOT NULL DEFAULT 0.00000000000000000000,
|
||||
\`iota_milestone\` bigint DEFAULT NULL,
|
||||
\`confirmed_at\` datetime NOT NULL,
|
||||
PRIMARY KEY (\`id\`),
|
||||
UNIQUE KEY \`signature\` (\`signature\`),
|
||||
FOREIGN KEY (\`signing_account_id\`) REFERENCES accounts(id),
|
||||
FOREIGN KEY (\`recipient_account_id\`) REFERENCES accounts(id),
|
||||
FOREIGN KEY (\`sender_community_id\`) REFERENCES communities(id),
|
||||
FOREIGN KEY (\`recipient_community_id\`) REFERENCES communities(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`,
|
||||
)
|
||||
|
||||
await queryFn(`ALTER TABLE \`communities\` ADD UNIQUE(\`iota_topic\`);`)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
CREATE TABLE IF NOT EXISTS \`transaction_recipes\` (
|
||||
\`id\` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
\`iota_message_id\` binary(32) DEFAULT NULL,
|
||||
\`signing_account_id\` int(10) unsigned NOT NULL,
|
||||
\`recipient_account_id\` int(10) unsigned DEFAULT NULL,
|
||||
\`sender_community_id\` int(10) unsigned NOT NULL,
|
||||
\`recipient_community_id\` int(10) unsigned DEFAULT NULL,
|
||||
\`amount\` decimal(40,20) DEFAULT NULL,
|
||||
\`type\` tinyint unsigned NOT NULL,
|
||||
\`created_at\` datetime(3) NOT NULL,
|
||||
\`body_bytes\` BLOB NOT NULL,
|
||||
\`signature\` binary(64) NOT NULL,
|
||||
\`protocol_version\` int(10) NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (\`id\`),
|
||||
FOREIGN KEY (\`signing_account_id\`) REFERENCES accounts(id),
|
||||
FOREIGN KEY (\`recipient_account_id\`) REFERENCES accounts(id),
|
||||
FOREIGN KEY (\`sender_community_id\`) REFERENCES communities(id),
|
||||
FOREIGN KEY (\`recipient_community_id\`) REFERENCES communities(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`)
|
||||
|
||||
await queryFn(`
|
||||
CREATE TABLE IF NOT EXISTS \`confirmed_transactions\` (
|
||||
\`id\` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
\`transaction_recipe_id\` bigint unsigned NOT NULL,
|
||||
\`nr\` bigint unsigned NOT NULL,
|
||||
\`running_hash\` binary(48) NOT NULL,
|
||||
\`account_id\` int(10) unsigned NOT NULL,
|
||||
\`account_balance\` decimal(40,20) NOT NULL DEFAULT 0,
|
||||
\`iota_milestone\` bigint NOT NULL,
|
||||
\`confirmed_at\` datetime NOT NULL,
|
||||
PRIMARY KEY (\`id\`),
|
||||
FOREIGN KEY (\`transaction_recipe_id\`) REFERENCES transaction_recipes(id),
|
||||
FOREIGN KEY (\`account_id\`) REFERENCES accounts(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`)
|
||||
|
||||
await queryFn(
|
||||
`ALTER TABLE \`accounts\` MODIFY COLUMN \`derivation_index\` int(10) unsigned NOT NULL;`,
|
||||
)
|
||||
await queryFn(`ALTER TABLE \`accounts\` DROP COLUMN \`account_balance_created_at\`;`)
|
||||
await queryFn(`ALTER TABLE \`accounts\` DROP COLUMN \`balance_created_at_date\`;`)
|
||||
await queryFn(`DROP TABLE \`transactions\`;`)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user