diff --git a/backend/src/core/bitcoin-api/bitcoin-api.schema.ts b/backend/src/core/bitcoin-api/bitcoin-api.schema.ts index c95c2ed1..cf1ad399 100644 --- a/backend/src/core/bitcoin-api/bitcoin-api.schema.ts +++ b/backend/src/core/bitcoin-api/bitcoin-api.schema.ts @@ -25,6 +25,7 @@ export const Block = z.object({ difficulty: z.number(), extras: z .object({ + reward: z.number(), totalFees: z.number(), avgFee: z.number(), avgFeeRate: z.number(), diff --git a/backend/src/modules/bitcoin/address/address.dataloader.ts b/backend/src/modules/bitcoin/address/address.dataloader.ts index 244120ed..5c9938e8 100644 --- a/backend/src/modules/bitcoin/address/address.dataloader.ts +++ b/backend/src/modules/bitcoin/address/address.dataloader.ts @@ -3,7 +3,7 @@ import { NestDataLoader } from '@applifting-io/nestjs-dataloader'; import { Address } from 'src/core/bitcoin-api/bitcoin-api.schema'; import { BitcoinApiService } from 'src/core/bitcoin-api/bitcoin-api.service'; import { DataLoaderResponse } from 'src/common/type/dataloader'; -import { BitcoinTransaction } from '../transaction/transaction.model'; +import { BitcoinBaseTransaction, BitcoinTransaction } from '../transaction/transaction.model'; @Injectable() export class BitcoinAddressLoader implements NestDataLoader { @@ -29,7 +29,7 @@ export interface BitcoinAddressTransactionsLoaderProps { @Injectable() export class BitcoinAddressTransactionsLoader - implements NestDataLoader + implements NestDataLoader { private logger = new Logger(BitcoinAddressTransactionsLoader.name); diff --git a/backend/src/modules/bitcoin/address/address.module.ts b/backend/src/modules/bitcoin/address/address.module.ts index d1d64b7c..c5a931a9 100644 --- a/backend/src/modules/bitcoin/address/address.module.ts +++ b/backend/src/modules/bitcoin/address/address.module.ts @@ -7,4 +7,4 @@ import { BitcoinAddressLoader, BitcoinAddressTransactionsLoader } from './addres imports: [BitcoinApiModule], providers: [BitcoinAddressResolver, BitcoinAddressLoader, BitcoinAddressTransactionsLoader], }) -export class AddressModule {} +export class BitcoinAddressModule {} diff --git a/backend/src/modules/bitcoin/address/address.resolver.ts b/backend/src/modules/bitcoin/address/address.resolver.ts index 90694d20..5cb7dca0 100644 --- a/backend/src/modules/bitcoin/address/address.resolver.ts +++ b/backend/src/modules/bitcoin/address/address.resolver.ts @@ -1,7 +1,6 @@ import DataLoader from 'dataloader'; import { Loader } from '@applifting-io/nestjs-dataloader'; -import { Args, Float, Parent, ResolveField, Resolver } from '@nestjs/graphql'; -import { BitcoinApiService } from 'src/core/bitcoin-api/bitcoin-api.service'; +import { Args, Float, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { BitcoinBaseTransaction, BitcoinTransaction } from '../transaction/transaction.model'; import { BitcoinAddress } from './address.model'; import { @@ -14,7 +13,13 @@ import { @Resolver(() => BitcoinAddress) export class BitcoinAddressResolver { - constructor(private bitcoinApiService: BitcoinApiService) {} + @Query(() => BitcoinAddress, { name: 'btcAddress', nullable: true }) + public async getBtcAddress( + @Loader(BitcoinAddressLoader) addressLoader: DataLoader, + @Args('address') address: string, + ): Promise { + return await addressLoader.load(address); + } @ResolveField(() => Float) public async satoshi( diff --git a/backend/src/modules/bitcoin/bitcoin.module.ts b/backend/src/modules/bitcoin/bitcoin.module.ts index 1cd9345b..5ccb41fd 100644 --- a/backend/src/modules/bitcoin/bitcoin.module.ts +++ b/backend/src/modules/bitcoin/bitcoin.module.ts @@ -2,19 +2,19 @@ import { Module } from '@nestjs/common'; import { BitcoinApiModule } from 'src/core/bitcoin-api/bitcoin-api.module'; import { BitcoinTransactionModule } from './transaction/transaction.module'; import { BitcoinResolver } from './bitcoin.resolver'; -import { BlockModule } from './block/block.module'; -import { AddressModule } from './address/address.module'; -import { InputModule } from './input/input.module'; -import { OutputModule } from './output/output.module'; +import { BitcoinBlockModule } from './block/block.module'; +import { BitcoinAddressModule } from './address/address.module'; +import { BitcoinInputModule } from './input/input.module'; +import { BitcoinOutputModule } from './output/output.module'; @Module({ imports: [ BitcoinApiModule, - BlockModule, + BitcoinBlockModule, BitcoinTransactionModule, - AddressModule, - InputModule, - OutputModule, + BitcoinAddressModule, + BitcoinInputModule, + BitcoinOutputModule, ], providers: [BitcoinResolver], }) diff --git a/backend/src/modules/bitcoin/block/block.model.ts b/backend/src/modules/bitcoin/block/block.model.ts index 3333d80a..f60e81a6 100644 --- a/backend/src/modules/bitcoin/block/block.model.ts +++ b/backend/src/modules/bitcoin/block/block.model.ts @@ -5,7 +5,7 @@ import { BitcoinAddress } from '../address/address.model'; export type BitcoinBaseBlock = Omit< BitcoinBlock, - 'miner' | 'totalFee' | 'feeRateRange' | 'transactions' + 'miner' | 'reward' | 'totalFee' | 'feeRateRange' | 'transactions' >; @ObjectType({ description: 'Fee Rate Range' }) @@ -49,6 +49,9 @@ export class BitcoinBlock { @Field(() => BitcoinAddress) miner: BitcoinAddress; + @Field(() => Float) + reward: number; + @Field(() => Float) totalFee: number; diff --git a/backend/src/modules/bitcoin/block/block.module.ts b/backend/src/modules/bitcoin/block/block.module.ts index 7074795b..ad2ea246 100644 --- a/backend/src/modules/bitcoin/block/block.module.ts +++ b/backend/src/modules/bitcoin/block/block.module.ts @@ -7,4 +7,4 @@ import { BitcoinBlockResolver } from './block.resolver'; imports: [BitcoinApiModule], providers: [BitcoinBlockResolver, BitcoinBlockLoader, BitcoinBlockTransactionsLoader], }) -export class BlockModule {} +export class BitcoinBlockModule {} diff --git a/backend/src/modules/bitcoin/block/block.resolver.ts b/backend/src/modules/bitcoin/block/block.resolver.ts index 7745f241..8c06b719 100644 --- a/backend/src/modules/bitcoin/block/block.resolver.ts +++ b/backend/src/modules/bitcoin/block/block.resolver.ts @@ -25,14 +25,33 @@ export class BitcoinBlockResolver { @ResolveField(() => BitcoinAddress) public async miner( @Parent() block: BitcoinBaseBlock, - @Loader(BitcoinBlockTransactionsLoader) - blockTxsLoader: DataLoader, + @Loader(BitcoinBlockLoader) blockLoader: DataLoader, ): Promise { - const txs = await blockTxsLoader.load(block.id); - const coinbaseTx = BitcoinTransaction.from(txs[0]); - return { - address: coinbaseTx.vout[0].scriptpubkeyAddress, - }; + // XXX: only the "mempool" mode returns the "extra" field + const detail = await blockLoader.load(block.id); + if (detail.extras) { + return { + address: detail.extras.coinbaseAddress, + }; + } else { + // TODO: what should be returned when using the "electrs" mode? + return null; + } + } + + @ResolveField(() => Float) + public async reward( + @Parent() block: BitcoinBaseBlock, + @Loader(BitcoinBlockLoader) blockLoader: DataLoader, + ): Promise { + // XXX: only the "mempool" mode returns the "extra" field + const detail = await blockLoader.load(block.id); + if (detail.extras) { + return detail.extras.reward; + } else { + // TODO: what should be returned when using the "electrs" mode? + return 0; + } } @ResolveField(() => Float) diff --git a/backend/src/modules/bitcoin/input/input.model.ts b/backend/src/modules/bitcoin/input/input.model.ts index b827016f..3ae2dbea 100644 --- a/backend/src/modules/bitcoin/input/input.model.ts +++ b/backend/src/modules/bitcoin/input/input.model.ts @@ -1,6 +1,6 @@ import { Field, Float, ObjectType } from '@nestjs/graphql'; import * as BitcoinApi from 'src/core/bitcoin-api/bitcoin-api.schema'; -import { BitcoinOutput } from '../output/output.model'; +import { BitcoinBaseOutput, BitcoinOutput } from '../output/output.model'; @ObjectType({ description: 'Bitcoin Input' }) export class BitcoinInput { @@ -11,7 +11,7 @@ export class BitcoinInput { vout: number; @Field(() => BitcoinOutput, { nullable: true }) - prevout: BitcoinOutput; + prevout: BitcoinBaseOutput; @Field(() => String) scriptsig: string; diff --git a/backend/src/modules/bitcoin/input/input.module.ts b/backend/src/modules/bitcoin/input/input.module.ts index 7307a6a1..8b1f6f1d 100644 --- a/backend/src/modules/bitcoin/input/input.module.ts +++ b/backend/src/modules/bitcoin/input/input.module.ts @@ -4,4 +4,4 @@ import { BitcoinInputResolver } from './input.resolver'; @Module({ providers: [BitcoinInputResolver], }) -export class InputModule {} +export class BitcoinInputModule {} diff --git a/backend/src/modules/bitcoin/output/output.model.ts b/backend/src/modules/bitcoin/output/output.model.ts index 1227ce56..c75e175c 100644 --- a/backend/src/modules/bitcoin/output/output.model.ts +++ b/backend/src/modules/bitcoin/output/output.model.ts @@ -2,6 +2,8 @@ import { Field, Float, ObjectType } from '@nestjs/graphql'; import * as BitcoinApi from 'src/core/bitcoin-api/bitcoin-api.schema'; import { BitcoinAddress, BitcoinBaseAddress } from '../address/address.model'; +export type BitcoinBaseOutput = Omit; + @ObjectType({ description: 'Bitcoin Output' }) export class BitcoinOutput { @Field(() => String) @@ -22,7 +24,10 @@ export class BitcoinOutput { @Field(() => BitcoinAddress, { nullable: true }) address: BitcoinBaseAddress; - public static from(output: BitcoinApi.Output) { + @Field(() => Boolean) + spent: boolean; + + public static from(output: BitcoinApi.Output): BitcoinBaseOutput { return { scriptpubkey: output.scriptpubkey, scriptpubkeyAsm: output.scriptpubkey_asm, diff --git a/backend/src/modules/bitcoin/output/output.module.ts b/backend/src/modules/bitcoin/output/output.module.ts index c4664b10..36e713c0 100644 --- a/backend/src/modules/bitcoin/output/output.module.ts +++ b/backend/src/modules/bitcoin/output/output.module.ts @@ -4,4 +4,4 @@ import { BitcoinOutputResolver } from './output.resolver'; @Module({ providers: [BitcoinOutputResolver], }) -export class OutputModule {} +export class BitcoinOutputModule {} diff --git a/backend/src/modules/bitcoin/output/output.resolver.ts b/backend/src/modules/bitcoin/output/output.resolver.ts index 37e4de78..a6612402 100644 --- a/backend/src/modules/bitcoin/output/output.resolver.ts +++ b/backend/src/modules/bitcoin/output/output.resolver.ts @@ -14,4 +14,10 @@ export class BitcoinOutputResolver { address: output.scriptpubkeyAddress, }; } + + @ResolveField(() => Boolean) + public async spent(@Parent() output: BitcoinOutput): Promise { + // TODO: implement this resolver + return false; + } } diff --git a/backend/src/modules/bitcoin/transaction/transaction.model.ts b/backend/src/modules/bitcoin/transaction/transaction.model.ts index a0f35d9b..d791ae1b 100644 --- a/backend/src/modules/bitcoin/transaction/transaction.model.ts +++ b/backend/src/modules/bitcoin/transaction/transaction.model.ts @@ -1,9 +1,9 @@ import { Field, Float, Int, ObjectType } from '@nestjs/graphql'; import * as BitcoinApi from 'src/core/bitcoin-api/bitcoin-api.schema'; import { BitcoinInput } from '../input/input.model'; -import { BitcoinOutput } from '../output/output.model'; +import { BitcoinBaseOutput, BitcoinOutput } from '../output/output.model'; -export type BitcoinBaseTransaction = BitcoinTransaction; +export type BitcoinBaseTransaction = Omit; @ObjectType({ description: 'Bitcoin Transaction' }) export class BitcoinTransaction { @@ -23,7 +23,7 @@ export class BitcoinTransaction { vin: BitcoinInput[]; @Field(() => [BitcoinOutput]) - vout: BitcoinOutput[]; + vout: BitcoinBaseOutput[]; @Field(() => Float) size: number; @@ -37,10 +37,18 @@ export class BitcoinTransaction { @Field(() => Float) fee: number; + @Field(() => Float) + feeRate: number; + @Field(() => Boolean) confirmed: boolean; + @Field(() => Float) + confirmations: number; + public static from(tx: BitcoinApi.Transaction): BitcoinBaseTransaction { + const vSize = Math.ceil(tx.weight / 4); + return { blockHeight: tx.status.block_height, blockHash: tx.status.block_hash, @@ -52,6 +60,7 @@ export class BitcoinTransaction { locktime: new Date(tx.locktime), weight: tx.weight, fee: tx.fee, + feeRate: tx.fee / vSize, confirmed: tx.status.confirmed, }; } diff --git a/backend/src/modules/bitcoin/transaction/transaction.resolver.ts b/backend/src/modules/bitcoin/transaction/transaction.resolver.ts index d813a069..b6c476fe 100644 --- a/backend/src/modules/bitcoin/transaction/transaction.resolver.ts +++ b/backend/src/modules/bitcoin/transaction/transaction.resolver.ts @@ -1,6 +1,7 @@ import DataLoader from 'dataloader'; -import { Args, Query, Resolver } from '@nestjs/graphql'; +import { Args, Float, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { Loader } from '@applifting-io/nestjs-dataloader'; +import { BitcoinApiService } from '../../../core/bitcoin-api/bitcoin-api.service'; import { BitcoinBaseTransaction, BitcoinTransaction } from './transaction.model'; import { BitcoinTransactionLoader, @@ -9,6 +10,8 @@ import { @Resolver(() => BitcoinTransaction) export class BitcoinTransactionResolver { + constructor(private bitcoinApiService: BitcoinApiService) {} + @Query(() => BitcoinTransaction, { name: 'btcTransaction', nullable: true }) public async getTransaction( @Args('txid') txid: string, @@ -18,4 +21,15 @@ export class BitcoinTransactionResolver { const transaction = await transactionLoader.load(txid); return BitcoinTransaction.from(transaction); } + + @ResolveField(() => Float) + public async confirmations(@Parent() transaction: BitcoinTransaction): Promise { + if (!transaction.confirmed) { + return 0; + } + + // TODO: should this resolver be refactored with a dataloader? + const info = await this.bitcoinApiService.getBlockchainInfo(); + return info.blocks - transaction.blockHeight + 1; + } } diff --git a/backend/src/modules/ckb/address/address.model.ts b/backend/src/modules/ckb/address/address.model.ts new file mode 100644 index 00000000..dcc17144 --- /dev/null +++ b/backend/src/modules/ckb/address/address.model.ts @@ -0,0 +1,25 @@ +import { Field, Float, ObjectType } from '@nestjs/graphql'; +import { CkbTransaction } from '../transaction/transaction.model'; + +export type CkbBaseAddress = Pick; + +@ObjectType({ description: 'CKB Address' }) +export class CkbAddress { + @Field(() => String) + address: string; + + @Field(() => Float) + shannon: number; + + @Field(() => Float) + transactionCount: number; + + @Field(() => [CkbTransaction]) + transactions: CkbTransaction[]; + + public static from(address: string): CkbBaseAddress { + return { + address, + }; + } +} diff --git a/backend/src/modules/ckb/address/address.module.ts b/backend/src/modules/ckb/address/address.module.ts new file mode 100644 index 00000000..3a196257 --- /dev/null +++ b/backend/src/modules/ckb/address/address.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { BitcoinApiModule } from 'src/core/bitcoin-api/bitcoin-api.module'; +import { CkbAddressResolver } from './address.resolver'; + +@Module({ + imports: [BitcoinApiModule], + providers: [CkbAddressResolver], +}) +export class CkbAddressModule {} diff --git a/backend/src/modules/ckb/address/address.resolver.ts b/backend/src/modules/ckb/address/address.resolver.ts new file mode 100644 index 00000000..b4366646 --- /dev/null +++ b/backend/src/modules/ckb/address/address.resolver.ts @@ -0,0 +1,34 @@ +import { Args, Float, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { CkbTransaction } from '../transaction/transaction.model'; +import { CkbAddress } from './address.model'; + +@Resolver(() => CkbAddress) +export class CkbAddressResolver { + @Query(() => CkbAddress, { name: 'ckbAddress', nullable: true }) + public async getCkbAddress(@Args('address') address: string): Promise { + // TODO: implement this resolver + return null; + } + + @ResolveField(() => Float) + public async shannon(@Parent() address: CkbAddress): Promise { + // TODO: implement this resolver + return 0; + } + + @ResolveField(() => Float) + public async transactionCount(@Parent() address: CkbAddress): Promise { + // TODO: implement this resolver + return 0; + } + + @ResolveField(() => [CkbTransaction]) + public async transactions( + @Parent() address: CkbAddress, + @Args('page', { nullable: true }) page?: number, + @Args('pageSize', { nullable: true }) pageSize?: number, + ): Promise { + // TODO: implement this resolver + return []; + } +} diff --git a/backend/src/modules/ckb/block/block.model.ts b/backend/src/modules/ckb/block/block.model.ts index 811931a8..bd21fd2a 100644 --- a/backend/src/modules/ckb/block/block.model.ts +++ b/backend/src/modules/ckb/block/block.model.ts @@ -1,9 +1,11 @@ import { toNumber } from 'lodash'; import { Field, Float, Int, ObjectType } from '@nestjs/graphql'; import * as CkbRpc from 'src/core/ckb-rpc/ckb-rpc.interface'; +import { CkbAddress } from '../address/address.model'; +import { FeeRateRange } from '../../bitcoin/block/block.model'; import { CkbTransaction } from '../transaction/transaction.model'; -export type CkbBaseBlock = Omit; +export type CkbBaseBlock = Omit; @ObjectType({ description: 'CKB Block' }) export class CkbBlock { @@ -25,6 +27,12 @@ export class CkbBlock { @Field(() => Float) totalFee: number; + @Field(() => FeeRateRange) + feeRateRange: FeeRateRange; + + @Field(() => CkbAddress) + miner: CkbAddress; + @Field(() => [CkbTransaction]) transactions: CkbTransaction[]; diff --git a/backend/src/modules/ckb/block/block.resolver.ts b/backend/src/modules/ckb/block/block.resolver.ts index 69dd72ac..5e6eab64 100644 --- a/backend/src/modules/ckb/block/block.resolver.ts +++ b/backend/src/modules/ckb/block/block.resolver.ts @@ -3,6 +3,8 @@ import { BI } from '@ckb-lumos/bi'; import { Loader } from '@applifting-io/nestjs-dataloader'; import { Args, Float, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { CkbBaseTransaction, CkbTransaction } from '../transaction/transaction.model'; +import { FeeRateRange } from '../../bitcoin/block/block.model'; +import { CkbAddress } from '../address/address.model'; import { CkbBlock, CkbBaseBlock } from './block.model'; import { CkbBlockEconomicStateLoader, @@ -36,6 +38,21 @@ export class CkbBlockResolver { return BI.from(blockEconomicState.txs_fee).toNumber(); } + @ResolveField(() => FeeRateRange) + public async feeRateRange(@Parent() block: CkbBlock): Promise { + // TODO: implement this resolver + return { + min: 0, + max: 0, + }; + } + + @ResolveField(() => CkbAddress) + public async miner(@Parent() block: CkbBlock): Promise { + // TODO: implement this resolver + return null; + } + @ResolveField(() => [String]) public async transactions( @Parent() { hash }: CkbBlock, diff --git a/backend/src/modules/ckb/cell/cell.model.ts b/backend/src/modules/ckb/cell/cell.model.ts index 115ed462..b170f611 100644 --- a/backend/src/modules/ckb/cell/cell.model.ts +++ b/backend/src/modules/ckb/cell/cell.model.ts @@ -18,7 +18,7 @@ export class CkbXUDTInfo { typeHash: string; } -export type CkbBaseCell = Omit; +export type CkbBaseCell = Omit; @ObjectType({ description: 'CKB Cell' }) export class CkbCell { @@ -40,6 +40,9 @@ export class CkbCell { @Field(() => CkbXUDTInfo, { nullable: true }) xudtInfo: CkbXUDTInfo; + @Field(() => Boolean) + spent: boolean; + public static from(tx: CkbRpc.Transaction, index: number): CkbBaseCell { const output = tx.outputs[index]; return { diff --git a/backend/src/modules/ckb/cell/cell.resolver.ts b/backend/src/modules/ckb/cell/cell.resolver.ts index 23024ad8..a2e34292 100644 --- a/backend/src/modules/ckb/cell/cell.resolver.ts +++ b/backend/src/modules/ckb/cell/cell.resolver.ts @@ -11,4 +11,10 @@ export class CellResolver { const tx = await this.ckbExplorerService.getTransaction(cell.txHash); return tx.data.attributes.display_outputs[cell.index].xudt_info; } + + @ResolveField(() => Boolean) + public async spent(@Parent() cell: CkbCell): Promise { + // TODO: implement this resolver + return false; + } } diff --git a/backend/src/modules/ckb/ckb.module.ts b/backend/src/modules/ckb/ckb.module.ts index d1cbf569..dea0fcce 100644 --- a/backend/src/modules/ckb/ckb.module.ts +++ b/backend/src/modules/ckb/ckb.module.ts @@ -5,9 +5,17 @@ import { CkbCellModule } from './cell/cell.module'; import { CkbTransactionModule } from './transaction/transaction.module'; import { CkbScriptModule } from './script/script.module'; import { CkbResolver } from './ckb.resolver'; +import { CkbAddressModule } from './address/address.module'; @Module({ - imports: [CkbRpcModule, CkbBlockModule, CkbTransactionModule, CkbCellModule, CkbScriptModule], + imports: [ + CkbRpcModule, + CkbBlockModule, + CkbTransactionModule, + CkbAddressModule, + CkbCellModule, + CkbScriptModule, + ], providers: [CkbResolver], }) export class CkbModule {} diff --git a/backend/src/modules/ckb/transaction/transaction.model.ts b/backend/src/modules/ckb/transaction/transaction.model.ts index 8628821e..f67c2b5b 100644 --- a/backend/src/modules/ckb/transaction/transaction.model.ts +++ b/backend/src/modules/ckb/transaction/transaction.model.ts @@ -4,7 +4,10 @@ import * as CkbRpc from 'src/core/ckb-rpc/ckb-rpc.interface'; import { CkbBaseCell, CkbCell } from '../cell/cell.model'; import { CkbBlock } from '../block/block.model'; -export type CkbBaseTransaction = Omit; +export type CkbBaseTransaction = Omit< + CkbTransaction, + 'block' | 'feeRate' | 'size' | 'inputs' | 'confirmations' +>; @ObjectType({ description: 'CKB Transaction' }) export class CkbTransaction { @@ -20,6 +23,12 @@ export class CkbTransaction { @Field(() => Float) fee: number; + @Field(() => Float) + feeRate: number; + + @Field(() => Float) + size: number; + @Field(() => [CkbCell]) inputs: CkbBaseCell[]; @@ -29,6 +38,9 @@ export class CkbTransaction { @Field(() => CkbBlock) block: CkbBlock; + @Field(() => Float) + confirmations: number; + public static from(transactionWithStatus: CkbRpc.TransactionWithStatusResponse) { const { transaction, tx_status, fee } = transactionWithStatus; const isCellbase = transaction.inputs[0].previous_output.tx_hash.endsWith('0'.repeat(64)); diff --git a/backend/src/modules/ckb/transaction/transaction.resolver.ts b/backend/src/modules/ckb/transaction/transaction.resolver.ts index 36880529..770941e2 100644 --- a/backend/src/modules/ckb/transaction/transaction.resolver.ts +++ b/backend/src/modules/ckb/transaction/transaction.resolver.ts @@ -1,7 +1,7 @@ import DataLoader from 'dataloader'; import { BI } from '@ckb-lumos/bi'; import { Loader } from '@applifting-io/nestjs-dataloader'; -import { Args, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Args, Float, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { CkbTransaction, CkbBaseTransaction } from './transaction.model'; import { CkbTransactionLoader, CkbTransactionLoaderResponse } from './transaction.dataloader'; import { CkbBlockLoader, CkbBlockLoaderResponse } from '../block/block.dataloader'; @@ -53,4 +53,22 @@ export class CkbTransactionResolver { const block = await blockLoader.load(transaction.blockNumber.toString()); return CkbBlock.from(block); } + + @ResolveField(() => Float) + public async feeRate(@Parent() transaction: CkbBaseTransaction): Promise { + // TODO: implement this resolver + return 0; + } + + @ResolveField(() => Float) + public async size(@Parent() transaction: CkbBaseTransaction): Promise { + // TODO: implement this resolver + return 0; + } + + @ResolveField(() => Float) + public async confirmations(@Parent() transaction: CkbBaseTransaction): Promise { + // TODO: implement this resolver + return 0; + } } diff --git a/backend/src/modules/rgbpp/coin/coin.resolver.ts b/backend/src/modules/rgbpp/coin/coin.resolver.ts index 25864dab..b42b8478 100644 --- a/backend/src/modules/rgbpp/coin/coin.resolver.ts +++ b/backend/src/modules/rgbpp/coin/coin.resolver.ts @@ -8,7 +8,7 @@ import { RgbppBaseCoin, RgbppCoin, RgbppCoinList } from './coin.model'; export class RgbppCoinResolver { constructor(private ckbExplorerService: CkbExplorerService) {} - @Query(() => RgbppCoinList, { name: 'rgbppCoinList' }) + @Query(() => RgbppCoinList, { name: 'rgbppCoins' }) public async coins( @Args('page', { type: () => Int, nullable: true }) page: number = 1, @Args('pageSize', { type: () => Int, nullable: true }) pageSize: number = 10, diff --git a/backend/src/modules/rgbpp/transaction/transaction.resolver.ts b/backend/src/modules/rgbpp/transaction/transaction.resolver.ts index e90d4348..756396b2 100644 --- a/backend/src/modules/rgbpp/transaction/transaction.resolver.ts +++ b/backend/src/modules/rgbpp/transaction/transaction.resolver.ts @@ -11,7 +11,11 @@ import { BitcoinTransactionLoader, BitcoinTransactionLoaderResponse, } from 'src/modules/bitcoin/transaction/transaction.dataloader'; -import { RgbppBaseTransaction, RgbppLatestTransactionList, RgbppTransaction } from './transaction.model'; +import { + RgbppTransaction, + RgbppBaseTransaction, + RgbppLatestTransactionList, +} from './transaction.model'; import { RgbppTransactionService } from './transaction.service'; @Resolver(() => RgbppTransaction) @@ -19,7 +23,7 @@ export class RgbppTransactionResolver { constructor(private transactionService: RgbppTransactionService) {} @Query(() => RgbppLatestTransactionList, { name: 'rgbppLatestTransactions' }) - public async getLatestTransaction( + public async getLatestTransactions( @Args('page', { type: () => Int, nullable: true }) page: number = 1, @Args('pageSize', { type: () => Int, nullable: true }) pageSize: number = 10, ): Promise { @@ -56,6 +60,9 @@ export class RgbppTransactionResolver { @Loader(BitcoinTransactionLoader) btcTxLoader: DataLoader, ) { + if (!tx.btcTxid) { + return null; + } const btcTx = await btcTxLoader.load(tx.btcTxid); if (!btcTx) { return null; diff --git a/backend/src/modules/rgbpp/transaction/transaction.service.ts b/backend/src/modules/rgbpp/transaction/transaction.service.ts index 657fea0e..61ba8af4 100644 --- a/backend/src/modules/rgbpp/transaction/transaction.service.ts +++ b/backend/src/modules/rgbpp/transaction/transaction.service.ts @@ -1,7 +1,11 @@ import { Injectable } from '@nestjs/common'; import { BitcoinApiService } from 'src/core/bitcoin-api/bitcoin-api.service'; import { CkbExplorerService } from 'src/core/ckb-explorer/ckb-explorer.service'; -import { RgbppBaseTransaction, RgbppLatestTransactionList, RgbppTransaction } from './transaction.model'; +import { + RgbppTransaction, + RgbppBaseTransaction, + RgbppLatestTransactionList, +} from './transaction.model'; @Injectable() export class RgbppTransactionService { diff --git a/backend/src/schema.gql b/backend/src/schema.gql index 5992df44..750e2727 100644 --- a/backend/src/schema.gql +++ b/backend/src/schema.gql @@ -25,38 +25,15 @@ type CkbCell { type: CkbScript lock: CkbScript! xudtInfo: CkbXUDTInfo + spent: Boolean! } -"""CKB Block""" -type CkbBlock { - version: Int! - hash: String! - number: Int! - timestamp: Timestamp! - transactionsCount: Int! - totalFee: Float! - transactions: [CkbTransaction!]! -} - -""" -`Date` type as integer. Type represents date and time as number of milliseconds from start of UNIX epoch. -""" -scalar Timestamp - -"""CKB Transaction""" -type CkbTransaction { - isCellbase: Boolean! - blockNumber: Int! - hash: String! - fee: Float! - inputs: [CkbCell!]! - outputs: [CkbCell!]! - block: CkbBlock! -} - -"""CKB ChainInfo""" -type CkbChainInfo { - tipBlockNumber: Float! +"""CKB Address""" +type CkbAddress { + address: String! + shannon: Float! + transactionCount: Float! + transactions(page: Float, pageSize: Float): [CkbTransaction!]! } """Bitcoin Address""" @@ -76,6 +53,7 @@ type BitcoinOutput { scriptpubkeyAddress: String value: Float! address: BitcoinAddress + spent: Boolean! } """Bitcoin Input""" @@ -101,7 +79,70 @@ type BitcoinTransaction { locktime: Timestamp! weight: Float! fee: Float! + feeRate: Float! confirmed: Boolean! + confirmations: Float! +} + +""" +`Date` type as integer. Type represents date and time as number of milliseconds from start of UNIX epoch. +""" +scalar Timestamp + +"""Fee Rate Range""" +type FeeRateRange { + min: Float! + max: Float! +} + +"""Bitcoin Block""" +type BitcoinBlock { + id: String! + height: Float! + version: Int! + timestamp: Timestamp! + txCount: Float! + size: Float! + weight: Float! + bits: Float! + difficulty: Float! + miner: BitcoinAddress! + reward: Float! + totalFee: Float! + feeRateRange: FeeRateRange! + transactions: [BitcoinTransaction!]! +} + +"""CKB Block""" +type CkbBlock { + version: Int! + hash: String! + number: Int! + timestamp: Timestamp! + transactionsCount: Int! + totalFee: Float! + feeRateRange: FeeRateRange! + miner: CkbAddress! + transactions: [CkbTransaction!]! +} + +"""CKB Transaction""" +type CkbTransaction { + isCellbase: Boolean! + blockNumber: Int! + hash: String! + fee: Float! + feeRate: Float! + size: Float! + inputs: [CkbCell!]! + outputs: [CkbCell!]! + block: CkbBlock! + confirmations: Float! +} + +"""CKB ChainInfo""" +type CkbChainInfo { + tipBlockNumber: Float! } """RGB++ Transaction""" @@ -169,38 +210,17 @@ type BitcoinChainInfo { fees: BitcoinFees! } -"""Fee Rate Range""" -type FeeRateRange { - min: Float! - max: Float! -} - -"""Bitcoin Block""" -type BitcoinBlock { - id: String! - height: Float! - version: Int! - timestamp: Timestamp! - txCount: Float! - size: Float! - weight: Float! - bits: Float! - difficulty: Float! - miner: BitcoinAddress! - totalFee: Float! - feeRateRange: FeeRateRange! - transactions: [BitcoinTransaction!]! -} - type Query { ckbChainInfo: CkbChainInfo! ckbBlock(heightOrHash: String!): CkbBlock! ckbTransaction(txHash: String!): CkbTransaction + ckbAddress(address: String!): CkbAddress btcChainInfo: BitcoinChainInfo! btcBlock(hashOrHeight: String!): BitcoinBlock! btcTransaction(txid: String!): BitcoinTransaction + btcAddress(address: String!): BitcoinAddress rgbppLatestTransactions(page: Int, pageSize: Int): RgbppLatestTransactionList! rgbppTransaction(txidOrTxHash: String!): RgbppTransaction - rgbppCoinList(page: Int, pageSize: Int): RgbppCoinList! + rgbppCoins(page: Int, pageSize: Int): RgbppCoinList! rgbppCoin(typeHash: String!): RgbppCoin! } \ No newline at end of file