diff --git a/README.md b/README.md index b8dde33e..48ab5ac6 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,38 @@ Refer to https://github.com/HathorNetwork/rfcs/blob/master/projects/wallet-servi #### System dependencies -You need nodejs installed on your enviroment, we suggest the latest Active LTS version (v18.x.x). +``` +Node: 20x +yarn: v4 (yarn-berry) +``` + +#### Install nix (preferred) + +For a better developer experience we suggest nix usage for mananing the enviroment. Visit this [link](https://nixos.org/download/#download-nix) to download it. + +To enable the commands `nix develop` and `nix build` using flakes, add the following to your `/etc/nix/nix.conf` file: + +``` +experimental-features = nix-command flakes +``` #### Clone the project and install dependencies -`git clone https://github.com/HathorNetwork/hathor-wallet-service-sync_daemon.git` +```sh +$ git clone https://github.com/HathorNetwork/hathor-wallet-service-sync_daemon.git +``` -`npm install` +To initialize nix dev environment: + +```sh +$ nix develop +``` + +then, install the depencies: + +```sh +yarn +``` #### Add env variables or an .env file to the repository: @@ -54,6 +79,16 @@ AWS_SECRET_ACCESS_KEY="..." These are used for communicating with the alert SQS +#### Docker images + +Some packages depends on some docker images. To build them you'll need to have Hathor VPN access configured, check this [link](https://github.com/HathorNetwork/ops-tools/blob/master/terraform/wireguard-vpn/SOP.md#adding-a-new-client-to-the-vpn) for it. + +#### Db initialize + +Before running the tests, make sure your database is already initialize by running the migrations. + +`nix develop . -c yarn sequelize db:migrate` + ## Reseeding the HTR Token After Database Reset If you need to reset the database (for example, to re-sync it from scratch), you must re-insert the HTR token into the `token` table. This is handled by a seed script that will automatically calculate the correct transaction count for HTR based on the current state of the database. diff --git a/db/migrations/20250529233113-add-token-version.js b/db/migrations/20250529233113-add-token-version.js new file mode 100644 index 00000000..07c6af10 --- /dev/null +++ b/db/migrations/20250529233113-add-token-version.js @@ -0,0 +1,15 @@ +"use strict"; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + queryInterface.addColumn("token", "version", { + type: Sequelize.INTEGER, + allowNull: true, + }); + }, + + async down(queryInterface) { + queryInterface.removeColumn("token", "version"); + }, +}; diff --git a/package.json b/package.json index dc312e8d..238e10ad 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "packages/wallet-service" ], "engines": { - "node": ">=18" + "node": ">=20" }, "nohoist": [ "**" diff --git a/packages/common/package.json b/packages/common/package.json index c8f7a8e7..97b9008a 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -5,6 +5,7 @@ "main": "dist/index.js", "scripts": { "build": "tsc --declaration", + "check-types": "tsc --noemit --skipLibCheck", "test": "jest --runInBand --collectCoverage --detectOpenHandles --forceExit" }, "peerDependencies": { diff --git a/packages/daemon/__tests__/db/index.test.ts b/packages/daemon/__tests__/db/index.test.ts index d61fc123..ab2a35fc 100644 --- a/packages/daemon/__tests__/db/index.test.ts +++ b/packages/daemon/__tests__/db/index.test.ts @@ -69,7 +69,7 @@ import { XPUBKEY, } from '../utils'; import { isAuthority } from '@wallet-service/common'; -import { DbTxOutput, StringMap, TokenInfo, WalletStatus } from '../../src/types'; +import { DbTxOutput, StringMap, TokenInfo, TokenInfoVersion, WalletStatus } from '../../src/types'; import { Authorities, TokenBalanceMap } from '@wallet-service/common'; import { constants } from '@hathor/wallet-lib'; import { generateAddresses } from '../../src/utils'; @@ -1061,8 +1061,13 @@ describe('token methods', () => { expect(await getTokenInformation(mysql, 'invalid')).toBeNull(); - const info = new TokenInfo('tokenId', 'tokenName', 'TKNS'); - storeTokenInformation(mysql, info.id, info.name, info.symbol); + const info = new TokenInfo({ + id: 'tokenId', + name: 'tokenName', + symbol: 'TKNS', + version: TokenInfoVersion.DEPOSIT + }); + storeTokenInformation(mysql, info.id, info.name, info.symbol, info.version); expect(info).toStrictEqual(await getTokenInformation(mysql, info.id)); }); @@ -1070,9 +1075,27 @@ describe('token methods', () => { test('incrementTokensTxCount', async () => { expect.hasAssertions(); - const htr = new TokenInfo('00', 'Hathor', 'HTR', 5); - const token1 = new TokenInfo('token1', 'MyToken1', 'MT1', 10); - const token2 = new TokenInfo('token2', 'MyToken2', 'MT2', 15); + const htr = new TokenInfo({ + id: '00', + name: 'Hathor', + symbol: 'HTR', + transactions: 5, + version: TokenInfoVersion.DEPOSIT + }); + const token1 = new TokenInfo({ + id: 'token1', + name: 'MyToken1', + symbol: 'MT1', + transactions: 10, + version: TokenInfoVersion.DEPOSIT + }); + const token2 = new TokenInfo({ + id: 'token2', + name: 'MyToken2', + symbol: 'MT2', + transactions: 15, + version: TokenInfoVersion.DEPOSIT + }); await addToTokenTable(mysql, [ { id: htr.id, name: htr.name, symbol: htr.symbol, transactions: htr.transactions }, @@ -1111,23 +1134,23 @@ describe('sync metadata', () => { }); }); +const generateMockTokens = (qty: number = 5) => new Array(qty).map((_, i) => new TokenInfo({ + id: `token${i + 1}`, + name: `tokenName${i + 1}`, + symbol: `TKN${i + 1}`, +})) + // TODO: This test is duplicated from the wallet-service package, we should // have methods shared between the two projects describe('getTokenSymbols', () => { it('should return a map of token symbol by token id', async () => { expect.hasAssertions(); - const tokensToPersist = [ - new TokenInfo('token1', 'tokenName1', 'TKN1'), - new TokenInfo('token2', 'tokenName2', 'TKN2'), - new TokenInfo('token3', 'tokenName3', 'TKN3'), - new TokenInfo('token4', 'tokenName4', 'TKN4'), - new TokenInfo('token5', 'tokenName5', 'TKN5'), - ]; + const tokensToPersist = generateMockTokens(); // persist tokens for (const eachToken of tokensToPersist) { - await storeTokenInformation(mysql, eachToken.id, eachToken.name, eachToken.symbol); + await storeTokenInformation(mysql, eachToken.id, eachToken.name, eachToken.symbol, eachToken.version); } const tokenIdList = tokensToPersist.map((each: TokenInfo) => each.id); @@ -1145,13 +1168,7 @@ describe('getTokenSymbols', () => { it('should return null when no token is found', async () => { expect.hasAssertions(); - const tokensToPersist = [ - new TokenInfo('token1', 'tokenName1', 'TKN1'), - new TokenInfo('token2', 'tokenName2', 'TKN2'), - new TokenInfo('token3', 'tokenName3', 'TKN3'), - new TokenInfo('token4', 'tokenName4', 'TKN4'), - new TokenInfo('token5', 'tokenName5', 'TKN5'), - ]; + const tokensToPersist = generateMockTokens(); // no token persistence diff --git a/packages/daemon/package.json b/packages/daemon/package.json index 759a4371..9f60ce8b 100644 --- a/packages/daemon/package.json +++ b/packages/daemon/package.json @@ -14,6 +14,7 @@ "build": "tsc -b", "start": "node dist/index.js", "watch": "tsc -w", + "check-types": "tsc --noemit --skipLibCheck", "test_images_up": "docker compose -f ./__tests__/integration/scripts/docker-compose.yml up -d", "test_images_down": "docker compose -f ./__tests__/integration/scripts/docker-compose.yml down", "test_images_integration": "jest --config ./jest_integration.config.js --runInBand --forceExit", diff --git a/packages/daemon/src/db/index.ts b/packages/daemon/src/db/index.ts index 7ea1ab56..6c7f73e6 100644 --- a/packages/daemon/src/db/index.ts +++ b/packages/daemon/src/db/index.ts @@ -20,6 +20,7 @@ import { Miner, TokenSymbolsRow, MaxAddressIndexRow, + TokenInfoVersion, } from '../types'; import { TxInput, @@ -974,14 +975,16 @@ export const mapDbResultToDbTxOutput = (result: TxOutputRow): DbTxOutput => ({ * @param tokenId - The token's id * @param tokenName - The token's name * @param tokenSymbol - The token's symbol + * @param tokenVersion - The token's version */ export const storeTokenInformation = async ( mysql: MysqlConnection, tokenId: string, tokenName: string, tokenSymbol: string, + tokenVersion?: TokenInfoVersion | null ): Promise => { - const entry = { id: tokenId, name: tokenName, symbol: tokenSymbol }; + const entry = { id: tokenId, name: tokenName, symbol: tokenSymbol, version: tokenVersion }; await mysql.query( 'INSERT INTO `token` SET ?', [entry], @@ -1462,7 +1465,12 @@ export const getTokenInformation = async ( if (results.length === 0) return null; - return new TokenInfo(tokenId, results[0].name as string, results[0].symbol as string); + return new TokenInfo({ + id: tokenId, + name: results[0].name as string, + symbol: results[0].symbol as string, + version: results[0].version as (TokenInfoVersion | null), + }); }; /** diff --git a/packages/daemon/src/services/index.ts b/packages/daemon/src/services/index.ts index aa8ff9a7..3b7a8f4e 100644 --- a/packages/daemon/src/services/index.ts +++ b/packages/daemon/src/services/index.ts @@ -196,6 +196,7 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { tokens, token_name, token_symbol, + token_info_version, parents, } = fullNodeData; @@ -258,7 +259,7 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { if (!token_name || !token_symbol) { throw new Error('Processed a token creation event but it did not come with token name and symbol'); } - await storeTokenInformation(mysql, hash, token_name, token_symbol); + await storeTokenInformation(mysql, hash, token_name, token_symbol, token_info_version); } // check if any of the inputs are still marked as locked and update tables accordingly. diff --git a/packages/daemon/src/types/db.ts b/packages/daemon/src/types/db.ts index b9685685..49b18c0d 100644 --- a/packages/daemon/src/types/db.ts +++ b/packages/daemon/src/types/db.ts @@ -119,6 +119,7 @@ export interface TokenInformationRow extends RowDataPacket { name: string; symbol: string; transactions: number; + version?: number | null; created_at: number; updated_at: number; } diff --git a/packages/daemon/src/types/event.ts b/packages/daemon/src/types/event.ts index a4bcb2a8..47d0989f 100644 --- a/packages/daemon/src/types/event.ts +++ b/packages/daemon/src/types/event.ts @@ -85,6 +85,7 @@ export type StandardFullNodeEvent = FullNodeEventBase & { tokens: string[]; token_name: null | string; token_symbol: null | string; + token_info_version: null | number; signal_bits: number; metadata: { hash: string; diff --git a/packages/daemon/src/types/token.ts b/packages/daemon/src/types/token.ts index c53da8a9..5d440960 100644 --- a/packages/daemon/src/types/token.ts +++ b/packages/daemon/src/types/token.ts @@ -5,9 +5,26 @@ * LICENSE file in the root directory of this source tree. */ -import { constants } from '@hathor/wallet-lib'; +import { constants } from "@hathor/wallet-lib"; -export class TokenInfo { +export enum TokenInfoVersion { + DEPOSIT = 1, + + FEE = 2, +} + +export interface ITokenInfo { + id: string; + name: string; + symbol: string; + version?: TokenInfoVersion | null; +} + +export interface ITokenInfoOptions extends ITokenInfo { + transactions?: number; +} + +export class TokenInfo implements ITokenInfo { id: string; name: string; @@ -16,11 +33,14 @@ export class TokenInfo { transactions: number; - constructor(id: string, name: string, symbol: string, transactions?: number) { + version?: TokenInfoVersion | null; + + constructor({ id, name, symbol, version, transactions }: ITokenInfoOptions) { this.id = id; this.name = name; this.symbol = symbol; this.transactions = transactions || 0; + this.version = version || TokenInfoVersion.DEPOSIT; // XXX: get config from settings? const hathorConfig = constants.DEFAULT_NATIVE_TOKEN_CONFIG; @@ -28,14 +48,16 @@ export class TokenInfo { if (this.id === constants.NATIVE_TOKEN_UID) { this.name = hathorConfig.name; this.symbol = hathorConfig.symbol; + this.version = null; } } - toJSON(): Record { + toJSON(): ITokenInfo { return { id: this.id, name: this.name, symbol: this.symbol, + version: this.version, }; } } diff --git a/packages/daemon/tsconfig.json b/packages/daemon/tsconfig.json index d8a17e24..e0187a1b 100644 --- a/packages/daemon/tsconfig.json +++ b/packages/daemon/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "composite": true, "target": "ES2022", "module": "CommonJS", "sourceMap": true, diff --git a/packages/wallet-service/src/db/index.ts b/packages/wallet-service/src/db/index.ts index 2eb29cdd..f5407c9e 100644 --- a/packages/wallet-service/src/db/index.ts +++ b/packages/wallet-service/src/db/index.ts @@ -39,6 +39,7 @@ import { TxByIdToken, PushDeviceSettings, FullNodeApiVersionResponse, + TokenInfoVersion, } from '@src/types'; import { getUnixTimestamp, @@ -1375,7 +1376,8 @@ export const getWalletBalances = async (mysql: ServerlessMysql, walletId: string w.transactions AS transactions, w.token_id AS token_id, token.name AS name, - token.symbol AS symbol + token.symbol AS symbol, + token.version AS version FROM (${subquery}) w INNER JOIN token ON w.token_id = token.id `; @@ -1389,12 +1391,25 @@ INNER JOIN token ON w.token_id = token.id const lockedAuthorities = new Authorities(result.locked_authorities as number); const timelockExpires = result.timelock_expires as number; - const balance = new WalletTokenBalance( - new TokenInfo(result.token_id as string, result.name as string, result.symbol as string), - new Balance(totalAmount, unlockedBalance, lockedBalance, timelockExpires, unlockedAuthorities, lockedAuthorities), + const tokenInfo = new TokenInfo({ + id: result.token_id as string, + name: result.name as string, + symbol: result.symbol as string, + version: result.version as TokenInfoVersion + }); + const balance = new Balance( + totalAmount, + unlockedBalance, + lockedBalance, + timelockExpires, + unlockedAuthorities, + lockedAuthorities); + const walletBalance = new WalletTokenBalance( + tokenInfo, + balance, result.transactions as number, ); - balances.push(balance); + balances.push(walletBalance); } return balances; @@ -1724,8 +1739,9 @@ export const storeTokenInformation = async ( tokenId: string, tokenName: string, tokenSymbol: string, + tokenVersion: number | null ): Promise => { - const entry = { id: tokenId, name: tokenName, symbol: tokenSymbol }; + const entry = { id: tokenId, name: tokenName, symbol: tokenSymbol, version: tokenVersion }; await mysql.query( 'INSERT INTO `token` SET ?', [entry], @@ -1748,7 +1764,12 @@ export const getTokenInformation = async ( [tokenId], ); if (results.length === 0) return null; - return new TokenInfo(tokenId, results[0].name as string, results[0].symbol as string); + return new TokenInfo({ + id: tokenId, + name: results[0].name as string, + symbol: results[0].symbol as string, + version: results[0].version as (TokenInfoVersion | null) + }) }; /** diff --git a/packages/wallet-service/src/types.ts b/packages/wallet-service/src/types.ts index edd97b56..b30c6c9b 100644 --- a/packages/wallet-service/src/types.ts +++ b/packages/wallet-service/src/types.ts @@ -164,7 +164,23 @@ export interface TokenBalance { transactions: number; } -export class TokenInfo { +export enum TokenInfoVersion { + DEPOSIT = 1, + FEE = 2 +} + +export interface ITokenInfo { + id: string; + name: string; + symbol: string; + version?: TokenInfoVersion | null; +} + +export interface ITokenInfoOptions extends ITokenInfo { + transactions?: number +} + +export class TokenInfo implements ITokenInfo { id: string; name: string; @@ -173,25 +189,30 @@ export class TokenInfo { transactions: number; - constructor(id: string, name: string, symbol: string, transactions?: number) { + version: TokenInfoVersion | null; // HTR is undefined + + constructor({ id, name, symbol, transactions, version }: ITokenInfoOptions) { this.id = id; this.name = name; this.symbol = symbol; this.transactions = transactions || 0; + this.version = version || TokenInfoVersion.DEPOSIT; const hathorConfig = hathorLib.constants.DEFAULT_NATIVE_TOKEN_CONFIG; if (this.id === hathorLib.constants.NATIVE_TOKEN_UID) { this.name = hathorConfig.name; this.symbol = hathorConfig.symbol; + this.version = null; } } - toJSON(): Record { + toJSON(): ITokenInfo { return { id: this.id, name: this.name, symbol: this.symbol, + version: this.version }; } } diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index 41098c59..211e4703 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -32,7 +32,7 @@ import * as Db from '@src/db'; import { ApiError } from '@src/api/errors'; import { closeDbConnection, getDbConnection, getUnixTimestamp, getWalletId } from '@src/utils'; import { STATUS_CODE_TABLE } from '@src/api/utils'; -import { WalletStatus, FullNodeApiVersionResponse } from '@src/types'; +import { WalletStatus, FullNodeApiVersionResponse, TokenInfoVersion } from '@src/types'; import { walletUtils, addressUtils, constants, network, HathorWalletServiceWallet } from '@hathor/wallet-lib'; import bitcore from 'bitcore-lib'; import { @@ -1464,12 +1464,12 @@ test('GET /wallet/tokens/token_id/details', async () => { expect(returnBody.details[0]).toStrictEqual({ message: '"token_id" is required', path: ['token_id'] }); // add tokens - const token1 = { id: TX_IDS[1], name: 'MyToken1', symbol: 'MT1' }; - const token2 = { id: TX_IDS[2], name: 'MyToken2', symbol: 'MT2' }; + const token1 = { id: TX_IDS[1], name: 'MyToken1', symbol: 'MT1', version: TokenInfoVersion.DEPOSIT }; + const token2 = { id: TX_IDS[2], name: 'MyToken2', symbol: 'MT2', version: TokenInfoVersion.DEPOSIT }; await addToTokenTable(mysql, [ - { id: token1.id, name: token1.name, symbol: token1.symbol, transactions: 0 }, - { id: token2.id, name: token2.name, symbol: token2.symbol, transactions: 0 }, + { id: token1.id, name: token1.name, symbol: token1.symbol, transactions: 0, version: token1.version }, + { id: token2.id, name: token2.name, symbol: token2.symbol, transactions: 0, version: token2.version }, ]); await addToUtxoTable(mysql, [{ diff --git a/packages/wallet-service/tests/commons.test.ts b/packages/wallet-service/tests/commons.test.ts index 5ecb8e8a..9ae70fdd 100644 --- a/packages/wallet-service/tests/commons.test.ts +++ b/packages/wallet-service/tests/commons.test.ts @@ -17,6 +17,7 @@ import { Block, FullNodeApiVersionResponse, TxOutputWithIndex, + TokenInfoVersion, } from '@src/types'; import fullnode from '@src/fullnode'; import { @@ -649,8 +650,9 @@ describe('getWalletBalancesForTx', () => { id: 'token1', name: 'Token 1', symbol: 'T1', + version: TokenInfoVersion.DEPOSIT }; - await storeTokenInformation(mysql, token1.id, token1.name, token1.symbol); + await storeTokenInformation(mysql, token1.id, token1.name, token1.symbol, token1.version); // transaction base const utxos = [ @@ -733,8 +735,9 @@ describe('getWalletBalancesForTx', () => { id: 'token1', name: 'Token 1', symbol: 'T1', + version: TokenInfoVersion.DEPOSIT }; - await storeTokenInformation(mysql, token1.id, token1.name, token1.symbol); + await storeTokenInformation(mysql, token1.id, token1.name, token1.symbol, token1.version); // instantiate token balance const balanceToken1 = { @@ -862,14 +865,16 @@ describe('getWalletBalancesForTx', () => { id: 'token1', name: 'Token 1', symbol: 'T1', + version: TokenInfoVersion.DEPOSIT }; - await storeTokenInformation(mysql, token1.id, token1.name, token1.symbol); + await storeTokenInformation(mysql, token1.id, token1.name, token1.symbol, token1.version); const token2 = { id: 'token2', name: 'Token 2', symbol: 'T2', + version: TokenInfoVersion.DEPOSIT }; - await storeTokenInformation(mysql, token2.id, token2.name, token2.symbol); + await storeTokenInformation(mysql, token2.id, token2.name, token2.symbol, token2.version); // instantiate token balance const balanceToken1 = { @@ -1032,14 +1037,16 @@ describe('getWalletBalancesForTx', () => { id: 'token1', name: 'Token 1', symbol: 'T1', + version: TokenInfoVersion.DEPOSIT }; - await storeTokenInformation(mysql, token1.id, token1.name, token1.symbol); + await storeTokenInformation(mysql, token1.id, token1.name, token1.symbol, token1.version); const token2 = { id: 'token2', name: 'Token 2', symbol: 'T2', + version: TokenInfoVersion.DEPOSIT }; - await storeTokenInformation(mysql, token2.id, token2.name, token2.symbol); + await storeTokenInformation(mysql, token2.id, token2.name, token2.symbol, token2.version); // instantiate token balance const balanceToken1 = { diff --git a/packages/wallet-service/tests/db.test.ts b/packages/wallet-service/tests/db.test.ts index 33759da5..b745d069 100644 --- a/packages/wallet-service/tests/db.test.ts +++ b/packages/wallet-service/tests/db.test.ts @@ -105,6 +105,7 @@ import { PushProvider, Block, FullNodeApiVersionResponse, + TokenInfoVersion, } from '@src/types'; import { Severity } from '@wallet-service/common/src/types'; import { isAuthority } from '@wallet-service/common/src/utils/wallet.utils'; @@ -929,8 +930,16 @@ test('getWalletAddressDetail', async () => { test('getWalletBalances', async () => { expect.hasAssertions(); const walletId = 'walletId'; - const token1 = new TokenInfo('token1', 'MyToken1', 'MT1'); - const token2 = new TokenInfo('token2', 'MyToken2', 'MT2'); + const token1 = new TokenInfo({ + id: 'token1', + name: 'MyToken1', + symbol: 'MT1' + }); + const token2 = new TokenInfo({ + id: 'token2', + name: 'MyToken2', + symbol: 'MT2' + }); const now = 1000; // add some balances into db @@ -1254,8 +1263,10 @@ test('storeTokenInformation and getTokenInformation', async () => { expect(await getTokenInformation(mysql, 'invalid')).toBeNull(); - const info = new TokenInfo('tokenId', 'tokenName', 'TKNS'); - storeTokenInformation(mysql, info.id, info.name, info.symbol); + const info = new TokenInfo({ + id: 'tokenId', name: 'tokenName', symbol: 'TKNS' + }); + storeTokenInformation(mysql, info.id, info.name, info.symbol, info.version); expect(info).toStrictEqual(await getTokenInformation(mysql, info.id)); }); @@ -1263,8 +1274,10 @@ test('storeTokenInformation and getTokenInformation', async () => { test('validateTokenTimestamps', async () => { expect.hasAssertions(); - const info = new TokenInfo('tokenId', 'tokenName', 'TKNS'); - storeTokenInformation(mysql, info.id, info.name, info.symbol); + const info = new TokenInfo({ + id: 'tokenId', name: 'tokenName', symbol: 'TKNS' + }); + storeTokenInformation(mysql, info.id, info.name, info.symbol, info.version); let result = await mysql.query('SELECT * FROM `token` WHERE `id` = ?', [info.id]); expect(result[0].created_at).toStrictEqual(result[0].updated_at); @@ -2025,7 +2038,7 @@ test('rebuildAddressBalancesFromUtxos', async () => { // add to the token table await addToTokenTable(mysql, [ - { id: token1, name: 'token1', symbol: 'TKN1', transactions: 2 }, + { id: token1, name: 'token1', symbol: 'TKN1', version: TokenInfoVersion.DEPOSIT, transactions: 2 }, ]); await expect(checkTokenTable(mysql, 1, [{ @@ -2033,6 +2046,7 @@ test('rebuildAddressBalancesFromUtxos', async () => { tokenSymbol: 'TKN1', tokenName: 'token1', transactions: 2, + tokenVersion: TokenInfoVersion.DEPOSIT, }])).resolves.toBe(true); // We are only using the txList parameter on `transactions` recalculation, so our balance @@ -2063,6 +2077,7 @@ test('rebuildAddressBalancesFromUtxos', async () => { tokenSymbol: 'TKN1', tokenName: 'token1', transactions: 0, + tokenVersion: TokenInfoVersion.DEPOSIT }])).resolves.toBe(true); }); @@ -2756,14 +2771,26 @@ test('getAffectedAddressTxCountFromTxList', async () => { test('incrementTokensTxCount', async () => { expect.hasAssertions(); - const htr = new TokenInfo('00', 'Hathor', 'HTR', 5); - const token1 = new TokenInfo('token1', 'MyToken1', 'MT1', 10); - const token2 = new TokenInfo('token2', 'MyToken2', 'MT2', 15); + const htr = new TokenInfo({ + id: '00', name: 'Hathor', symbol: 'HTR', transactions: 5 + }) + const token1 = new TokenInfo({ + id: 'token1', name: 'MyToken1', symbol: 'MT1', transactions: 10 + }); + const token2 = new TokenInfo({ + id: 'token2', name: 'MyToken2', symbol: 'MT2', transactions: 15, version: TokenInfoVersion.FEE + }); await addToTokenTable(mysql, [ - { id: htr.id, name: htr.name, symbol: htr.symbol, transactions: htr.transactions }, - { id: token1.id, name: token1.name, symbol: token1.symbol, transactions: token1.transactions }, - { id: token2.id, name: token2.name, symbol: token2.symbol, transactions: token2.transactions }, + { id: htr.id, name: htr.name, symbol: htr.symbol, transactions: htr.transactions, version: htr.version }, + { id: token1.id, name: token1.name, symbol: token1.symbol, transactions: token1.transactions, version: token1.version }, + { + id: token2.id, + name: token2.name, + symbol: token2.symbol, + transactions: token2.transactions, + version: token2.version, + }, ]); await incrementTokensTxCount(mysql, ['token1', '00', 'token2']); @@ -2773,16 +2800,19 @@ test('incrementTokensTxCount', async () => { tokenSymbol: token1.symbol, tokenName: token1.name, transactions: token1.transactions + 1, + tokenVersion: token1.version, }, { tokenId: token2.id, tokenSymbol: token2.symbol, tokenName: token2.name, transactions: token2.transactions + 1, + tokenVersion: token2.version }, { tokenId: htr.id, tokenSymbol: htr.symbol, tokenName: htr.name, transactions: htr.transactions + 1, + tokenVersion: htr.version }])).resolves.toBe(true); }); @@ -3445,21 +3475,20 @@ describe('getPushDeviceSettingsList', () => { }); }); +const generateMockTokens = (qty: number = 5) => new Array(qty).fill(0).map((_, i) => new TokenInfo({ + id: `token${i + 1}`, + name: `tokenName${i + 1}`, + symbol: `TKN${i + 1}`, +})) describe('getTokenSymbols', () => { it('should return a map of token symbol by token id', async () => { expect.hasAssertions(); - const tokensToPersist = [ - new TokenInfo('token1', 'tokenName1', 'TKN1'), - new TokenInfo('token2', 'tokenName2', 'TKN2'), - new TokenInfo('token3', 'tokenName3', 'TKN3'), - new TokenInfo('token4', 'tokenName4', 'TKN4'), - new TokenInfo('token5', 'tokenName5', 'TKN5'), - ]; + const tokensToPersist = generateMockTokens(); // persist tokens for (const eachToken of tokensToPersist) { - await storeTokenInformation(mysql, eachToken.id, eachToken.name, eachToken.symbol); + await storeTokenInformation(mysql, eachToken.id, eachToken.name, eachToken.symbol, eachToken.version); } const tokenIdList = tokensToPersist.map((each: TokenInfo) => each.id); @@ -3477,13 +3506,7 @@ describe('getTokenSymbols', () => { it('should return null when no token is found', async () => { expect.hasAssertions(); - const tokensToPersist = [ - new TokenInfo('token1', 'tokenName1', 'TKN1'), - new TokenInfo('token2', 'tokenName2', 'TKN2'), - new TokenInfo('token3', 'tokenName3', 'TKN3'), - new TokenInfo('token4', 'tokenName4', 'TKN4'), - new TokenInfo('token5', 'tokenName5', 'TKN5'), - ]; + const tokensToPersist = generateMockTokens(); // no token persistence diff --git a/packages/wallet-service/tests/txPushNotificationRequested.test.ts b/packages/wallet-service/tests/txPushNotificationRequested.test.ts index f4fdd2e1..c86dd01a 100644 --- a/packages/wallet-service/tests/txPushNotificationRequested.test.ts +++ b/packages/wallet-service/tests/txPushNotificationRequested.test.ts @@ -7,7 +7,7 @@ import { buildWallet, } from '@tests/utils'; import { handleRequest, pushNotificationMessage } from '@src/api/txPushNotificationRequested'; -import { StringMap, WalletBalanceValue, PushProvider, SendNotificationToDevice } from '@src/types'; +import { StringMap, WalletBalanceValue, PushProvider, SendNotificationToDevice, TokenInfoVersion } from '@src/types'; import { PushNotificationUtils } from '@src/utils/pushnotification.utils'; import { registerPushDevice, storeTokenInformation } from '@src/db'; import { Context } from 'aws-lambda'; @@ -28,6 +28,7 @@ const buildEvent = (walletId, txId, walletBalanceForTx?): StringMap { enableShowAmounts: false, }; - await storeTokenInformation(mysql, 'token1', 'token1', 'T1'); + await storeTokenInformation(mysql, 'token1', 'token1', 'T1', TokenInfoVersion.DEPOSIT); await registerPushDevice(mysql, pushDevice); @@ -141,8 +143,8 @@ describe('success', () => { enableShowAmounts: false, }; - await storeTokenInformation(mysql, 'token1', 'token1', 'T1'); - await storeTokenInformation(mysql, 'token2', 'token2', 'T2'); + await storeTokenInformation(mysql, 'token1', 'token1', 'T1', TokenInfoVersion.DEPOSIT); + await storeTokenInformation(mysql, 'token2', 'token2', 'T2', TokenInfoVersion.DEPOSIT); await registerPushDevice(mysql, pushDevice); @@ -228,7 +230,7 @@ describe('success', () => { }; await registerPushDevice(mysql, pushDevice); - await storeTokenInformation(mysql, 'token2', 'token2', 'T2'); + await storeTokenInformation(mysql, 'token2', 'token2', 'T2', TokenInfoVersion.DEPOSIT); const sendEvent = buildEvent(walletId, txId, [ { @@ -274,10 +276,10 @@ describe('success', () => { enableShowAmounts: true, }; await registerPushDevice(mysql, pushDevice); - await storeTokenInformation(mysql, 'token1', 'token1', 'T1'); - await storeTokenInformation(mysql, 'token2', 'token2', 'T2'); - await storeTokenInformation(mysql, 'token3', 'token3', 'T3'); - await storeTokenInformation(mysql, 'token4', 'token4', 'T4'); + await storeTokenInformation(mysql, 'token1', 'token1', 'T1', TokenInfoVersion.DEPOSIT); + await storeTokenInformation(mysql, 'token2', 'token2', 'T2', TokenInfoVersion.DEPOSIT); + await storeTokenInformation(mysql, 'token3', 'token3', 'T3', TokenInfoVersion.DEPOSIT); + await storeTokenInformation(mysql, 'token4', 'token4', 'T4', TokenInfoVersion.DEPOSIT); }); it('token balance with 1 token', async () => { diff --git a/packages/wallet-service/tests/types.test.ts b/packages/wallet-service/tests/types.test.ts index 94d4d195..3137892d 100644 --- a/packages/wallet-service/tests/types.test.ts +++ b/packages/wallet-service/tests/types.test.ts @@ -1,4 +1,5 @@ -import { Authorities, Balance, TokenBalanceMap } from '@src/types'; +import { NATIVE_TOKEN_UID } from '@hathor/wallet-lib/lib/constants'; +import { Authorities, Balance, TokenBalanceMap, TokenInfo, TokenInfoVersion } from '@src/types'; import { DecodedOutput, TxInput, TxOutput } from '@wallet-service/common/src/types'; test('Authorities', () => { @@ -153,3 +154,16 @@ test('TokenBalanceMap fromTxOutput fromTxInput', () => { txOutput.locked = true; expect(TokenBalanceMap.fromTxOutput(txOutput)).toStrictEqual(TokenBalanceMap.fromStringMap({ '00': { totalSent: 200, locked: txOutput.value, unlocked: 0, lockExpires: timelock } })); }); + +test('TokenInfo', () => { + expect.hasAssertions(); + + const htr = new TokenInfo({ id: NATIVE_TOKEN_UID, name: "Hathor" ,symbol: 'HTR' }) + expect(htr.version).toBeNull(); + + const token1 = new TokenInfo({ id: 'Token1', name: "MyToken1" ,symbol: 'TK1' }) + expect(token1.version).toBe(TokenInfoVersion.DEPOSIT); + + const token2 = new TokenInfo({ id: 'Token2', name: "MyToken2" ,symbol: 'TK2', version: TokenInfoVersion.FEE }) + expect(token2.version).toBe(TokenInfoVersion.FEE); +}) \ No newline at end of file diff --git a/packages/wallet-service/tests/types.ts b/packages/wallet-service/tests/types.ts index 2bf07dbe..abbcf1fb 100644 --- a/packages/wallet-service/tests/types.ts +++ b/packages/wallet-service/tests/types.ts @@ -39,6 +39,7 @@ export interface TokenTableEntry { name: string; symbol: string; transactions: number; + version?: number | null; } export interface WalletTableEntry { diff --git a/packages/wallet-service/tests/utils.ts b/packages/wallet-service/tests/utils.ts index 8342ef85..92e45445 100644 --- a/packages/wallet-service/tests/utils.ts +++ b/packages/wallet-service/tests/utils.ts @@ -493,6 +493,7 @@ type Token = { tokenSymbol: string; tokenName: string; transactions: number; + tokenVersion: number | null; } export const checkTokenTable = async ( @@ -518,7 +519,8 @@ export const checkTokenTable = async ( SELECT id AS tokenId, symbol AS tokenSymbol, name AS tokenName, - transactions + transactions, + version as tokenVersion FROM \`token\` WHERE \`id\` IN (?) `; @@ -743,10 +745,11 @@ export const addToTokenTable = async ( entry.name, entry.symbol, entry.transactions, + entry.version ])); await mysql.query( - 'INSERT INTO `token`(`id`, `name`, `symbol`, `transactions`) VALUES ?', + 'INSERT INTO `token`(`id`, `name`, `symbol`, `transactions`, `version`) VALUES ?', [payload], ); }; diff --git a/packages/wallet-service/tsconfig.json b/packages/wallet-service/tsconfig.json index f5098fa9..13ab2c58 100644 --- a/packages/wallet-service/tsconfig.json +++ b/packages/wallet-service/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "composite": true, "lib": ["es2017"], "removeComments": true, "moduleResolution": "node", @@ -24,7 +25,9 @@ "include": [ "src/*.ts", "src/**/*.ts", - "tests" + "tests", + "events/*.json", + "events/**/*.json" ], "exclude": [ "node_modules/**/*",