import { RxCollection, RxDocument, RxJsonSchema } from "rxdb" import { Transaction } from "../../../metaverse-ts" import { Observable, BehaviorSubject, merge, from } from "rxjs" import { map, switchMap, // startWith, // throttleTime, take, skip, debounceTime, startWith, } from "rxjs/operators" import { MetaverseLightwalletDatabase } from "./database" export interface TransactionDocType { hash: string confirmed_at?: number rawtx: string, height?: number, } export type TransactionDocMethods = {} export type TransactionDocument = RxDocument< TransactionDocType, TransactionDocMethods > export type TransactionCollection = RxCollection< TransactionDocType, TransactionDocMethods, TransactionCollectionMethods > export type TransactionCollectionMethods = { count$: () => Observable countAll: () => Promise watch$: (debounce?: number) => Observable latest: () => Promise latest$: () => BehaviorSubject | null> clear: () => Promise add: ( this: TransactionCollection, txid: string, serializedTransaction: string, height: number, ) => Promise> } export async function initTransactionCollection( database: MetaverseLightwalletDatabase, ): Promise { const transactionCollection = await database.collection< TransactionDocType, TransactionDocMethods, TransactionCollectionMethods >({ name: "transactions", schema: transactionSchema, methods: {}, statics: transactionCollectionMethods, }) transactionCollection.postInsert(async (type, data)=>{ const transaction = Transaction.decode(data.rawtx).toJSON('mainnet') // console.log(transaction.version) let index = 0 for(const output of transaction.outputs){ await database.outputs.insert({txid: data.hash, index, ...output, spent: false, height: data.height, address: output.address || ""}) index++ } for(const input of transaction.inputs){ const output = await database.outputs.findOne({ selector: { txid: input.prevOutId, index: input.prevOutIndex}}).exec() if(output){ await output.update({$set: {spent: true}}) // output.spent = true // console.info(`marked output ${input.prevOutId.toString('hex')}-${input.prevOutIndex} as spent`) // await output.save() } } return data }, false) return transactionCollection } export const transactionCollectionMethods: TransactionCollectionMethods = { count$: function (this: TransactionCollection) { return this.find().$.pipe(map((txs) => txs.length)) }, countAll: async function (this: TransactionCollection) { const allDocs = await this.find().exec() return allDocs.length }, watch$: function (this: TransactionCollection, debounce = 2000) { return merge( this.$.pipe(take(1), startWith(0), switchMap(() => from(this.find().exec()))), this.$.pipe(skip(1), debounceTime(debounce), switchMap(() => this.find().$)), ) }, latest$: function (this: TransactionCollection) { return this.findOne().sort({ height: "desc" }).$ }, latest: async function (this: TransactionCollection) { const latestTx = await this.find().sort({ height: "desc" }).exec() return latestTx.length ? latestTx[0].toJSON() : undefined }, clear: async function (this: TransactionCollection) { const deleted = await this.find().remove() return deleted.length }, add: async function ( this: TransactionCollection, txid: string, serializedTransaction: string, height: number, ) { return this.insert({ hash: txid, height, rawtx: serializedTransaction, }) }, } export const transactionSchema: RxJsonSchema = { title: "transaction", version: 0, description: "Metaverse transactions", type: "object", indexes: ["height"], properties: { hash: { type: "string", primary: true, }, rawtx: { type: "string", }, height: { type: "integer", }, confirmed_at: { type: "integer", }, }, }