diff --git a/.env.dev b/.env.dev index 4d660c00..c06bbe11 100644 --- a/.env.dev +++ b/.env.dev @@ -12,26 +12,34 @@ BITCOIN_RPC_PASS=apd3g41pkl # JWT configuration JWT_SECRET=secret -# Database configuration DATABASE_TYPE=postgres -DATABASE_HOST=localhost +DATABASE_HOST=192.168.1.253 DATABASE_PORT=5432 -DATABASE_NAME=supersats_182_testnet +DATABASE_NAME=supersats_testnet DATABASE_USER=postgres DATABASE_PASSWORD=Codelight123 +MARKETPLACE_DATABASE_TYPE=postgres +MARKETPLACE_DATABASE_HOST=192.168.1.253 +MARKETPLACE_DATABASE_PORT=5432 +MARKETPLACE_DATABASE_NAME=supersats_testnet_marketplace +MARKETPLACE_DATABASE_USER=postgres +MARKETPLACE_DATABASE_PASSWORD=Codelight123 + # Encryption configuration ENCRYPTION_KEY=9a846aa04a6ccc797b24ee11323f03f219ce485123f133d7c4fe7a4e47c36a50 ENCRYPTION_ALGORITHM=aes-256-cbc ENCRYPTION_IV_LENGTH=16 -RUNE_TAG=R - -ODR_URL=http://222.253.82.244 +# Testnet +ODR_URL=http://192.168.1.239 ODR_PORT=8088 +BTC_NETWORK=testnet + +# Config service fee SELLER_SERVICE_FEE=0.015 PLATFORM_SERVICE_FEE=1000 PLATFORM_FEE_ADDRESS=tb1p6q553c7pr7grtshzyhpj2u3djuqazqsvpmtl3ljnyrqhc4h2a45s0g57vc -BTC_NETWORK=testnet \ No newline at end of file +ADMIN_SECRET=ADMIN_SECRET \ No newline at end of file diff --git a/.github/workflows/deploy-dev-testnet.yaml b/.github/workflows/deploy-dev-testnet.yaml new file mode 100644 index 00000000..99a26f44 --- /dev/null +++ b/.github/workflows/deploy-dev-testnet.yaml @@ -0,0 +1,31 @@ +name: Deployment runebeta +permissions: + id-token: write + contents: read +on: + push: + branches: + - develop + + workflow_dispatch: + +jobs: + release: + # if: github.ref_name == 'master' || github.event_name == 'workflow_dispatch' + runs-on: [self-hosted] + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + - name: Build docker image + run: | + docker build . -f Dockerfile -t ${{ github.event.repository.name }} + - name: stop container + run: docker stop ${{ github.event.repository.name }} || echo + - name: Running container + run: | + docker run --rm -p 3000:3000 \ + --network=host --name ${{ github.event.repository.name }} --env-file .env.dev -d ${{ github.event.repository.name }} + + - name: Health-check + run: sleep 30 && curl localhost:3000 diff --git a/.github/workflows/deploy-runebeta-mainnet.yaml b/.github/workflows/deploy-runebeta-mainnet.yaml index 447414cb..dcb8f1ec 100644 --- a/.github/workflows/deploy-runebeta-mainnet.yaml +++ b/.github/workflows/deploy-runebeta-mainnet.yaml @@ -36,5 +36,3 @@ jobs: echo ${{ secrets.KUBE_CONFIG_DATA }} | base64 -d > $HOME/.kube/config kubectl rollout restart deployment/runebeta-mainnet kubectl rollout restart deployment/runebeta-mainnet-scheduler - - diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml deleted file mode 100644 index af557c69..00000000 --- a/.github/workflows/deployment.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: Deployment runebeta -permissions: - id-token: write - contents: read -on: - push: - branches: - - '*' # matches every branch that doesn't contain a '/' - - '*/*' # matches every branch containing a single '/' - - '**' - workflow_dispatch: - -jobs: - release: - # if: github.ref_name == 'master' || github.event_name == 'workflow_dispatch' - runs-on: [self-hosted] - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.ref_name }} - - name: Build docker image - run: | - docker build . -f Dockerfile -t ${{ github.event.repository.name }} - - name: stop container - run: docker stop ${{ github.event.repository.name }} || echo - - name: Running container - run: | - docker run --rm -p 3000:3000 \ - --network=host --name ${{ github.event.repository.name }} --env-file .env.dev -d ${{ github.event.repository.name }} - - - name: Health-check - run: sleep 30 && curl localhost:3000 \ No newline at end of file diff --git a/db_example/database_index.sql b/db_example/database_index.sql index 96bd64f6..f8fa358a 100644 --- a/db_example/database_index.sql +++ b/db_example/database_index.sql @@ -20,3 +20,7 @@ CREATE INDEX transaction_rune_entries_rune_id_idx ON public.transaction_rune_ent CREATE INDEX transactions_tx_hash_idx ON public.transactions (tx_hash); CREATE INDEX transactions_block_height_idx ON public.transactions (block_height); + +CREATE INDEX txid_runes_tx_hash_idx ON public.txid_runes (tx_hash); +CREATE INDEX txid_runes_tx_index_idx ON public.txid_runes (tx_index); +CREATE INDEX txid_runes_block_height_idx ON public.txid_runes (block_height); diff --git a/service/ormconfig.ts b/service/ormconfig.ts index f8e7e19e..df78065c 100644 --- a/service/ormconfig.ts +++ b/service/ormconfig.ts @@ -1,17 +1,25 @@ import * as dotenv from 'dotenv'; +import { + MARKETPLACE_DATABASE_TYPE, + MARKETPLACE_DATABASE_HOST, + MARKETPLACE_DATABASE_PORT, + MARKETPLACE_DATABASE_USER, + MARKETPLACE_DATABASE_PASSWORD, + MARKETPLACE_DATABASE_NAME, +} from 'src/environments'; import { NamingStrategy } from 'src/modules/database/naming.strategy'; import { DataSource } from 'typeorm'; dotenv.config(); export const connectionSource = new DataSource({ - type: 'postgres', - host: process.env.DATABASE_HOST, - port: parseInt(process.env.DATABASE_PORT, 10), - username: process.env.DATABASE_USER, - password: process.env.DATABASE_PASSWORD, - database: process.env.DATABASE_NAME, - entities: [`${__dirname}/**/*.entity{.ts,.js}`], + type: MARKETPLACE_DATABASE_TYPE as any, + host: MARKETPLACE_DATABASE_HOST, + port: MARKETPLACE_DATABASE_PORT, + username: MARKETPLACE_DATABASE_USER, + password: MARKETPLACE_DATABASE_PASSWORD, + database: MARKETPLACE_DATABASE_NAME, + entities: [`${__dirname}/**/marketplace/*.entity{.ts,.js}`], namingStrategy: new NamingStrategy(), migrationsTableName: '__migrations', migrations: ['./migrations/**/*.ts'], diff --git a/service/package-lock.json b/service/package-lock.json index 3e724efb..4877cacd 100644 --- a/service/package-lock.json +++ b/service/package-lock.json @@ -31,6 +31,7 @@ "class-validator": "^0.14.1", "compression": "^1.7.4", "cron": "^3.1.6", + "dayjs": "^1.11.10", "ioredis": "^5.3.2", "lodash": "^4.17.21", "pg": "^8.11.3", diff --git a/service/package.json b/service/package.json index 392368e7..846c4cc9 100644 --- a/service/package.json +++ b/service/package.json @@ -46,6 +46,7 @@ "class-validator": "^0.14.1", "compression": "^1.7.4", "cron": "^3.1.6", + "dayjs": "^1.11.10", "ioredis": "^5.3.2", "lodash": "^4.17.21", "pg": "^8.11.3", diff --git a/service/src/app.service.ts b/service/src/app.service.ts index 314183d7..f228df19 100644 --- a/service/src/app.service.ts +++ b/service/src/app.service.ts @@ -26,7 +26,7 @@ export class AppService implements OnModuleInit { this.taskService.addNewJob( 'calculateRuneStats', async () => await this.statsService.calculateNetworkStats(), - '*/3', + '*/1', ); } } diff --git a/service/src/common/handlers/runes/buyer.ts b/service/src/common/handlers/runes/buyer.ts index 3435da53..0317faf4 100644 --- a/service/src/common/handlers/runes/buyer.ts +++ b/service/src/common/handlers/runes/buyer.ts @@ -247,6 +247,7 @@ export namespace BuyerHandler { if (!runeId) { throw new Error('Invalid Rune ID'); } + const sellerEdict = new Edict({ id: runeId as RuneId, amount: _seller_total_tokens - _seller_listing_item, @@ -291,6 +292,7 @@ export namespace BuyerHandler { for (let i = 0; i < _all_outputs_except_change.length; i++) { psbt.addOutput(_all_outputs_except_change[i] as any); } + /// Adding change fee: const fee = await calculateTxBytesFee( psbt.txInputs.length > 2 ? psbt.txInputs.length : 4, @@ -307,6 +309,7 @@ export namespace BuyerHandler { fee - _platform_fee - DUST_AMOUNT; + if (changeValue < 0) { throw new Error(`Your wallet address doesn't have enough funds to buy this rune. Price: ${satToBtc(_seller_listing_prices)} BTC diff --git a/service/src/common/handlers/runes/seller.ts b/service/src/common/handlers/runes/seller.ts index 77b83d3a..fa2ab81a 100644 --- a/service/src/common/handlers/runes/seller.ts +++ b/service/src/common/handlers/runes/seller.ts @@ -74,11 +74,11 @@ export namespace SellerHandler { serviceFee, Number(listing.seller.runeItem.outputValue), ); + psbt.addOutput({ address: listing.seller.sellerReceiveAddress, value: sellerOutput, }); - listing.seller.unsignedListingPSBTBase64 = psbt.toBase64(); return listing; @@ -146,7 +146,6 @@ export namespace SellerHandler { const serviceFee = req.price * Number(runeItem.tokenValue) * SELLER_SERVICE_FEE; - console.log('serviceFee :>> ', serviceFee); // verify that the ordItem's selling price matches the output value with makerFeeBp const output = psbt.txOutputs[0]; const expectedOutput = getSellerRuneOutputValue( diff --git a/service/src/environments/index.ts b/service/src/environments/index.ts index 7965676a..8a9384cd 100644 --- a/service/src/environments/index.ts +++ b/service/src/environments/index.ts @@ -15,7 +15,7 @@ const JWT_SECRET: string = process.env.JWT_SECRET || 'JWT_SECRET'; const ODR_URL: string = process.env.ODR_URL || 'localhost'; const ODR_PORT: number = +process.env.ODR_PORT || 18089; -// Database +// Indexer Database const DATABASE_TYPE: string = process.env.DATABASE_TYPE || NEED_TO_CONFIGURED; const DATABASE_HOST: string = process.env.DATABASE_HOST || NEED_TO_CONFIGURED; const DATABASE_NAME: string = process.env.DATABASE_NAME || NEED_TO_CONFIGURED; @@ -24,6 +24,20 @@ const DATABASE_PASSWORD: string = process.env.DATABASE_PASSWORD || NEED_TO_CONFIGURED; const DATABASE_PORT: number = +process.env.DATABASE_PORT || 5432; +// Marketplace Database +const MARKETPLACE_DATABASE_TYPE: string = + process.env.MARKETPLACE_DATABASE_TYPE || NEED_TO_CONFIGURED; +const MARKETPLACE_DATABASE_HOST: string = + process.env.MARKETPLACE_DATABASE_HOST || NEED_TO_CONFIGURED; +const MARKETPLACE_DATABASE_NAME: string = + process.env.MARKETPLACE_DATABASE_NAME || NEED_TO_CONFIGURED; +const MARKETPLACE_DATABASE_USER: string = + process.env.MARKETPLACE_DATABASE_USER || NEED_TO_CONFIGURED; +const MARKETPLACE_DATABASE_PASSWORD: string = + process.env.MARKETPLACE_DATABASE_PASSWORD || NEED_TO_CONFIGURED; +const MARKETPLACE_DATABASE_PORT: number = + +process.env.MARKETPLACE_DATABASE_PORT || 5432; + // Redis cache const REDIS_HOST: string = process.env.REDIS_HOST || 'localhost'; const REDIS_PORT: number = +process.env.REDIS_PORT || 6379; @@ -69,6 +83,12 @@ export { DATABASE_USER, DATABASE_PASSWORD, DATABASE_PORT, + MARKETPLACE_DATABASE_TYPE, + MARKETPLACE_DATABASE_HOST, + MARKETPLACE_DATABASE_NAME, + MARKETPLACE_DATABASE_USER, + MARKETPLACE_DATABASE_PASSWORD, + MARKETPLACE_DATABASE_PORT, REDIS_HOST, REDIS_PORT, CACHE_TTL, diff --git a/service/src/modules/auth/auth.providers.ts b/service/src/modules/auth/auth.providers.ts index 2c2e3dbc..e6b29150 100644 --- a/service/src/modules/auth/auth.providers.ts +++ b/service/src/modules/auth/auth.providers.ts @@ -1,15 +1,15 @@ import { DataSource } from 'typeorm'; -import { User } from '../database/entities/user.entity'; -import { TransactionOut } from '../database/entities/transaction-out.entity'; -import { Transaction } from '../database/entities/transaction.entity'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; -import { Order } from '../database/entities/order.entity'; +import { User } from '../database/entities/marketplace/user.entity'; +import { TransactionOut } from '../database/entities/indexer/transaction-out.entity'; +import { Transaction } from '../database/entities/indexer/transaction.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; +import { Order } from '../database/entities/marketplace/order.entity'; export const authProviders = [ { provide: 'USER_REPOSITORY', useFactory: (dataSource: DataSource) => dataSource.getRepository(User), - inject: ['DATA_SOURCE'], + inject: ['MARKETPLACE_DATA_SOURCE'], }, { provide: 'TRANSACTION_OUT_REPOSITORY', @@ -32,6 +32,6 @@ export const authProviders = [ { provide: 'ORDER_REPOSITORY', useFactory: (dataSource: DataSource) => dataSource.getRepository(Order), - inject: ['DATA_SOURCE'], + inject: ['MARKETPLACE_DATA_SOURCE'], }, ]; diff --git a/service/src/modules/database/database.providers.ts b/service/src/modules/database/database.providers.ts index bdf1b75d..2e780fa8 100644 --- a/service/src/modules/database/database.providers.ts +++ b/service/src/modules/database/database.providers.ts @@ -5,6 +5,12 @@ import { DATABASE_PASSWORD, DATABASE_PORT, DATABASE_USER, + MARKETPLACE_DATABASE_TYPE, + MARKETPLACE_DATABASE_HOST, + MARKETPLACE_DATABASE_PORT, + MARKETPLACE_DATABASE_USER, + MARKETPLACE_DATABASE_PASSWORD, + MARKETPLACE_DATABASE_NAME, } from 'src/environments'; import { DataSource } from 'typeorm'; import { NamingStrategy } from './naming.strategy'; @@ -20,7 +26,27 @@ export const databaseProviders = [ username: DATABASE_USER, password: DATABASE_PASSWORD, database: DATABASE_NAME, - entities: [`${__dirname}/**/*.entity{.ts,.js}`], + entities: [`${__dirname}/**/indexer/*.entity{.ts,.js}`], + namingStrategy: new NamingStrategy(), + migrationsTableName: '__migrations', + migrations: ['./migrations/**/*.ts'], + synchronize: false, + }); + + return dataSource.initialize(); + }, + }, + { + provide: 'MARKETPLACE_DATA_SOURCE', + useFactory: async () => { + const dataSource = new DataSource({ + type: MARKETPLACE_DATABASE_TYPE as any, + host: MARKETPLACE_DATABASE_HOST, + port: MARKETPLACE_DATABASE_PORT, + username: MARKETPLACE_DATABASE_USER, + password: MARKETPLACE_DATABASE_PASSWORD, + database: MARKETPLACE_DATABASE_NAME, + entities: [`${__dirname}/**/marketplace/*.entity{.ts,.js}`], namingStrategy: new NamingStrategy(), migrationsTableName: '__migrations', migrations: ['./migrations/**/*.ts'], diff --git a/service/src/modules/database/entities/index.ts b/service/src/modules/database/entities/index.ts deleted file mode 100644 index 4a7b8007..00000000 --- a/service/src/modules/database/entities/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './etch_rune.entity'; -export * from './rune_stat.entity'; diff --git a/service/src/modules/database/entities/block-header.entity.ts b/service/src/modules/database/entities/indexer/block-header.entity.ts similarity index 100% rename from service/src/modules/database/entities/block-header.entity.ts rename to service/src/modules/database/entities/indexer/block-header.entity.ts diff --git a/service/src/modules/database/entities/block.entity.ts b/service/src/modules/database/entities/indexer/block.entity.ts similarity index 100% rename from service/src/modules/database/entities/block.entity.ts rename to service/src/modules/database/entities/indexer/block.entity.ts diff --git a/service/src/modules/database/entities/content-type-count.entity.ts b/service/src/modules/database/entities/indexer/content-type-count.entity.ts similarity index 100% rename from service/src/modules/database/entities/content-type-count.entity.ts rename to service/src/modules/database/entities/indexer/content-type-count.entity.ts diff --git a/service/src/modules/database/entities/indexer/index.ts b/service/src/modules/database/entities/indexer/index.ts new file mode 100644 index 00000000..55059661 --- /dev/null +++ b/service/src/modules/database/entities/indexer/index.ts @@ -0,0 +1,2 @@ +export * from '../marketplace/etch_rune.entity'; +export * from '../marketplace/rune_stat.entity'; diff --git a/service/src/modules/database/entities/indexing_block_timestamp.entity.ts b/service/src/modules/database/entities/indexer/indexing_block_timestamp.entity.ts similarity index 100% rename from service/src/modules/database/entities/indexing_block_timestamp.entity.ts rename to service/src/modules/database/entities/indexer/indexing_block_timestamp.entity.ts diff --git a/service/src/modules/database/entities/inscription.entity.ts b/service/src/modules/database/entities/indexer/inscription.entity.ts similarity index 100% rename from service/src/modules/database/entities/inscription.entity.ts rename to service/src/modules/database/entities/indexer/inscription.entity.ts diff --git a/service/src/modules/database/entities/inscription_entry.entity.ts b/service/src/modules/database/entities/indexer/inscription_entry.entity.ts similarity index 100% rename from service/src/modules/database/entities/inscription_entry.entity.ts rename to service/src/modules/database/entities/indexer/inscription_entry.entity.ts diff --git a/service/src/modules/database/entities/outpoint-rune-balance.entity.ts b/service/src/modules/database/entities/indexer/outpoint-rune-balance.entity.ts similarity index 96% rename from service/src/modules/database/entities/outpoint-rune-balance.entity.ts rename to service/src/modules/database/entities/indexer/outpoint-rune-balance.entity.ts index ea698a37..94ea87b1 100644 --- a/service/src/modules/database/entities/outpoint-rune-balance.entity.ts +++ b/service/src/modules/database/entities/indexer/outpoint-rune-balance.entity.ts @@ -26,9 +26,6 @@ export class OutpointRuneBalance { @Column({ type: 'int8' }) tx_index: number; - @Column({ type: 'boolean' }) - spent: boolean; - @Column({ type: 'varchar' }) address: string; diff --git a/service/src/modules/database/entities/outpoint_value.entity.ts b/service/src/modules/database/entities/indexer/outpoint_value.entity.ts similarity index 100% rename from service/src/modules/database/entities/outpoint_value.entity.ts rename to service/src/modules/database/entities/indexer/outpoint_value.entity.ts diff --git a/service/src/modules/database/entities/rune-entry.entity.ts b/service/src/modules/database/entities/indexer/rune-entry.entity.ts similarity index 63% rename from service/src/modules/database/entities/rune-entry.entity.ts rename to service/src/modules/database/entities/indexer/rune-entry.entity.ts index 6ed188d3..680d3737 100644 --- a/service/src/modules/database/entities/rune-entry.entity.ts +++ b/service/src/modules/database/entities/indexer/rune-entry.entity.ts @@ -4,11 +4,9 @@ import { Entity, JoinColumn, OneToMany, - OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; import { OutpointRuneBalance } from './outpoint-rune-balance.entity'; -import { RuneStat } from './rune_stat.entity'; @Entity({ synchronize: false }) export class TransactionRuneEntry { @@ -18,11 +16,15 @@ export class TransactionRuneEntry { @Column({ type: 'varchar' }) tx_hash: string; + @Column({ type: 'int8' }) + block_height: string; + + @Column({ type: 'int4' }) + tx_index: string; + @Column({ type: 'varchar' }) rune_id: string; - rune_hex: string; - @Column({ type: 'text' }) burned: string; @@ -32,24 +34,45 @@ export class TransactionRuneEntry { @Column({ type: 'varchar' }) etching: string; + @Column({ type: 'varchar' }) + parent: string; + + @Column({ type: 'bool' }) + mintable: boolean; + + @Column({ type: 'bool' }) + turbo: boolean; + @Column({ type: 'int8' }) - mints: string; + mints: bigint; + + @Column({ type: 'int8' }) + premine: bigint; @Column({ type: 'int8' }) number: string; @Column({ type: 'jsonb' }) - mint_entry: any; + terms: any; @Column({ type: 'text' }) rune: string; + @Column({ type: 'int8', nullable: true, default: 0, name: 'height_start' }) + start_block: number; + + @Column({ type: 'int8', nullable: true, default: 0, name: 'height_end' }) + end_block: number; + @Column({ type: 'int4' }) spacers: number; @Column({ type: 'text' }) supply: string; + @Column({ type: 'numeric' }) + remaining: bigint; + @Column({ type: 'varchar' }) spaced_rune: string; @@ -59,6 +82,11 @@ export class TransactionRuneEntry { @Column({ type: 'int4' }) timestamp: number; + @Column({ type: 'varchar' }) + mint_type: string; + + rune_hex: string; + @OneToMany( () => OutpointRuneBalance, (outpointRuneBalance) => outpointRuneBalance.rune, @@ -66,9 +94,9 @@ export class TransactionRuneEntry { @JoinColumn({ name: 'rune_id', referencedColumnName: 'rune_id' }) outpointRuneBalances: OutpointRuneBalance[]; - @OneToOne(() => RuneStat, (runeStat) => runeStat.rune) - @JoinColumn({ name: 'rune_id', referencedColumnName: 'rune_id' }) - stat: RuneStat; + // @OneToOne(() => RuneStat, (runeStat) => runeStat.rune) + // @JoinColumn({ name: 'rune_id', referencedColumnName: 'rune_id' }) + // stat: RuneStat; @AfterLoad() afterLoad() { diff --git a/service/src/modules/database/entities/satpoint.entity.ts b/service/src/modules/database/entities/indexer/satpoint.entity.ts similarity index 100% rename from service/src/modules/database/entities/satpoint.entity.ts rename to service/src/modules/database/entities/indexer/satpoint.entity.ts diff --git a/service/src/modules/database/entities/transaction-ins.entity.ts b/service/src/modules/database/entities/indexer/transaction-ins.entity.ts similarity index 100% rename from service/src/modules/database/entities/transaction-ins.entity.ts rename to service/src/modules/database/entities/indexer/transaction-ins.entity.ts diff --git a/service/src/modules/database/entities/transaction-out.entity.ts b/service/src/modules/database/entities/indexer/transaction-out.entity.ts similarity index 96% rename from service/src/modules/database/entities/transaction-out.entity.ts rename to service/src/modules/database/entities/indexer/transaction-out.entity.ts index 36e1ee0d..91fda432 100644 --- a/service/src/modules/database/entities/transaction-out.entity.ts +++ b/service/src/modules/database/entities/indexer/transaction-out.entity.ts @@ -37,9 +37,6 @@ export class TransactionOut { @Column({ type: 'varchar' }) address: string; - @Column({ type: 'bool' }) - spent: boolean; - @Column({ type: 'varchar', name: 'runestone' }) rune_stone: RuneStone; diff --git a/service/src/modules/database/entities/transaction.entity.ts b/service/src/modules/database/entities/indexer/transaction.entity.ts similarity index 100% rename from service/src/modules/database/entities/transaction.entity.ts rename to service/src/modules/database/entities/indexer/transaction.entity.ts diff --git a/service/src/modules/database/entities/txid-rune.entity.ts b/service/src/modules/database/entities/indexer/txid-rune.entity.ts similarity index 92% rename from service/src/modules/database/entities/txid-rune.entity.ts rename to service/src/modules/database/entities/indexer/txid-rune.entity.ts index 39c9beb5..41595c96 100644 --- a/service/src/modules/database/entities/txid-rune.entity.ts +++ b/service/src/modules/database/entities/indexer/txid-rune.entity.ts @@ -15,9 +15,6 @@ export class TxidRune { @Column({ type: 'int8' }) tx_index: number; - @Column({ type: 'varchar' }) - rune_id: string; - @OneToMany( () => OutpointRuneBalance, (outpointRuneBalance) => outpointRuneBalance.rune, diff --git a/service/src/modules/database/entities/etch_rune.entity.ts b/service/src/modules/database/entities/marketplace/etch_rune.entity.ts similarity index 91% rename from service/src/modules/database/entities/etch_rune.entity.ts rename to service/src/modules/database/entities/marketplace/etch_rune.entity.ts index 542a24c3..095bca70 100644 --- a/service/src/modules/database/entities/etch_rune.entity.ts +++ b/service/src/modules/database/entities/marketplace/etch_rune.entity.ts @@ -31,6 +31,9 @@ export class EtchRune { @Column({ type: 'text' }) mint_tx_hex: string; + @Column({ type: 'text', nullable: true }) + mint_tx_id: string; + @Column({ type: 'varchar' }) status: EEtchRuneStatus; diff --git a/service/src/modules/database/entities/order.entity.ts b/service/src/modules/database/entities/marketplace/order.entity.ts similarity index 95% rename from service/src/modules/database/entities/order.entity.ts rename to service/src/modules/database/entities/marketplace/order.entity.ts index f071cec7..0d0c36bd 100644 --- a/service/src/modules/database/entities/order.entity.ts +++ b/service/src/modules/database/entities/marketplace/order.entity.ts @@ -8,7 +8,7 @@ import { UpdateDateColumn, } from 'typeorm'; import { User } from './user.entity'; -import { TransactionRuneEntry } from './rune-entry.entity'; +import { TransactionRuneEntry } from '../indexer/rune-entry.entity'; @Entity() export class Order { diff --git a/service/src/modules/database/entities/rune_stat.entity.ts b/service/src/modules/database/entities/marketplace/rune_stat.entity.ts similarity index 86% rename from service/src/modules/database/entities/rune_stat.entity.ts rename to service/src/modules/database/entities/marketplace/rune_stat.entity.ts index 5715f5ac..05673e46 100644 --- a/service/src/modules/database/entities/rune_stat.entity.ts +++ b/service/src/modules/database/entities/marketplace/rune_stat.entity.ts @@ -1,13 +1,6 @@ -import { - Column, - Entity, - Index, - OneToOne, - PrimaryGeneratedColumn, -} from 'typeorm'; -import { TransactionRuneEntry } from './rune-entry.entity'; +import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; import { IEntry } from 'src/common/interfaces/rune.interface'; -import BaseTable from '../base-table'; +import BaseTable from '../../base-table'; @Entity() export class RuneStat extends BaseTable { @@ -27,12 +20,18 @@ export class RuneStat extends BaseTable { @Index() rune_name: string; + @Column({ type: 'decimal', nullable: true }) + block: bigint; + @Column({ type: 'decimal', nullable: true }) total_transactions: bigint; @Column({ type: 'decimal', nullable: true, default: 0 }) total_mints: bigint; + @Column({ type: 'decimal', nullable: true, default: 0 }) + number: bigint; + @Column({ type: 'decimal', nullable: true, default: 0 }) total_burns: bigint; @@ -78,18 +77,12 @@ export class RuneStat extends BaseTable { @Column({ type: 'decimal', nullable: true }) premine: bigint; - @Column({ type: 'int8', nullable: true, default: 0 }) + @Column({ type: 'int8', nullable: true, default: 0, name: 'block_start' }) start_block: number; - @Column({ type: 'int8', nullable: true, default: 0 }) + @Column({ type: 'int8', nullable: true, default: 0, name: 'block_end' }) end_block: number; - @Column({ type: 'jsonb', nullable: true }) - height: Array; - - @Column({ type: 'jsonb', nullable: true }) - offset: Array; - @Column({ type: 'varchar', nullable: true }) etching: string; @@ -153,7 +146,4 @@ export class RuneStat extends BaseTable { @Column({ type: 'varchar', nullable: true }) mint_type: string; - - @OneToOne(() => TransactionRuneEntry, (runeEntry) => runeEntry.stat) - rune: TransactionRuneEntry; } diff --git a/service/src/modules/database/entities/user.entity.ts b/service/src/modules/database/entities/marketplace/user.entity.ts similarity index 100% rename from service/src/modules/database/entities/user.entity.ts rename to service/src/modules/database/entities/marketplace/user.entity.ts diff --git a/service/src/modules/indexers/indexers.service.ts b/service/src/modules/indexers/indexers.service.ts index d6b90500..64ae52b8 100644 --- a/service/src/modules/indexers/indexers.service.ts +++ b/service/src/modules/indexers/indexers.service.ts @@ -26,6 +26,14 @@ export class IndexersService { return res.data; } + async getBlockIndex() { + const res = await this.httpService + .get(`${ODR_URL}:${ODR_PORT}/extend/blockindexed`) + .toPromise(); + + return res.data; + } + async getBlockSyncNumber() { const res = await this.httpService .get(`${ODR_URL}:${ODR_PORT}/blockcount`) diff --git a/service/src/modules/markets/markets.controller.ts b/service/src/modules/markets/markets.controller.ts index 075db323..153eb0ed 100644 --- a/service/src/modules/markets/markets.controller.ts +++ b/service/src/modules/markets/markets.controller.ts @@ -17,7 +17,7 @@ import { import { CoreTransformInterceptor } from 'src/common/interceptors/coreTransform.interceptor'; import { AuthGuard } from 'src/common/guards/auth.guard'; import { UserDecorator } from 'src/common/decorators/user.decorator'; -import { User } from '../database/entities/user.entity'; +import { User } from '../database/entities/marketplace/user.entity'; import { IRuneListingState, ISelectPaymentUtxo, diff --git a/service/src/modules/markets/markets.providers.ts b/service/src/modules/markets/markets.providers.ts index 8111fd34..bb78a5a4 100644 --- a/service/src/modules/markets/markets.providers.ts +++ b/service/src/modules/markets/markets.providers.ts @@ -1,13 +1,19 @@ import { DataSource } from 'typeorm'; -import { Order } from '../database/entities/order.entity'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; -import { OutpointRuneBalance } from '../database/entities/outpoint-rune-balance.entity'; +import { Order } from '../database/entities/marketplace/order.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; +import { OutpointRuneBalance } from '../database/entities/indexer/outpoint-rune-balance.entity'; +import { RuneStat } from '../database/entities/indexer'; export const ordersProviders = [ { provide: 'ORDER_REPOSITORY', useFactory: (dataSource: DataSource) => dataSource.getRepository(Order), - inject: ['DATA_SOURCE'], + inject: ['MARKETPLACE_DATA_SOURCE'], + }, + { + provide: 'RUNE_STAT_REPOSITORY', + useFactory: (dataSource: DataSource) => dataSource.getRepository(RuneStat), + inject: ['MARKETPLACE_DATA_SOURCE'], }, { provide: 'RUNE_ENTRY_REPOSITORY', diff --git a/service/src/modules/markets/markets.service.ts b/service/src/modules/markets/markets.service.ts index fe6da011..805745a7 100644 --- a/service/src/modules/markets/markets.service.ts +++ b/service/src/modules/markets/markets.service.ts @@ -10,9 +10,9 @@ import { MarketRuneFilterDto, MarketRuneOrderFilterDto, } from './dto'; -import { User } from '../database/entities/user.entity'; +import { User } from '../database/entities/marketplace/user.entity'; import { Repository } from 'typeorm'; -import { Order } from '../database/entities/order.entity'; +import { Order } from '../database/entities/marketplace/order.entity'; import { IRuneListingState, IRunePostPSBTListing, @@ -25,22 +25,23 @@ import { BASE_URL } from 'src/environments'; import { AddressTxsUtxo } from '@mempool/mempool.js/lib/interfaces/bitcoin/addresses'; import { BuyerOrderDto } from './dto/buyer-order.dto'; import { MergeSingers } from 'src/common/handlers/runes/merge'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; import { UsersService } from '../users/users.service'; import { TransactionsService } from '../transactions/transactions.service'; import { BroadcastTransactionDto } from '../transactions/dto'; -import { RuneStat } from '../database/entities'; import { EOrderStatus } from 'src/common/enums'; -import { OutpointRuneBalance } from '../database/entities/outpoint-rune-balance.entity'; +import { OutpointRuneBalance } from '../database/entities/indexer/outpoint-rune-balance.entity'; +import { RuneStat } from '../database/entities/indexer'; @Injectable() export class MarketsService implements OnModuleInit { constructor( - private readonly httpService: HttpService, @Inject('ORDER_REPOSITORY') private orderRepository: Repository, @Inject('RUNE_ENTRY_REPOSITORY') private runeEntryRepository: Repository, + @Inject('RUNE_STAT_REPOSITORY') + private runeStatRepository: Repository, @Inject('OUTPOINT_RUNE_BALANCE_REPOSITORY') private outpoinBalanceRepository: Repository, private readonly usersService: UsersService, @@ -54,9 +55,8 @@ export class MarketsService implements OnModuleInit { } async getRunes(marketRuneFilterDto: MarketRuneFilterDto) { - const builder = this.runeEntryRepository - .createQueryBuilder('rune') - .leftJoinAndSelect('rune.stat', 'rune_stat') + const builder = this.runeStatRepository + .createQueryBuilder('rune_stat') .offset(marketRuneFilterDto.offset) .limit(marketRuneFilterDto.limit); @@ -91,7 +91,7 @@ export class MarketsService implements OnModuleInit { case 'created_at': builder.orderBy( - `rune.timestamp`, + `rune_stat.rune_id`, marketRuneFilterDto.sortOrder?.toLocaleUpperCase() === 'DESC' ? 'DESC' : 'ASC', @@ -101,32 +101,48 @@ export class MarketsService implements OnModuleInit { default: break; } + } else { + builder.orderBy('rune_stat.rune_id', 'ASC'); } if (marketRuneFilterDto.search) { - builder.andWhere('spaced_rune ILIKE :search', { - search: `%${marketRuneFilterDto.search.split(' ').join('_')}%`, - }); + builder.andWhere( + `rune_stat.rune_name ILIKE '%${marketRuneFilterDto.search.replace(/•/g, '')}%'`, + ); } const runes = await builder.getMany(); + const runeIds = runes.map((rune) => rune.rune_id); + const runeEntrys = {}; + if (runeIds.length) { + const arrRuneEntrys = await this.runeEntryRepository + .createQueryBuilder('rune') + .where('rune.rune_id IN (:...ids)', { ids: runeIds }) + .getMany(); + for (let index = 0; index < arrRuneEntrys.length; index++) { + const entry = arrRuneEntrys[index]; + runeEntrys[entry.rune_id] = entry; + } + } + return { total: await builder.getCount(), limit: marketRuneFilterDto.limit, offset: marketRuneFilterDto.offset, runes: runes.map((rune) => ({ - change_24h: rune?.stat?.change_24h, - floor_price: rune?.stat?.price, - last_price: rune?.stat?.ma_price, - marketcap: rune?.stat?.market_cap, - order_sold: rune?.stat?.order_sold, - token_holders: rune?.stat?.total_holders, + change_24h: rune?.change_24h, + floor_price: rune?.price, + last_price: rune?.ma_price, + marketcap: rune?.market_cap, + order_sold: rune?.order_sold, + token_holders: rune?.total_holders, id: rune.id, rune_id: rune.rune_id, - rune_hex: rune.rune_hex, - rune_name: rune.spaced_rune, - total_supply: rune?.stat?.total_supply, - total_volume: rune?.stat?.total_volume, + divisibility: runeEntrys[rune.rune_id]?.divisibility, + rune_hex: runeEntrys[rune.rune_id]?.rune_hex, + rune_name: runeEntrys[rune.rune_id]?.spaced_rune, + total_supply: rune?.total_supply, + total_volume: rune?.total_volume, })), }; } @@ -137,18 +153,6 @@ export class MarketsService implements OnModuleInit { ): Promise { const builder = this.orderRepository .createQueryBuilder('order') - .innerJoinAndMapOne( - 'order.runeInfo', - TransactionRuneEntry, - 'runeInfo', - `runeInfo.rune_id = order.rune_id`, - ) - .leftJoinAndMapOne( - 'order.runeStat', - RuneStat, - 'rune_stat', - 'rune_stat.rune_id = order.rune_id', - ) .where(`order.rune_id = :id`, { id }); if (marketRuneOrderFilterDto.status) { @@ -256,6 +260,15 @@ export class MarketsService implements OnModuleInit { builder.orderBy('order.createdAt', 'DESC'); } + const rune = await this.runeEntryRepository.findOne({ + where: { + rune_id: id, + }, + }); + if (!rune) { + throw new BadRequestException('Rune not found'); + } + const orders = await builder.getMany(); return { total, @@ -280,9 +293,10 @@ export class MarketsService implements OnModuleInit { received_address: order.status === 'completed' ? order.buyerRuneAddress : '', confirmed: order.status === 'completed', - rune_hex: order.runeInfo.rune_hex, + divisibility: rune.divisibility, rune_id: order.runeItem.id, - rune_name: order.runeInfo.spaced_rune, + rune_name: rune.spaced_rune, + rune_hex: rune.rune_hex, rune_utxo: [ { id: order.runeItem.id, @@ -378,7 +392,6 @@ export class MarketsService implements OnModuleInit { .andWhere('outpoint.vout = :vout', { vout: body.seller.runeItem.vout, }) - .andWhere('txOut.spent = false') .getOne(); if (!outputValue) { throw new BadRequestException('No output value found'); @@ -437,6 +450,7 @@ export class MarketsService implements OnModuleInit { if (orders.length !== body.orderIds.length) { throw new BadRequestException('Invalid order ids'); } + const seller_items = await Promise.all( orders.map(async (order) => { const outputValue = await this.outpoinBalanceRepository @@ -448,7 +462,6 @@ export class MarketsService implements OnModuleInit { .andWhere('outpoint.vout = :vout', { vout: order.runeItem.vout, }) - .andWhere('txOut.spent = false') .getOne(); return { @@ -545,6 +558,7 @@ export class MarketsService implements OnModuleInit { async selectUTXOsForBuying(body: BuyerOrderDto, user: User): Promise { const utxos = await this.usersService.getMyUtxo(user); + // Get order by ids const orders = await this.orderRepository .createQueryBuilder('order') diff --git a/service/src/modules/runes/runes.controller.ts b/service/src/modules/runes/runes.controller.ts index 376328d6..b27ac9bc 100644 --- a/service/src/modules/runes/runes.controller.ts +++ b/service/src/modules/runes/runes.controller.ts @@ -14,7 +14,7 @@ import { CoreTransformInterceptor } from 'src/common/interceptors/coreTransform. import { EtchRuneDto } from './dto/etch-rune-filter.dto'; import { AuthGuard } from 'src/common/guards/auth.guard'; import { UserDecorator } from 'src/common/decorators/user.decorator'; -import { User } from '../database/entities/user.entity'; +import { User } from '../database/entities/marketplace/user.entity'; import { ParseRuneIdPipe } from 'src/common/pipes'; @Controller('runes') diff --git a/service/src/modules/runes/runes.providers.ts b/service/src/modules/runes/runes.providers.ts index e2c859e1..361fd1b4 100644 --- a/service/src/modules/runes/runes.providers.ts +++ b/service/src/modules/runes/runes.providers.ts @@ -1,6 +1,6 @@ import { DataSource } from 'typeorm'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; -import { EtchRune } from '../database/entities'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; +import { EtchRune, RuneStat } from '../database/entities/indexer'; export const runesProviders = [ { @@ -12,6 +12,11 @@ export const runesProviders = [ { provide: 'ETCH_RUNE_REPOSITORY', useFactory: (dataSource: DataSource) => dataSource.getRepository(EtchRune), - inject: ['DATA_SOURCE'], + inject: ['MARKETPLACE_DATA_SOURCE'], + }, + { + provide: 'RUNE_STAT_REPOSITORY', + useFactory: (dataSource: DataSource) => dataSource.getRepository(RuneStat), + inject: ['MARKETPLACE_DATA_SOURCE'], }, ]; diff --git a/service/src/modules/runes/runes.service.ts b/service/src/modules/runes/runes.service.ts index a6fd8279..890fed49 100644 --- a/service/src/modules/runes/runes.service.ts +++ b/service/src/modules/runes/runes.service.ts @@ -6,10 +6,10 @@ import { } from '@nestjs/common'; import { RuneFilterDto } from './dto'; import { Repository } from 'typeorm'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; import { EtchRuneDto } from './dto/etch-rune-filter.dto'; -import { EtchRune, RuneStat } from '../database/entities'; -import { User } from '../database/entities/user.entity'; +import { EtchRune, RuneStat } from '../database/entities/indexer'; +import { User } from '../database/entities/marketplace/user.entity'; import { EEtchRuneStatus } from 'src/common/enums'; import { StatsService } from '../stats/stats.service'; import { TransactionsService } from '../transactions/transactions.service'; @@ -27,6 +27,8 @@ export class RunesService { private runeEntryRepository: Repository, @Inject('ETCH_RUNE_REPOSITORY') private etchRuneEntryRepository: Repository, + @Inject('RUNE_STAT_REPOSITORY') + private runeStatRepository: Repository, private readonly indexersService: IndexersService, ) {} @@ -41,14 +43,8 @@ export class RunesService { return cachedData; } - const builder = this.runeEntryRepository - .createQueryBuilder('rune') - .innerJoinAndMapOne( - 'rune.stat', - RuneStat, - 'rune_stat', - 'rune_stat.rune_id = rune.rune_id', - ) + const builder = this.runeStatRepository + .createQueryBuilder('rune_stat') .offset(runeFilterDto.offset) .limit(runeFilterDto.limit); @@ -57,7 +53,6 @@ export class RunesService { type: runeFilterDto.type, }); } - if (runeFilterDto.search) { const search = runeFilterDto.search.replace(/•/g, ''); builder.andWhere(`rune_stat.rune_name ILIKE '%${search}%'`); @@ -91,18 +86,15 @@ export class RunesService { ); break; - case 'created_at': + default: builder.orderBy( - `rune.timestamp`, + `rune_stat.block`, runeFilterDto.sortOrder?.toLocaleUpperCase() === 'DESC' ? 'DESC' : 'ASC', ); - break; - - default: - builder.orderBy( - `rune.rune_id`, + builder.addOrderBy( + `rune_stat.number`, runeFilterDto.sortOrder?.toLocaleUpperCase() === 'DESC' ? 'DESC' : 'ASC', @@ -110,42 +102,74 @@ export class RunesService { break; } } else { - builder - .orderBy(`rune.block_height`, 'ASC') - .addOrderBy('rune.tx_index', 'ASC'); + builder.orderBy(`rune_stat.block`, 'ASC'); + builder.addOrderBy(`rune_stat.number`, 'ASC'); } const runes = await builder.getMany(); + const runeIds = runes.map((rune) => rune.rune_id); + const runeEntries = {}; + if (runeIds.length) { + const arrRuneEntries = await this.runeEntryRepository + .createQueryBuilder('rune') + .where('rune.rune_id IN (:...runeIds)', { runeIds }) + .getMany(); + for (let index = 0; index < arrRuneEntries.length; index++) { + const entry = arrRuneEntries[index]; + + runeEntries[entry.rune_id] = entry; + } + } + + const runeData = await Promise.all( + runes.map(async (rune) => { + let entry = runeEntries[rune.rune_id]; + if (!entry) { + const runeInfo = (await this.cacheService.get( + `rune:${rune.rune_id}`, + )) as any; + if (runeInfo) { + entry = { + ...runeInfo?.entry, + mint_type: runeInfo?.entry?.terms ? 'fairmint' : 'fixed-cap', + }; + } + } + + return { + id: rune.id, + rune_id: rune.rune_id, + rune_hex: entry?.rune_hex, + supply: entry?.supply || 0, + deploy_transaction: entry?.etching, + divisibility: entry?.divisibility, + start_block: + entry?.terms?.height?.length === 2 ? entry?.terms?.height[0] : null, + end_block: + entry?.terms?.height?.length === 2 ? entry?.terms?.height[1] : null, + holder_count: rune?.total_holders || '0', + rune: entry?.spaced_rune, + symbol: entry?.symbol, + premine: entry?.premine || '0', + timestamp: entry?.timestamp || 1000, + transaction_count: rune?.total_transactions || '0', + mint_type: entry?.mint_type || '', + terms: entry?.terms || null, + etching: entry?.etching || null, + parent: rune?.parent || null, + mints: entry?.mints || '0', + remaining: entry?.remaining || null, + burned: entry?.burned || '0', + limit: entry?.terms?.amount || '0', + mintable: entry?.mintable || false, + }; + }), + ); const result = { total: await builder.getCount(), limit: runeFilterDto.limit, offset: runeFilterDto.offset, - runes: runes.map((rune) => ({ - id: rune.id, - rune_id: rune.rune_id, - rune_hex: rune.rune_hex, - supply: rune?.stat?.total_supply || rune.supply || 0, - deploy_transaction: rune.tx_hash, - divisibility: rune.divisibility, - end_block: rune?.stat?.end_block || null, - start_block: rune.stat?.start_block || null, - holder_count: rune?.stat?.total_holders || '0', - rune: rune.spaced_rune, - symbol: rune.symbol, - premine: rune?.stat?.premine || '0', - term: rune?.stat?.term || 0, - timestamp: rune.timestamp, - transaction_count: rune?.stat?.total_transactions || '0', - mint_type: rune?.stat?.mint_type || '', - terms: rune?.stat?.entry?.terms || null, - etching: rune?.stat?.etching || null, - parent: rune?.stat?.parent || null, - mints: rune?.stat?.total_mints || '0', - remaining: rune?.stat?.entry.remaining || null, - burned: rune?.stat?.total_burns || '0', - limit: rune?.stat?.limit || '0', - mintable: rune?.stat?.mintable || false, - })), + runes: runeData, }; await this.cacheService.set( @@ -158,16 +182,16 @@ export class RunesService { } async getRuneById(id: string): Promise { - const rune = await this.runeEntryRepository - .createQueryBuilder('rune') - .leftJoinAndMapOne( - 'rune.stat', - RuneStat, - 'rune_stat', - 'rune_stat.rune_id = rune.rune_id', - ) - .where('rune.rune_id = :id', { id }) - .getOne(); + const [rune, runeStat] = await Promise.all([ + this.runeEntryRepository + .createQueryBuilder('rune') + .where('rune.rune_id = :id', { id }) + .getOne(), + this.runeStatRepository + .createQueryBuilder('rune_stat') + .where('rune_stat.rune_id = :id', { id }) + .getOne(), + ]); if (!rune) { throw new BadRequestException('Rune not found'); } @@ -176,28 +200,29 @@ export class RunesService { rows: { id: rune.id, rune_id: rune.rune_id, - rune_hex: rune.rune_hex, - supply: rune?.stat?.total_supply || rune.supply || 0, + rune_hex: rune?.rune_hex, + supply: runeStat?.total_supply || rune.supply || 0, deploy_transaction: rune.etching, divisibility: rune.divisibility, - end_block: rune?.stat?.end_block || null, - start_block: rune.stat?.start_block || null, - holder_count: rune?.stat?.total_holders || '0', + start_block: + rune?.terms?.height?.length === 2 ? rune?.terms?.height[0] : null, + end_block: + rune?.terms?.height?.length === 2 ? rune?.terms?.height[1] : null, + holder_count: runeStat?.total_holders || '0', rune: rune.spaced_rune, symbol: rune.symbol, - premine: rune?.stat?.premine || '0', - term: rune?.stat?.term || 0, + premine: rune?.premine || '0', timestamp: rune.timestamp, - transaction_count: rune?.stat?.total_transactions || '0', - mint_type: rune?.stat?.mint_type || '', - terms: rune?.stat?.entry?.terms || null, - etching: rune?.stat?.etching || null, - parent: rune?.stat?.parent || null, - mints: rune?.stat?.total_mints || '0', - remaining: rune?.stat?.entry.remaining || null, - burned: rune?.stat?.total_burns || '0', - limit: rune?.stat?.limit || '0', - mintable: rune?.stat?.mintable || false, + transaction_count: runeStat?.total_transactions || '0', + mint_type: rune?.mint_type || '', + terms: rune?.terms || null, + etching: rune?.etching || null, + parent: runeStat?.parent || null, + mints: rune?.mints || '0', + remaining: rune?.remaining || null, + burned: rune?.burned || '0', + limit: rune?.terms?.amount || '0', + mintable: rune?.mintable || false, }, }; } @@ -207,7 +232,7 @@ export class RunesService { select orb.address, sum(orb.balance_value) as amount, min(tre.spaced_rune) as spaced_rune, min(tre.rune_id) as rune_id from outpoint_rune_balances orb inner join transaction_rune_entries tre on tre.rune_id = orb.rune_id - where orb.spent = false and orb.address is not null and tre.rune_id = '${id}' + where orb.address is not null and tre.rune_id = '${id}' group by orb.address order by amount desc limit 10`); @@ -258,14 +283,22 @@ export class RunesService { for (const etchRune of etchRunes) { try { this.logger.log('Processing etching', etchRune.id); - const tx = await this.transactionsService.broadcastTransaction({ - rawTransaction: etchRune.mint_tx_hex, - } as BroadcastTransactionDto); - console.log('tx :>> ', tx); - await this.etchRuneEntryRepository.update(etchRune.id, { - status: EEtchRuneStatus.MINTED, - }); + const tx = await this.transactionsService.broadcastTransaction( + { + rawTransaction: etchRune.mint_tx_hex, + } as BroadcastTransactionDto, + [0.1, 1], + ); + + if (tx.result) { + await this.etchRuneEntryRepository.update(etchRune.id, { + mint_tx_id: tx?.result || '', + status: EEtchRuneStatus.MINTED, + }); + } else { + this.logger.error('Error processing etching', tx.error); + } } catch (error) { this.logger.error('Error processing etching', error); @@ -293,12 +326,11 @@ export class RunesService { } const data = await this.runeEntryRepository.query(` - select to2.tx_hash as utxo_tx_hash, to2.vout as utxo_vout, to2.value as utxo_value, tre.* ,orb.*, rs.* - from transaction_outs to2 + select to2.tx_hash as utxo_tx_hash, to2.vout as utxo_vout, to2.value as utxo_value, tre.* , orb.* + from transaction_outs to2 inner join outpoint_rune_balances orb on orb.tx_hash = to2.tx_hash and orb.vout = to2.vout inner join transaction_rune_entries tre on tre.rune_id = orb.rune_id - left join rune_stats rs on rs.rune_id = tre.rune_id - where orb.spent = false and orb.address is not null and to2.address = '${address}' + where orb.address is not null and orb.address = '${address}' order by orb.balance_value desc`); const result = data.map((d: any) => ({ @@ -316,13 +348,13 @@ export class RunesService { rune_id: d.rune_id, deploy_transaction: d.etching, divisibility: d.divisibility, - end_block: d?.end_block, - start_block: d?.start_block, - mints: d?.entry?.mints, - terms: d?.entry?.terms, - turbo: d?.entry?.turbo, - burned: d?.entry?.burned, - premine: d?.entry?.premine, + start_block: d?.height_start, + end_block: d?.height_end, + mints: d?.mints, + terms: d?.terms, + turbo: d?.turbo, + burned: d?.burned, + premine: d?.premine, rune: d.spaced_rune, symbol: d.symbol ? d.symbol : '¤', timestamp: d.timestamp, diff --git a/service/src/modules/stats/stats.providers.ts b/service/src/modules/stats/stats.providers.ts index 61fd6eb1..94286b42 100644 --- a/service/src/modules/stats/stats.providers.ts +++ b/service/src/modules/stats/stats.providers.ts @@ -1,8 +1,9 @@ import { DataSource } from 'typeorm'; -import { Transaction } from '../database/entities/transaction.entity'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; -import { RuneStat } from '../database/entities'; -import { TxidRune } from '../database/entities/txid-rune.entity'; +import { Transaction } from '../database/entities/indexer/transaction.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; +import { TxidRune } from '../database/entities/indexer/txid-rune.entity'; +import { Order } from '../database/entities/marketplace/order.entity'; +import { RuneStat } from '../database/entities/marketplace/rune_stat.entity'; export const statsProviders = [ { @@ -20,11 +21,16 @@ export const statsProviders = [ { provide: 'RUNE_STAT_REPOSITORY', useFactory: (dataSource: DataSource) => dataSource.getRepository(RuneStat), - inject: ['DATA_SOURCE'], + inject: ['MARKETPLACE_DATA_SOURCE'], }, { provide: 'TX_ID_RUNE_REPOSITORY', useFactory: (dataSource: DataSource) => dataSource.getRepository(TxidRune), inject: ['DATA_SOURCE'], }, + { + provide: 'ORDER_REPOSITORY', + useFactory: (dataSource: DataSource) => dataSource.getRepository(Order), + inject: ['MARKETPLACE_DATA_SOURCE'], + }, ]; diff --git a/service/src/modules/stats/stats.service.ts b/service/src/modules/stats/stats.service.ts index c921eabe..2b40957a 100644 --- a/service/src/modules/stats/stats.service.ts +++ b/service/src/modules/stats/stats.service.ts @@ -2,10 +2,9 @@ import { HttpService } from '@nestjs/axios'; import { CacheInterceptor } from '@nestjs/cache-manager'; import { Inject, Injectable, Logger, UseInterceptors } from '@nestjs/common'; import { ODR_PORT, ODR_URL } from 'src/environments'; -import { Transaction } from '../database/entities/transaction.entity'; +import { Transaction } from '../database/entities/indexer/transaction.entity'; import { Repository } from 'typeorm'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; -import { RuneStat } from '../database/entities'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; import { Queue } from 'bull'; import { InjectQueue } from '@nestjs/bull'; import { PROCESS, PROCESSOR } from 'src/common/enums'; @@ -14,7 +13,10 @@ import Redis from 'ioredis'; import { IndexersService } from '../indexers/indexers.service'; import { getFeesRecommended } from 'src/vendors/mempool'; import { FeesRecommended } from '@mempool/mempool.js/lib/interfaces/bitcoin/fees'; -import { TxidRune } from '../database/entities/txid-rune.entity'; +import { TxidRune } from '../database/entities/indexer/txid-rune.entity'; +import { Order } from '../database/entities/marketplace/order.entity'; +import { RuneStat } from '../database/entities/marketplace/rune_stat.entity'; +import * as dayjs from 'dayjs'; @Injectable() @UseInterceptors(CacheInterceptor) @@ -28,6 +30,8 @@ export class StatsService { private runeEntryRepository: Repository, @Inject('RUNE_STAT_REPOSITORY') private runeStatRepository: Repository, + @Inject('ORDER_REPOSITORY') + private orderRepository: Repository, @Inject('TX_ID_RUNE_REPOSITORY') private txidRuneRepository: Repository, @InjectQueue(PROCESSOR.STAT_QUEUE) private statQueue: Queue, @@ -44,22 +48,24 @@ export class StatsService { return res.data; } - async getDailyTransactionCount(): Promise { - const res = await this.transactionRepository - .createQueryBuilder('transaction') - .innerJoinAndMapOne( - 'transaction.block', - 'transaction.block', - 'block', - 'block.block_height = transaction.block_height', - ) - .select('COUNT(*)') - .where( - `date_trunc('day', to_timestamp(block.block_time)) = date_trunc('day', now())`, - ) - .getRawOne(); - - return res.count; + async getDailyTransactionCount(): Promise { + const res = await this.transactionRepository.query(` + select DATE_TRUNC('day', to_timestamp(b.block_time)) as date, count(t.id) as total + from transactions t + inner join blocks b on b.block_height = t.block_height + where to_timestamp(b.block_time) >= current_date - interval '6 days' + group by date + order by date asc`); + + const data = []; + const labels = []; + for (let index = 0; index < res.length; index++) { + const item = res[index]; + data.push(parseInt(item.total)); + labels.push(dayjs(item.date).format('MM/DD')); + } + + return { data, labels }; } async getBlockSyncNumber() { @@ -82,7 +88,7 @@ export class StatsService { const totalRune = await this.runeEntryRepository.count(); const totalFreeMintRune = await this.runeEntryRepository .createQueryBuilder('rune') - .where(`mint_entry ->> 'cap' is null`) + .where(`terms ->> 'cap' is null`) .getCount(); const totalTransaction = await this.txidRuneRepository.count(); const totalHolderData = await this.transactionRepository @@ -90,11 +96,11 @@ export class StatsService { from ( select orb.address from outpoint_rune_balances orb - where orb.address is not null and orb.spent = false + where orb.address is not null group by orb.address ) as rp`); let totalFee = 0; - const totalFeeData = await this.transactionRepository.query( + const totalFeeData = await this.orderRepository.query( `select sum(price) from orders o`, ); if (totalFeeData.length) { @@ -156,34 +162,88 @@ export class StatsService { return { total: result.length }; } - async calculateNetworkStats(): Promise { + async getBlockIndex(): Promise { + let blockHeight = null; + try { - const blockHeight = await this.indexersService.getBlockHeight(false); - const currentBlockHeight = await this.redis.get('currentBlockHeight'); - if (parseInt(currentBlockHeight) >= parseInt(blockHeight)) { + blockHeight = await this.indexersService.getBlockIndex(); + } catch (error) { + this.logger.error('Error getting block index', error); + } + + if (!blockHeight) { + try { + blockHeight = await this.indexersService.getBlockHeight(false); + } catch (error) { + this.logger.error('Error getting block height', error); return; } + } - await this.redis.set('currentBlockHeight', blockHeight); - this.logger.log(`Calculating network stats on block ${blockHeight} ...`); - - const runes = await this.runeEntryRepository.find({}); - for (let index = 0; index < runes.length; index++) { - const rune = runes[index]; - await this.statQueue.add( - PROCESS.STAT_QUEUE.CALCULATE_RUNE_STAT, - { - blockHeight, - rune, - }, - { - jobId: `${rune.rune_id}`, - attempts: 0, - backoff: 0, - removeOnComplete: true, - removeOnFail: true, - }, - ); + return parseInt(blockHeight); + } + + async getCurrentBlockIndex(): Promise { + const currentBlockHeight = await this.redis.get('currentBlockHeight'); + if (currentBlockHeight) { + return parseInt(currentBlockHeight); + } + + return null; + } + + async calculateNetworkStats(): Promise { + const blockHeight = await this.getBlockIndex(); + const currentBlockHeight = await this.getCurrentBlockIndex(); + if (currentBlockHeight >= blockHeight) { + return; + } + + await this.redis.set('currentBlockHeight', blockHeight); + this.logger.log(`Calculating network stats on block ${blockHeight} ...`); + + try { + let runeIds = []; + + if (!currentBlockHeight) { + const runes = await this.runeEntryRepository.find(); + if (runes?.length) { + runeIds = runes.map((rune) => rune.rune_id); + } + } else { + // Get changes in rune stats + const runeChanges = await this.transactionRepository.query(` + select jsonb_agg(rune_id) as ids + from ( + select rune_id + from outpoint_rune_balances orb + where rune_id is not null and block_height = ${blockHeight} + group by rune_id + ) as rb + `); + if (runeChanges?.length && runeChanges[0]?.ids?.length) { + runeIds = runeChanges[0]?.ids; + } + } + + if (runeIds?.length) { + for (let index = 0; index < runeIds.length; index++) { + const id = runeIds[index]; + await this.statQueue.add( + PROCESS.STAT_QUEUE.CALCULATE_RUNE_STAT, + { + blockHeight, + rune: { rune_id: id }, + }, + { + jobId: `${id}`, + attempts: 0, + backoff: 0, + removeOnComplete: true, + removeOnFail: true, + }, + ); + } } } catch (error) { this.logger.error('Error calculating network stats', error); @@ -195,11 +255,7 @@ export class StatsService { rune: TransactionRuneEntry, ): Promise { try { - const runeStats = await this.runeStatRepository.findOne({ - where: { rune_id: rune.rune_id }, - }); - const stats = (await this.runeStatRepository - .query(`select 'total_transactions' as name, count(*) as total + const query = `select 'total_transactions' as name, count(*) as total from ( select orb.tx_hash from outpoint_rune_balances orb @@ -213,9 +269,26 @@ from ( select orb.address from outpoint_rune_balances orb inner join transaction_rune_entries tre on tre.rune_id = orb.rune_id - where tre.rune_id = '${rune.rune_id}' and CAST(orb.balance_value AS DECIMAL) > 0 and orb.spent = false + where tre.rune_id = '${rune.rune_id}' and CAST(orb.balance_value AS DECIMAL) > 0 group by orb.address -) as rp2`)) as Array<{ name: string; total: number }>; +) as rp2`; + + this.logger.log(`Calculating rune stat ${rune.rune_id} ...`); + + const [runeStats, stats, runeIndex] = await Promise.all([ + this.runeStatRepository.findOne({ + where: { rune_id: rune.rune_id }, + }), + this.transactionRepository.query(query), + this.indexersService.getRuneDetails(rune.rune_id), + ]); + if (runeIndex) { + // Cache rune details + await this.redis.set( + `rune-info:${rune.rune_id}`, + JSON.stringify(runeIndex), + ); + } const payload = {} as any; for (let index = 0; index < stats.length; index++) { @@ -223,18 +296,11 @@ from ( payload[stat.name] = stat.total; } - let runeIndex = null; - try { - runeIndex = await this.indexersService.getRuneDetails(rune.rune_id); - } catch (error) { - this.logger.error('Error getting rune details', error); - } - if (!runeIndex) { - return; - } + const block = runeIndex?.entry?.block || BigInt(0); const rune_name = runeIndex?.entry?.spaced_rune ? String(runeIndex?.entry?.spaced_rune).replace(/•/g, '') : ''; + const number = runeIndex?.entry?.number || BigInt(0); const premine = runeIndex?.entry.premine ? BigInt(runeIndex?.entry.premine) : BigInt(0); @@ -270,7 +336,7 @@ from ( // Calculate market stats let total_volume = BigInt(0); - const dataVolume = await this.transactionRepository + const dataVolume = await this.orderRepository .query(`select sum((rune_item ->> 'tokenValue')::int) as total from orders o where rune_id = '${rune.rune_id}' and status = 'completed' @@ -279,7 +345,7 @@ from ( total_volume = BigInt(dataVolume[0]?.total); } let volume_24h = BigInt(0); - const dataVolume24h = await this.transactionRepository + const dataVolume24h = await this.orderRepository .query(`select sum((rune_item ->> 'tokenValue')::int) as total from orders o where rune_id = '${rune.rune_id}' and status = 'completed' and created_at >= now() - interval '24 hours' @@ -288,7 +354,7 @@ from ( volume_24h = BigInt(dataVolume24h[0]?.total); } let prev_volume_24h = BigInt(0); - const dataPrevVolume24h = await this.transactionRepository.query( + const dataPrevVolume24h = await this.runeStatRepository.query( `select volume_24h as total from rune_stats rs where rune_id = '${rune.rune_id}'`, ); if (dataPrevVolume24h.length) { @@ -301,9 +367,11 @@ from ( const change_24h = diffVolume === BigInt(0) ? BigInt(0) - : (diffVolume * BigInt(100)) / prev_volume_24h; + : prev_volume_24h > BigInt(0) + ? (diffVolume * BigInt(100)) / prev_volume_24h + : BigInt(0); let price = BigInt(0); - const dataPrice = await this.transactionRepository.query(` + const dataPrice = await this.orderRepository.query(` select price from orders o where rune_id = '${rune.rune_id}' and status in ('listing','completed') @@ -315,7 +383,7 @@ from ( } let ma_price = BigInt(0); - const dataMAPrice = await this.transactionRepository + const dataMAPrice = await this.orderRepository .query(`select (sum(medium_price)/count(*))::integer as price from ( select DATE_TRUNC('day', created_at) AS date, AVG(price) AS medium_price @@ -329,15 +397,13 @@ from ( ma_price = BigInt(dataMAPrice[0]?.price || 0); market_cap = BigInt(dataMAPrice[0].price || 0) * supply; } - let order_sold = BigInt(0); - const dataOrderSold = await this.transactionRepository.query( + const dataOrderSold = await this.orderRepository.query( `select count(*) as total from orders o where rune_id = '${rune.rune_id}' and status = 'completed'`, ); if (dataOrderSold.length) { order_sold = BigInt(dataOrderSold[0]?.total); } - await this.runeStatRepository.save( new RuneStat({ id: runeStats?.id, @@ -349,6 +415,7 @@ from ( change_24h, volume_24h, prev_volume_24h, + block, price, ma_price, order_sold, @@ -356,6 +423,7 @@ from ( market_cap, mintable: runeIndex?.mintable || false, term, + number, start_block: runeIndex?.entry?.terms?.height && runeIndex?.entry?.terms?.height.length > 0 @@ -379,8 +447,7 @@ from ( }), ); } catch (error) { - console.log('rune.rune_id, :>> ', rune.rune_id); - this.logger.error('Error calculating rune stat', error); + this.logger.error(rune.rune_id, 'Error calculating rune stat', error); } } } diff --git a/service/src/modules/transactions/transactions.providers.ts b/service/src/modules/transactions/transactions.providers.ts index 013fb436..775ee49a 100644 --- a/service/src/modules/transactions/transactions.providers.ts +++ b/service/src/modules/transactions/transactions.providers.ts @@ -1,10 +1,11 @@ import { DataSource } from 'typeorm'; -import { Transaction } from '../database/entities/transaction.entity'; -import { TransactionIns } from '../database/entities/transaction-ins.entity'; -import { TransactionOut } from '../database/entities/transaction-out.entity'; -import { TxidRune } from '../database/entities/txid-rune.entity'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; -import { OutpointRuneBalance } from '../database/entities/outpoint-rune-balance.entity'; +import { Transaction } from '../database/entities/indexer/transaction.entity'; +import { TransactionIns } from '../database/entities/indexer/transaction-ins.entity'; +import { TransactionOut } from '../database/entities/indexer/transaction-out.entity'; +import { TxidRune } from '../database/entities/indexer/txid-rune.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; +import { OutpointRuneBalance } from '../database/entities/indexer/outpoint-rune-balance.entity'; +import { RuneStat } from '../database/entities/indexer'; export const transactionsProviders = [ { @@ -47,4 +48,9 @@ export const transactionsProviders = [ useFactory: (dataSource: DataSource) => dataSource.getRepository(TxidRune), inject: ['DATA_SOURCE'], }, + { + provide: 'RUNE_STAT_REPOSITORY', + useFactory: (dataSource: DataSource) => dataSource.getRepository(RuneStat), + inject: ['MARKETPLACE_DATA_SOURCE'], + }, ]; diff --git a/service/src/modules/transactions/transactions.service.ts b/service/src/modules/transactions/transactions.service.ts index 2398be8c..335130c1 100644 --- a/service/src/modules/transactions/transactions.service.ts +++ b/service/src/modules/transactions/transactions.service.ts @@ -11,20 +11,20 @@ import { } from './dto'; import { HttpService } from '@nestjs/axios'; import { Repository, SelectQueryBuilder } from 'typeorm'; -import { Transaction } from '../database/entities/transaction.entity'; +import { Transaction } from '../database/entities/indexer/transaction.entity'; import { BITCOIN_RPC_HOST, BITCOIN_RPC_PASS, BITCOIN_RPC_PORT, BITCOIN_RPC_USER, } from 'src/environments'; -import { TransactionOut } from '../database/entities/transaction-out.entity'; -import { OutpointRuneBalance } from '../database/entities/outpoint-rune-balance.entity'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; +import { TransactionOut } from '../database/entities/indexer/transaction-out.entity'; +import { OutpointRuneBalance } from '../database/entities/indexer/outpoint-rune-balance.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager'; import { IndexersService } from '../indexers/indexers.service'; -import { TxidRune } from '../database/entities/txid-rune.entity'; -import { Block } from '../database/entities/block.entity'; +import { TxidRune } from '../database/entities/indexer/txid-rune.entity'; +import { Block } from '../database/entities/indexer/block.entity'; @Injectable() export class TransactionsService { @@ -181,23 +181,31 @@ export class TransactionsService { } async getTransactionById(tx_hash: string): Promise { - const rawTransaction = await this.httpService - .post( - `${BITCOIN_RPC_HOST}:${BITCOIN_RPC_PORT}`, - { - jsonrpc: '1.0', - id: 'curltest', - method: 'getrawtransaction', - params: [tx_hash, true], - }, - { - auth: { - username: BITCOIN_RPC_USER, - password: BITCOIN_RPC_PASS, + let rawTransaction = null; + try { + rawTransaction = await this.httpService + .post( + `${BITCOIN_RPC_HOST}:${BITCOIN_RPC_PORT}`, + { + jsonrpc: '1.0', + id: 'curltest', + method: 'getrawtransaction', + params: [tx_hash, true], }, - }, - ) - .toPromise(); + { + auth: { + username: BITCOIN_RPC_USER, + password: BITCOIN_RPC_PASS, + }, + }, + ) + .toPromise(); + } catch (error) { + this.logger.error('Error getting raw transaction', error); + + throw new BadRequestException('Error getting transaction'); + } + const voutValues = rawTransaction.data.result.vout.map( (vout) => vout.value, ); @@ -450,7 +458,10 @@ export class TransactionsService { } } - async broadcastTransaction(txDto: BroadcastTransactionDto) { + async broadcastTransaction( + txDto: BroadcastTransactionDto, + config: Array = [], + ) { try { const response = await this.httpService .post( @@ -459,7 +470,7 @@ export class TransactionsService { jsonrpc: '1.0', id: 'codelight', method: 'sendrawtransaction', - params: [txDto.rawTransaction], + params: [txDto.rawTransaction, ...config], }, { auth: { @@ -500,20 +511,40 @@ export class TransactionsService { } try { - const transaction = await this.outpointRuneBalanceRepository.find({ - where: { tx_hash: arrLocation[0], vout: parseInt(arrLocation[1]) }, - relations: ['rune', 'rune.stat'], - }); - - if (transaction?.length) { + const outpointRuneBalances = + await this.outpointRuneBalanceRepository.find({ + where: { + tx_hash: arrLocation[0], + vout: parseInt(arrLocation[1]), + }, + relations: ['rune'], + }); + + if (outpointRuneBalances?.length) { + await Promise.all( + outpointRuneBalances.map(async (outpoint, index) => { + const runeInfo: any = await this.cacheService.get( + `rune-info:${outpoint.rune_id}`, + ); + if (runeInfo) { + outpointRuneBalances[index].rune.parent = + runeInfo?.parent || null; + outpointRuneBalances[index].rune.mintable = + runeInfo?.mintable || false; + } + + return; + }), + ); + // Get stat data await this.cacheService.set( `${blockHeight}:retrive-transaction:${location}`, - transaction, + outpointRuneBalances, 900, ); } - return transaction; + return outpointRuneBalances; } catch (error) { this.logger.error('Error retrieving rune by tx id', error); return null; @@ -523,19 +554,7 @@ export class TransactionsService { return runeData.map((rune) => { if (rune?.length) { - return rune.map((r) => ({ - ...r, - rune: { - ...r.rune, - mints: r.rune?.stat?.entry?.mints, - premine: r.rune?.stat?.premine, - burned: r.rune?.stat?.entry?.burned, - supply: r.rune?.stat?.total_supply || r.rune?.supply, - minable: r.rune?.stat?.mintable, - terms: r.rune?.stat?.entry?.terms, - stat: null, - }, - })); + return rune; } return null; diff --git a/service/src/modules/users/user.providers.ts b/service/src/modules/users/user.providers.ts index a5353128..2fb2126b 100644 --- a/service/src/modules/users/user.providers.ts +++ b/service/src/modules/users/user.providers.ts @@ -1,15 +1,15 @@ import { DataSource } from 'typeorm'; -import { User } from '../database/entities/user.entity'; -import { TransactionOut } from '../database/entities/transaction-out.entity'; -import { Transaction } from '../database/entities/transaction.entity'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; -import { Order } from '../database/entities/order.entity'; +import { User } from '../database/entities/marketplace/user.entity'; +import { TransactionOut } from '../database/entities/indexer/transaction-out.entity'; +import { Transaction } from '../database/entities/indexer/transaction.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; +import { Order } from '../database/entities/marketplace/order.entity'; export const userProviders = [ { provide: 'USER_REPOSITORY', useFactory: (dataSource: DataSource) => dataSource.getRepository(User), - inject: ['DATA_SOURCE'], + inject: ['MARKETPLACE_DATA_SOURCE'], }, { provide: 'TRANSACTION_OUT_REPOSITORY', @@ -32,6 +32,6 @@ export const userProviders = [ { provide: 'ORDER_REPOSITORY', useFactory: (dataSource: DataSource) => dataSource.getRepository(Order), - inject: ['DATA_SOURCE'], + inject: ['MARKETPLACE_DATA_SOURCE'], }, ]; diff --git a/service/src/modules/users/users.controller.ts b/service/src/modules/users/users.controller.ts index b0e386fd..d662e2ab 100644 --- a/service/src/modules/users/users.controller.ts +++ b/service/src/modules/users/users.controller.ts @@ -9,7 +9,7 @@ import { import { UserDecorator } from 'src/common/decorators/user.decorator'; import { AuthGuard } from 'src/common/guards/auth.guard'; import { CoreTransformInterceptor } from 'src/common/interceptors/coreTransform.interceptor'; -import { User } from '../database/entities/user.entity'; +import { User } from '../database/entities/marketplace/user.entity'; import { UsersService } from './users.service'; import { AddressTxsUtxo } from '@mempool/mempool.js/lib/interfaces/bitcoin/addresses'; import { MarketRuneOrderFilterDto } from '../markets/dto'; diff --git a/service/src/modules/users/users.service.ts b/service/src/modules/users/users.service.ts index 8a03df01..e51973f0 100644 --- a/service/src/modules/users/users.service.ts +++ b/service/src/modules/users/users.service.ts @@ -1,15 +1,16 @@ import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; -import { User } from '../database/entities/user.entity'; +import { User } from '../database/entities/marketplace/user.entity'; import { Repository } from 'typeorm'; import mempoolJS from '@mempool/mempool.js'; import { BITCOIN_NETWORK } from 'src/environments'; import { MempoolReturn } from '@mempool/mempool.js/lib/interfaces/index'; import { AddressTxsUtxo } from '@mempool/mempool.js/lib/interfaces/bitcoin/addresses'; -import { TransactionOut } from '../database/entities/transaction-out.entity'; +import { TransactionOut } from '../database/entities/indexer/transaction-out.entity'; import { TransactionsService } from '../transactions/transactions.service'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; -import { Order } from '../database/entities/order.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; +import { Order } from '../database/entities/marketplace/order.entity'; import { MarketRuneOrderFilterDto } from '../markets/dto'; +import { IndexersService } from '../indexers/indexers.service'; @Injectable() export class UsersService implements OnModuleInit { @@ -23,6 +24,7 @@ export class UsersService implements OnModuleInit { @Inject('ORDER_REPOSITORY') private orderRepository: Repository, private readonly transactionsService: TransactionsService, + private readonly indexersService: IndexersService, ) {} private mempoolClient: MempoolReturn['bitcoin']; @@ -62,7 +64,6 @@ export class UsersService implements OnModuleInit { select rune_id, sum(balance_value) as balance_value from outpoint_rune_balances orb where orb.address = '${user.walletAddress}' - and orb.spent = false group by rune_id ) as rb on rb.rune_id = tre.rune_id `); @@ -74,14 +75,13 @@ export class UsersService implements OnModuleInit { select tre.spaced_rune ,orb.* from outpoint_rune_balances orb inner join transaction_rune_entries tre on tre.rune_id = orb.rune_id - where orb.spent = false and orb.address is not null and tre.rune_id = '${id}' and orb.address = '${user.walletAddress}' + where orb.address is not null and tre.rune_id = '${id}' and orb.address = '${user.walletAddress}' order by orb.balance_value desc`); return data; } async search(query: string): Promise { - console.log('query :>> ', query); // Is transaction hash if (query.length === 64) { const transaction = @@ -146,12 +146,6 @@ export class UsersService implements OnModuleInit { ): Promise { const builder = this.orderRepository .createQueryBuilder('order') - .innerJoinAndMapOne( - 'order.runeInfo', - TransactionRuneEntry, - 'runeInfo', - 'order.rune_id = runeInfo.rune_id', - ) .where('order.user_id = :userId', { userId: user.id }); if (marketRuneOrderFilterDto.status) { @@ -181,6 +175,18 @@ export class UsersService implements OnModuleInit { } const orders = await builder.getMany(); + const runeIds = orders.map((order) => order.rune_id); + const runeEntries = runeIds.length + ? await this.runeEntryRepository + .createQueryBuilder('rune') + .where('rune.rune_id IN (:...runeIds)', { runeIds }) + .getMany() + : []; + const runeEntriesMap = {}; + for (let index = 0; index < runeEntries.length; index++) { + const entry = runeEntries[index]; + runeEntriesMap[entry.rune_id] = entry; + } return { total, @@ -201,9 +207,9 @@ export class UsersService implements OnModuleInit { owner_id: order.userId, price_per_unit: order.price, received_address: null, - rune_hex: '', + rune_hex: runeEntriesMap[order.rune_id]?.rune_hex || '', rune_id: order?.runeItem.id, - rune_name: order?.runeInfo.spaced_rune, + rune_name: runeEntriesMap[order.rune_id].spaced_rune, rune_utxo: [ { id: order?.runeItem.id, diff --git a/service/src/modules/workers/workers.processor.ts b/service/src/modules/workers/workers.processor.ts index cf48a548..213e0aaf 100644 --- a/service/src/modules/workers/workers.processor.ts +++ b/service/src/modules/workers/workers.processor.ts @@ -2,7 +2,7 @@ import { InjectQueue, Process, Processor } from '@nestjs/bull'; import { Logger } from '@nestjs/common'; import { Job, Queue } from 'bull'; import { PROCESS, PROCESSOR } from 'src/common/enums'; -import { TransactionRuneEntry } from '../database/entities/rune-entry.entity'; +import { TransactionRuneEntry } from '../database/entities/indexer/rune-entry.entity'; import { StatsService } from '../stats/stats.service'; @Processor(PROCESSOR.STAT_QUEUE)