diff --git a/package-lock.json b/package-lock.json index 52613bb..202405a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -158,7 +158,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.2.tgz", "integrity": "sha512-B4UoIjPi0QkvIzZH5fV3BQLWrqSYABdrzZQSI9sJA9aA+iC0ohOzFwVVGXanlxeDAy1bcvPbb29f6sVUk0UnnQ==", - "license": "Apache-2.0", "peer": true, "dependencies": { "eventsource": "^2.0.2" @@ -168,7 +167,6 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.3.tgz", "integrity": "sha512-jIg5nTrDwnite4fXao3dD83eCpTvInTjZon/rZZrIftIegh4XxyVb5G2mpMqXrVGk1e8SVXm3Kj5OtfMplQs0w==", - "license": "Apache-2.0", "peer": true, "dependencies": { "node-fetch": "^2.6.9" @@ -178,7 +176,6 @@ "version": "2.2.6", "resolved": "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.2.6.tgz", "integrity": "sha512-kyoDz5EqgsycYP+A+JbVsAUYHNT059BCrK+m0pqxykMODwpziuSAXfwAZmHcg8v7NB9VKYbdFY55xKeXOuEd0w==", - "license": "Apache-2.0", "peer": true, "dependencies": { "tweetnacl": "^1.0.3", @@ -186,10 +183,9 @@ } }, "node_modules/@tonconnect/sdk": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@tonconnect/sdk/-/sdk-3.0.5.tgz", - "integrity": "sha512-ow0qnN4s3iQ/r2uXobZ7YzdQBtan/36CgCT9IP35G07g38UxsUXwzw8ANmJTDj/JPiQcIKuYBMfIwIX9zLM0wg==", - "license": "Apache-2.0", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@tonconnect/sdk/-/sdk-3.0.6.tgz", + "integrity": "sha512-dJipe0Cw43p/7o3Pa6Y6h0QMDtY2V2YKzwdCqcYvmyCYadBNmvA+8ScH9QK5GpkngRJnYaWq+321lAaQTFpUwA==", "peer": true, "dependencies": { "@tonconnect/isomorphic-eventsource": "^0.0.2", @@ -420,7 +416,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", - "license": "MIT", "peer": true }, "node_modules/dataloader": { @@ -490,7 +485,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", - "license": "MIT", "peer": true, "engines": { "node": ">=12.0.0" @@ -1158,7 +1152,6 @@ "version": "0.15.1", "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "license": "Unlicense", "peer": true }, "node_modules/typescript": { diff --git a/package.json b/package.json index 5f8a57c..89a9479 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "build": "tsc -p .", - "start": "ts-node src/index.ts" + "start": "ts-node src/index.ts", + "start:resync": "ts-node src/services/users/resync.ts" }, "keywords": [], "author": "", diff --git a/src/db/database.ts b/src/db/database.ts index b6d747b..bd28ce5 100644 --- a/src/db/database.ts +++ b/src/db/database.ts @@ -1,9 +1,8 @@ -import {Log, User} from "./types"; -import {Pool, PoolConfig} from "pg"; -import { Address } from "@ton/core"; -import { PoolConfig as EvaaPoolConifg, PoolAssetConfig, PoolAssetsConfig } from "@evaafi/sdk"; -import { UserPrincipals } from "../services/indexer/types"; -import { AssertionError } from "assert"; +import { PoolConfig as EvaaPoolConifg, PoolAssetConfig, PoolAssetsConfig } from "@evaafi/sdk" +import { Address } from "@ton/core" +import { Pool, PoolConfig } from "pg" +import { UserPrincipals } from "../services/indexer/types" +import { Log, User } from "./types" export function getFriendlyAddress(address: string | Address): string { if (typeof address === 'string') { @@ -243,6 +242,22 @@ export class MyDatabase { ); } + async getUsers(limit: number, offset: number) { + const usersQuery = this.pgPool.query( + `SELECT * FROM ${this.usersTable} LIMIT $1 OFFSET $2`, [limit, offset] + ); + const countQuery = this.pgPool.query( + `SELECT COUNT(*) FROM ${this.usersTable}` + ); + + const [users, countResult] = await Promise.all([usersQuery, countQuery]); + + return { + users: users.rows, + count: parseInt(countResult.rows[0].count, 10) + }; + } + async addUser( wallet_address: string, contract_address: string, code_version: number, created_at: number, updated_at: number, principals: UserPrincipals, state: number) { diff --git a/src/services/users/resync.ts b/src/services/users/resync.ts new file mode 100644 index 0000000..3f42e0c --- /dev/null +++ b/src/services/users/resync.ts @@ -0,0 +1,184 @@ +#!/usr/bin/env ts-node + +import { Address, Dictionary, TonClient } from "@ton/ton"; +import { getFriendlyAddress, MyDatabase } from "../../db/database"; + +import { + PoolConfig as EvaaPoolConfig, + MAINNET_ALTS_POOL_CONFIG, + MAINNET_LP_POOL_CONFIG, + MAINNET_POOL_CONFIG, + PoolAssetConfig, +} from "@evaafi/sdk"; +import * as dotenv from "dotenv"; +import { PoolConfig } from "pg"; +import { GetResult } from "../indexer/types"; + +dotenv.config(); + +let tonClient = undefined; + +async function getAllUserScData( + userContractAddress: Address +): Promise { + const res = await tonClient + .provider(userContractAddress) + .get("getAllUserScData", []); + + return { + exit_code: 0, + gas_used: 0, + stack: res.stack, + }; +} + +async function getUserData( + db: MyDatabase, + userContractAddress: Address +): Promise<{ + codeVersion: number; + userAddress: Address; + principals: Map; + state: number; +}> { + const result = await getAllUserScData(userContractAddress); + if (result.exit_code !== 0) { + throw new Error( + `User contract ${getFriendlyAddress( + userContractAddress + )} returned exit code ${result.exit_code}` + ); + } + const codeVersion = result.stack.readNumber(); + result.stack.readCell(); // skip master cell + const userAddress = result.stack.readCell().beginParse().loadAddress(); + const principalsCell = result.stack.readCellOpt(); + + const parser = principalsCell.beginParse(); + const principalsDict = parser.loadDictDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(64) + ); + + const state = result.stack.readNumber(); + + if (!principalsCell) { + throw new Error("No principals cell found in the getPrincipals response"); + } + + const principals: Map = new Map(); + + for (const asset of db.evaaPool.poolAssetsConfig) { + if (principalsDict && principalsDict.has(asset.assetId)) { + principals.set(asset, principalsDict.get(asset.assetId)); + } + } + + return { + codeVersion, + userAddress, + principals, + state, + }; +} + +async function syncUsers(db: MyDatabase) { + try { + let offset = 0; + const limit = 100; + let total = 0; + + while (true) { + const { users, count } = await db.getUsers(limit, offset); + + for (const user of users) { + console.log( + `Syncing user: wallet ${getFriendlyAddress( + user.wallet_address + )}, contract ${getFriendlyAddress(user.contract_address)}` + ); + const { codeVersion, userAddress, principals, state } = + await getUserData(db, user.contract_address); + const now = Date.now(); + const existingUser = await db.getUser( + getFriendlyAddress(user.contract_address) + ); + if (existingUser) { + await db.updateUser( + getFriendlyAddress(user.contract_address), + codeVersion, + now, + now, + principals, + state + ); + console.log( + `User ${getFriendlyAddress(user.contract_address)} updated` + ); + } else { + await db.addUser( + getFriendlyAddress(userAddress), + getFriendlyAddress(user.contract_address), + codeVersion, + now, + now, + principals, + state + ); + console.log( + `User ${getFriendlyAddress(user.contract_address)} added` + ); + } + } + + total += users.length; + + if (total >= count) { + break; + } + + offset += limit; + } + } catch (err) { + console.error(`Failed to sync user:`, err); + } +} + +async function syncAllUsers() { + if (tonClient == undefined) { + tonClient = new TonClient({ + endpoint: process.env.RPC_URL, + apiKey: process.env.RPC_API_KEY, + }); + } + + const pools = { + main: MAINNET_POOL_CONFIG, + lp: MAINNET_LP_POOL_CONFIG, + alts: MAINNET_ALTS_POOL_CONFIG, + }; + + let currentPool: EvaaPoolConfig = pools["main"]; + if (process.env.POOL) { + currentPool = pools[process.env.POOL]; + } + + const pgConfig: PoolConfig = { + max: parseInt(process.env.DB_MAX_CONNECTIONS || "5"), + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + host: process.env.DB_HOST, + port: Number(process.env.DB_PORT) || 5432, + }; + const db = new MyDatabase(pgConfig, currentPool); + await db.init(); + + await syncUsers(db); + + console.log("Synchronization of user principals finished"); +} + +syncAllUsers().catch((err) => { + console.error("Fatal error during user synchronization:", err); +});