diff --git a/package.json b/package.json index b313f96..9d45d92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "db3.js", - "version": "0.3.9", + "version": "0.3.10", "description": "DB3 Network Javascript API", "author": "dbpunk labs", "keywords": [ diff --git a/src/client/client_v2.ts b/src/client/client_v2.ts index d1dbcf3..378f5e6 100644 --- a/src/client/client_v2.ts +++ b/src/client/client_v2.ts @@ -223,3 +223,17 @@ export async function scanGcRecords( const response = await client.provider.scanGcRecords(start, limit) return response.records } + +/** + * + * Get the contract sync status + * + * + * @param client - the instance of client + * @returns the records + * + **/ +export async function getContractSyncStatus(client: Client) { + const response = await client.indexer.getContractSyncStatus() + return response.statusList +} diff --git a/src/index.ts b/src/index.ts index d5630a6..1d599ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,9 +42,12 @@ export { export { createDocumentDatabase, + createEventDatabase, showDatabase, createCollection, showCollection, + getDatabase, + getCollection, } from './store/database_v2' export { Index, IndexType } from './proto/db3_database_v2' diff --git a/src/provider/indexer_provider.ts b/src/provider/indexer_provider.ts index 5eb4053..77943c0 100644 --- a/src/provider/indexer_provider.ts +++ b/src/provider/indexer_provider.ts @@ -19,7 +19,11 @@ import { GrpcWebOptions, } from '@protobuf-ts/grpcweb-transport' import { IndexerNodeClient } from '../proto/db3_indexer.client' -import { RunQueryRequest, GetSystemStatusRequest } from '../proto/db3_indexer' +import { + RunQueryRequest, + GetSystemStatusRequest, + GetContractSyncStatusRequest, +} from '../proto/db3_indexer' import { Query } from '../proto/db3_database_v2' export class IndexerProvider { @@ -46,9 +50,16 @@ export class IndexerProvider { const { response } = await this.client.runQuery(request) return response } + async getSystemStatus() { const request: GetSystemStatusRequest = {} const { response } = await this.client.getSystemStatus(request) return response } + + async getContractSyncStatus() { + const request: GetContractSyncStatusRequest = {} + const { response } = await this.client.getContractSyncStatus(request) + return response + } } diff --git a/src/provider/storage_provider_v2.ts b/src/provider/storage_provider_v2.ts index 390c0fa..f794898 100644 --- a/src/provider/storage_provider_v2.ts +++ b/src/provider/storage_provider_v2.ts @@ -33,6 +33,7 @@ import { ScanGcRecordRequest, GetSystemStatusRequest, SetupRequest, + GetDatabaseRequest, } from '../proto/db3_storage' import { fromHEX, toHEX } from '../crypto/crypto_utils' import { DB3Account } from '../account/types' @@ -202,6 +203,14 @@ export class StorageProviderV2 { return response } + async getDatabase(addr: string) { + const request: GetDatabaseRequest = { + addr, + } + const { response } = await this.client.getDatabase(request) + return response + } + parseMutationBody(body: MutationBody) { const typedMsg = new TextDecoder().decode(body.payload) const typedData = JSON.parse(typedMsg) diff --git a/src/store/database_v2.ts b/src/store/database_v2.ts index b5dbeb3..29a9e45 100644 --- a/src/store/database_v2.ts +++ b/src/store/database_v2.ts @@ -31,12 +31,84 @@ import { DocumentMask, Mutation_BodyWrapper, DocumentDatabaseMutation, + EventDatabaseMutation, } from '../proto/db3_mutation_v2' import { Client } from '../client/types' import { toHEX, fromHEX } from '../crypto/crypto_utils' import { Index } from '../proto/db3_database_v2' +/** + * + * Create an event database to store contract events + * + * ```ts + * const {db, result} = await createEventDatabase(client, "my_db") + * ``` + * @param client - the db3 client instance + * @param desc - the description for the database + * @returns the {@link CreateDBResult} + * + **/ +export async function createEventDatabase( + client: Client, + desc: string, + contractAddress: string, + tables: string[], + abi: string, + evmNodeUrl: string +) { + const collections = tables.map((name) => { + const collection: CollectionMutation = { + indexFields: [], + collectionName: name, + } + return collection + }) + + const mutation: EventDatabaseMutation = { + contractAddress, + ttl: '0', + desc, + tables: collections, + eventsJsonAbi: abi, + evmNodeUrl, + } + const body: Mutation_BodyWrapper = { + body: { + oneofKind: 'eventDatabaseMutation', + eventDatabaseMutation: mutation, + }, + dbAddress: new Uint8Array(0), + } + + const dm: Mutation = { + action: MutationAction.CreateEventDB, + bodies: [body], + } + + const payload = Mutation.toBinary(dm) + const response = await client.provider.sendMutation( + payload, + client.nonce.toString() + ) + if (response.code == 0) { + client.nonce += 1 + return { + db: { + addr: response.items[0].value, + client, + } as Database, + result: { + id: response.id, + block: response.block, + order: response.order, + } as MutationResult, + } + } else { + throw new Error('fail to create database') + } +} /** * * Create a document database to group the collections @@ -84,6 +156,68 @@ export async function createDocumentDatabase(client: Client, desc: string) { } } +/** + * + * Get the collection by an db address and collection name + * + * ```ts + * const database = await getCollection("0x....", "col1", client) + * ``` + * @param addr - a hex format string address + * @param name - the name of collection + * @param client- the db3 client instance + * @returns the {@link Database}[] + * + **/ +export async function getCollection( + addr: string, + name: string, + client: Client +) { + const db = await getDatabase(addr, client) + const collections = await showCollection(db) + const targetCollections = collections.filter((item) => item.name === name) + if (targetCollections.length > 0) { + return targetCollections[0] + } else { + throw new Error( + 'db with addr ' + addr + ' has no collection with name ' + name + ) + } +} +/** + * + * Get the database by an address + * + * ```ts + * const database = await getDatabase("0x....", client) + * ``` + * @param addr - a hex format string address + * @param client- the db3 client instance + * @returns the {@link Database}[] + * + **/ +export async function getDatabase(addr: string, client: Client) { + const response = await client.provider.getDatabase(addr) + const db = response.database + if (!db) { + throw new Error('db with addr ' + addr + ' does not exist') + } + if (db.database.oneofKind === 'docDb') { + return { + addr, + client, + internal: db, + } + } else { + return { + addr, + client, + internal: db, + } + } +} + /** * * Query the all databases created by an address diff --git a/src/store/types.ts b/src/store/types.ts index a38c756..53408c4 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -56,3 +56,7 @@ export type QueryResult = { docs: Array> collection: Collection } + +export type EventDatabaseOption = { + ttl: string +} diff --git a/tests/client_v2.test.ts b/tests/client_v2.test.ts index 0d5ea93..776c6a0 100644 --- a/tests/client_v2.test.ts +++ b/tests/client_v2.test.ts @@ -26,6 +26,7 @@ import { getStorageNodeStatus, getIndexNodeStatus, configRollup, + getContractSyncStatus, } from '../src/client/client_v2' import { addDoc, @@ -36,6 +37,7 @@ import { import { createRandomAccount } from '../src/account/db3_account' import { createDocumentDatabase, + createEventDatabase, showDatabase, createCollection, } from '../src/store/database_v2' @@ -59,6 +61,27 @@ describe('test db3.js client module', () => { return client } + test('create event db smoke test', async () => { + const client = await createTestClient() + expect(1).toBe(client.nonce) + const abi = ` + [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}] + ` + const evmNodeUrl = + 'wss://polygon-mainnet.g.alchemy.com/v2/EH9ZSJ0gS7a1DEIohAWMbhP33lK6qHj9' + const response = await createEventDatabase( + client, + 'desc', + '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', + ['Transfer', 'Deposit', 'Approval', 'Withdrawal'], + abi, + evmNodeUrl + ) + console.log(response) + await new Promise((r) => setTimeout(r, 10000)) + console.log(await getContractSyncStatus(client)) + }) + test('create client smoke test', async () => { const client = await createTestClient() expect(1).toBe(client.nonce) diff --git a/tests/database_v2.test.ts b/tests/database_v2.test.ts index 002fbf9..2a40f8e 100644 --- a/tests/database_v2.test.ts +++ b/tests/database_v2.test.ts @@ -28,6 +28,8 @@ import { showDatabase, createCollection, showCollection, + getDatabase, + getCollection, } from '../src/store/database_v2' import { Index, IndexType } from '../src/proto/db3_database_v2' @@ -51,6 +53,8 @@ describe('test db3.js sdk database', () => { expect(databases2[0].addr).toBe(db.addr) expect(databases2[0].internal.database.oneofKind).toBe('docDb') expect(databases2[0].internal.database.docDb.desc).toBe('test_db') + const database2 = await getDatabase(db.addr, client) + expect(database2.addr).toBe(db.addr) }) test('collection smoke test from sdk', async () => { @@ -72,6 +76,8 @@ describe('test db3.js sdk database', () => { expect(collection).toBeDefined() expect(result).toBeDefined() expect(result.id).toBeTruthy() + const collection2 = await getCollection(db.addr, 'col1', client) + expect(collection.name).toBe(collection2.name) } }) }) diff --git a/thirdparty/db3 b/thirdparty/db3 index c054449..5e5488e 160000 --- a/thirdparty/db3 +++ b/thirdparty/db3 @@ -1 +1 @@ -Subproject commit c05444963494f4a662e8fee218029c7d16473603 +Subproject commit 5e5488e323f9f0742a60de16cd207595a9a6524a