From 6e3feb96edbea16357256a961a71061a5a404be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Fri, 4 Apr 2025 13:33:24 -0300 Subject: [PATCH 1/8] fix: address index was being fetched from pathParameters --- packages/wallet-service/serverless.yml | 6 ++++-- packages/wallet-service/src/api/addresses.ts | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/wallet-service/serverless.yml b/packages/wallet-service/serverless.yml index d0ab53b9..0a43ce9d 100644 --- a/packages/wallet-service/serverless.yml +++ b/packages/wallet-service/serverless.yml @@ -326,8 +326,10 @@ functions: authorizer: ${self:custom.authorizer.walletBearer} request: parameters: - paths: - index: false + querystrings: + index: + required: false + default: "0" warmup: walletWarmer: enabled: true diff --git a/packages/wallet-service/src/api/addresses.ts b/packages/wallet-service/src/api/addresses.ts index a9e31839..8737d869 100644 --- a/packages/wallet-service/src/api/addresses.ts +++ b/packages/wallet-service/src/api/addresses.ts @@ -108,7 +108,7 @@ export const checkMine: APIGatewayProxyHandler = middy(walletIdProxyHandler(asyn })).use(cors()) .use(errorHandler()); -/* +/** * Get the addresses of a wallet, allowing an index filter * Notice: If the index filter is passed, it will only find addresses * that are already in our database, this will not derive new addresses @@ -127,7 +127,7 @@ export const get: APIGatewayProxyHandler = middy( return closeDbAndGetError(mysql, ApiError.WALLET_NOT_READY); } - const { value: body, error } = AddressAtIndexValidator.validate(event.pathParameters || {}); + const { value: body, error } = AddressAtIndexValidator.validate(event.queryStringParameters || {}); if (error) { const details = error.details.map((err) => ({ From 79094782cbc2ca0a428be7b994ec88f6a8aa2886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 16 Apr 2025 13:09:23 -0300 Subject: [PATCH 2/8] tests: added more tests to get addresses with index --- packages/wallet-service/tests/api.test.ts | 75 ++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index 260457f7..742a8910 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -188,7 +188,7 @@ test('GET /addresses', async () => { expect(result.statusCode).toBe(STATUS_CODE_TABLE[ApiError.INVALID_PAYLOAD]); expect(returnBody.details).toHaveLength(1); expect(returnBody.details[0].message) - .toMatchInlineSnapshot('"\"index\" must be greater than or equal to 0"'); + .toMatchInlineSnapshot('"index" must be greater than or equal to 0'); // we should be able to filter for a specific index event = makeGatewayEventWithAuthorizer('my-wallet', { @@ -2258,3 +2258,76 @@ describe('GET /health', () => { }); }); }); + +test('GET /addresses (query string index parameter)', async () => { + expect.hasAssertions(); + + await addToWalletTable(mysql, [{ + id: 'my-wallet', + xpubkey: 'xpubkey', + authXpubkey: 'auth_xpubkey', + status: 'ready', + maxGap: 5, + createdAt: 10000, + readyAt: 10001, + }]); + + const addresses = [ + { address: ADDRESSES[0], index: 0, walletId: 'my-wallet', transactions: 0 }, + { address: ADDRESSES[1], index: 1, walletId: 'my-wallet', transactions: 0 }, + ]; + + await addToAddressTable(mysql, addresses); + + // 1. No index parameter (should return all addresses) + let event = makeGatewayEventWithAuthorizer('my-wallet', null); + let result = await addressesGet(event, null, null) as APIGatewayProxyResult; + let returnBody = JSON.parse(result.body as string); + expect(result.statusCode).toBe(200); + expect(returnBody.success).toBe(true); + expect(returnBody.addresses).toHaveLength(2); + + // 2. index as a string (should return the address at that index) + event = makeGatewayEventWithAuthorizer('my-wallet', { index: '1' }); + result = await addressesGet(event, null, null) as APIGatewayProxyResult; + returnBody = JSON.parse(result.body as string); + expect(result.statusCode).toBe(200); + expect(returnBody.success).toBe(true); + expect(returnBody.addresses).toHaveLength(1); + expect(returnBody.addresses[0].index).toBe(1); + + // 3. index as a number (should return the address at that index) + event = makeGatewayEventWithAuthorizer('my-wallet', { index: '0' }); + result = await addressesGet(event, null, null) as APIGatewayProxyResult; + returnBody = JSON.parse(result.body as string); + expect(result.statusCode).toBe(200); + expect(returnBody.success).toBe(true); + expect(returnBody.addresses).toHaveLength(1); + expect(returnBody.addresses[0].index).toBe(0); + + // 4. index is invalid (non-numeric) + event = makeGatewayEventWithAuthorizer('my-wallet', { index: 'not-a-number' }); + result = await addressesGet(event, null, null) as APIGatewayProxyResult; + returnBody = JSON.parse(result.body as string); + expect(result.statusCode).toBe(400); + expect(returnBody.success).toBe(false); + expect(returnBody.error).toBe(ApiError.INVALID_PAYLOAD); + expect(returnBody.details[0].message).toMatch(/must be a number/); + + // 5. index is negative (should error) + event = makeGatewayEventWithAuthorizer('my-wallet', { index: '-1' }); + result = await addressesGet(event, null, null) as APIGatewayProxyResult; + returnBody = JSON.parse(result.body as string); + expect(result.statusCode).toBe(400); + expect(returnBody.success).toBe(false); + expect(returnBody.error).toBe(ApiError.INVALID_PAYLOAD); + expect(returnBody.details[0].message).toMatch(/greater than or equal to 0/); + + // 6. index not found (should return ADDRESS_NOT_FOUND) + event = makeGatewayEventWithAuthorizer('my-wallet', { index: '999' }); + result = await addressesGet(event, null, null) as APIGatewayProxyResult; + returnBody = JSON.parse(result.body as string); + expect(result.statusCode).toBe(404); + expect(returnBody.success).toBe(false); + expect(returnBody.error).toBe(ApiError.ADDRESS_NOT_FOUND); +}); From 32c72f8f36b6d644e3cc59b2710a1d6ddbbead39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 16 Apr 2025 13:17:58 -0300 Subject: [PATCH 3/8] refactor: we shouldn't have a default for index --- packages/wallet-service/serverless.yml | 6 ------ packages/wallet-service/tests/api.test.ts | 10 ++++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/wallet-service/serverless.yml b/packages/wallet-service/serverless.yml index 0a43ce9d..4daa1e60 100644 --- a/packages/wallet-service/serverless.yml +++ b/packages/wallet-service/serverless.yml @@ -324,12 +324,6 @@ functions: method: get cors: true authorizer: ${self:custom.authorizer.walletBearer} - request: - parameters: - querystrings: - index: - required: false - default: "0" warmup: walletWarmer: enabled: true diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index d151faa4..82a406f0 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -2292,6 +2292,16 @@ test('GET /addresses (query string index parameter)', async () => { expect(result.statusCode).toBe(200); expect(returnBody.success).toBe(true); expect(returnBody.addresses).toHaveLength(2); + expect(returnBody.addresses).toContainEqual({ + address: addresses[0].address, + index: addresses[0].index, + transactions: addresses[0].transactions, + }); + expect(returnBody.addresses).toContainEqual({ + address: addresses[1].address, + index: addresses[1].index, + transactions: addresses[1].transactions, + }); // 2. index as a string (should return the address at that index) event = makeGatewayEventWithAuthorizer('my-wallet', { index: '1' }); From 6989637ab5fc16a667e00ec2a0c5e4558b822542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Fri, 25 Apr 2025 12:29:50 -0300 Subject: [PATCH 4/8] chore: invalid test snapshot --- packages/wallet-service/package.json | 2 +- packages/wallet-service/src/api/healthcheck.ts | 2 +- packages/wallet-service/tests/api.test.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/wallet-service/package.json b/packages/wallet-service/package.json index cae43da2..e0e5eeaf 100644 --- a/packages/wallet-service/package.json +++ b/packages/wallet-service/package.json @@ -6,7 +6,7 @@ "lint": "eslint src/ tests/ --ext .js,.jsx,.ts,.tsx src tests", "lint-fix": "eslint src/ tests/ --fix --ext .js,.jsx,.ts,.tsx src tests", "check-types": "tsc --noemit --skipLibCheck", - "test": "jest" + "test": "jest -u" }, "author": "Hathor Labs", "license": "MIT", diff --git a/packages/wallet-service/src/api/healthcheck.ts b/packages/wallet-service/src/api/healthcheck.ts index 30f9b4ec..379e4436 100644 --- a/packages/wallet-service/src/api/healthcheck.ts +++ b/packages/wallet-service/src/api/healthcheck.ts @@ -140,6 +140,6 @@ export const getHealthcheck: APIGatewayProxyHandler = middy(async (event) => { return { statusCode: response.getHttpStatusCode(), - body: response.toJson(), + body: JSON.stringify(JSON.parse(response.toJson())), }; }).use(errorHandler()); diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index 82a406f0..be610ac3 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -188,8 +188,8 @@ test('GET /addresses', async () => { expect(result.statusCode).toBe(STATUS_CODE_TABLE[ApiError.INVALID_PAYLOAD]); expect(returnBody.details).toHaveLength(1); - expect(returnBody.details[0].message) - .toMatchInlineSnapshot('"index" must be greater than or equal to 0'); + expect(returnBody.details[0].message.trim()) + .toMatchInlineSnapshot(`"index" must be greater than or equal to 0`); // we should be able to filter for a specific index event = makeGatewayEventWithAuthorizer('my-wallet', { @@ -1681,7 +1681,7 @@ test('GET /version', async () => { genesis_block_hash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', genesis_tx1_hash: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', genesis_tx2_hash: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', - native_token: { name: 'Hathor', symbol: 'HTR'}, + native_token: { name: 'Hathor', symbol: 'HTR' }, }; const returnData = convertApiVersionData(mockData); From c31a924afd6a87e0174846032e78c057d1b3a724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 28 Apr 2025 14:24:20 -0300 Subject: [PATCH 5/8] tests: updated get addresses snapshot --- packages/wallet-service/tests/api.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index be610ac3..41098c59 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -189,7 +189,7 @@ test('GET /addresses', async () => { expect(result.statusCode).toBe(STATUS_CODE_TABLE[ApiError.INVALID_PAYLOAD]); expect(returnBody.details).toHaveLength(1); expect(returnBody.details[0].message.trim()) - .toMatchInlineSnapshot(`"index" must be greater than or equal to 0`); + .toMatchInlineSnapshot(`""index" must be greater than or equal to 0"`); // we should be able to filter for a specific index event = makeGatewayEventWithAuthorizer('my-wallet', { From 37f3d12e9b1e0fb382da57cf339ea73cc015a73d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Tue, 29 Apr 2025 12:05:24 -0300 Subject: [PATCH 6/8] fix: invalid argument order in dbGetAddressAtIndex --- packages/wallet-service/src/api/addresses.ts | 4 +- packages/wallet-service/src/db/index.ts | 56 ++++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/wallet-service/src/api/addresses.ts b/packages/wallet-service/src/api/addresses.ts index 8737d869..52c394bc 100644 --- a/packages/wallet-service/src/api/addresses.ts +++ b/packages/wallet-service/src/api/addresses.ts @@ -39,7 +39,7 @@ class AddressAtIndexValidator { index: Joi.number().min(0).optional(), }); - static validate(payload: unknown): { value: AddressAtIndexRequest, error: ValidationError} { + static validate(payload: unknown): { value: AddressAtIndexRequest, error: ValidationError } { return AddressAtIndexValidator.bodySchema.validate(payload, { abortEarly: false, // We want it to return all the errors not only the first convert: true, // We need to convert as parameters are sent on the QueryString @@ -92,7 +92,7 @@ export const checkMine: APIGatewayProxyHandler = middy(walletIdProxyHandler(asyn await closeDbConnection(mysql); - const addressBelongMap = sentAddresses.reduce((acc: {string: boolean}, address: string) => { + const addressBelongMap = sentAddresses.reduce((acc: { string: boolean }, address: string) => { acc[address] = walletAddresses.has(address); return acc; diff --git a/packages/wallet-service/src/db/index.ts b/packages/wallet-service/src/db/index.ts index 796d4b49..2eb29cdd 100644 --- a/packages/wallet-service/src/db/index.ts +++ b/packages/wallet-service/src/db/index.ts @@ -426,7 +426,7 @@ export const getWalletAddressDetail = async (mysql: ServerlessMysql, walletId: s FROM \`address\` WHERE \`wallet_id\` = ? AND \`address\` = ?`, - [walletId, address]); + [walletId, address]); if (results.length > 0) { const data = results[0]; @@ -1160,12 +1160,12 @@ export const updateAddressLockedBalance = async ( \`unlocked_authorities\` = (unlocked_authorities | ?) WHERE \`address\` = ? AND \`token_id\` = ?`, [ - tokenBalance.unlockedAmount, - tokenBalance.unlockedAmount, - tokenBalance.unlockedAuthorities.toInteger(), - address, - token, - ], + tokenBalance.unlockedAmount, + tokenBalance.unlockedAmount, + tokenBalance.unlockedAuthorities.toInteger(), + address, + token, + ], ); // if any authority has been unlocked, we have to refresh the locked authorities @@ -1201,7 +1201,7 @@ export const updateAddressLockedBalance = async ( ) WHERE \`address\` = ? AND \`token_id\` = ?`, - [address, token, address, token]); + [address, token, address, token]); } } } @@ -1233,7 +1233,7 @@ export const updateWalletLockedBalance = async ( WHERE \`wallet_id\` = ? AND \`token_id\` = ?`, [tokenBalance.unlockedAmount, tokenBalance.unlockedAmount, - tokenBalance.unlockedAuthorities.toInteger(), walletId, token], + tokenBalance.unlockedAuthorities.toInteger(), walletId, token], ); // if any authority has been unlocked, we have to refresh the locked authorities @@ -1418,7 +1418,7 @@ export const getWalletTokens = async ( ); for (const result of results) { - tokenList.push( result.token_id); + tokenList.push(result.token_id); } return tokenList; @@ -1464,7 +1464,7 @@ LEFT OUTER JOIN transaction ON transaction.tx_id = wallet_tx_history.tx_id ORDER BY wallet_tx_history.timestamp DESC LIMIT ?, ?`, - [walletId, tokenId, skip, count]); + [walletId, tokenId, skip, count]); for (const result of results) { const tx: TxTokenBalance = { @@ -2074,7 +2074,7 @@ export const markUtxosAsVoided = async ( UPDATE \`tx_output\` SET \`voided\` = TRUE WHERE \`tx_id\` IN (?)`, - [txIds]); + [txIds]); }; /** @@ -2812,7 +2812,7 @@ export const existsPushDevice = async ( mysql: ServerlessMysql, deviceId: string, walletId: string, -) : Promise => { +): Promise => { const [{ count }] = await mysql.query( ` SELECT COUNT(1) as \`count\` @@ -2820,7 +2820,7 @@ export const existsPushDevice = async ( WHERE device_id = ? AND wallet_id = ?`, [deviceId, walletId], - ) as unknown as Array<{count}>; + ) as unknown as Array<{ count }>; return count > 0; }; @@ -2840,7 +2840,7 @@ export const registerPushDevice = async ( enablePush: boolean, enableShowAmounts: boolean, }, -) : Promise => { +): Promise => { await mysql.query( ` INSERT @@ -2889,7 +2889,7 @@ export const updatePushDevice = async ( enablePush: boolean, enableShowAmounts: boolean, }, -) : Promise => { +): Promise => { await mysql.query( ` UPDATE \`push_devices\` @@ -2912,7 +2912,7 @@ export const unregisterPushDevice = async ( mysql: ServerlessMysql, deviceId: string, walletId?: string, -) : Promise => { +): Promise => { if (walletId) { await mysql.query( ` @@ -2964,8 +2964,8 @@ export const getTransactionById = async ( WHERE transaction.tx_id = ? AND transaction.voided = FALSE AND wallet_tx_history.wallet_id = ?`, - // eslint-disable-next-line camelcase - [txId, walletId]) as Array<{tx_id, timestamp, version, voided, weight, balance, token_id, name, symbol }>; + // eslint-disable-next-line camelcase + [txId, walletId]) as Array<{ tx_id, timestamp, version, voided, weight, balance, token_id, name, symbol }>; const txTokens = []; result.forEach((eachTxToken) => { @@ -2995,7 +2995,7 @@ export const getTransactionById = async ( export const existsWallet = async ( mysql: ServerlessMysql, walletId: string, -) : Promise => { +): Promise => { const [{ count }] = (await mysql.query( ` SELECT COUNT(1) as \`count\` @@ -3016,15 +3016,15 @@ export const existsWallet = async ( export const getPushDevice = async ( mysql: ServerlessMysql, deviceId: string, -) : Promise => { +): Promise => { const [pushDevice] = await mysql.query( ` SELECT * FROM \`push_devices\` WHERE device_id = ?`, [deviceId], - // eslint-disable-next-line camelcase - ) as Array<{wallet_id, device_id, push_provider, enable_push, enable_show_amounts}>; + // eslint-disable-next-line camelcase + ) as Array<{ wallet_id, device_id, push_provider, enable_push, enable_show_amounts }>; if (!pushDevice) { return null; @@ -3049,7 +3049,7 @@ export const getPushDevice = async ( export const getPushDeviceSettingsList = async ( mysql: ServerlessMysql, walletIdList: string[], -) : Promise => { +): Promise => { const pushDeviceSettingsResult = await mysql.query( ` SELECT wallet_id @@ -3059,8 +3059,8 @@ export const getPushDeviceSettingsList = async ( FROM \`push_devices\` WHERE wallet_id in (?)`, [walletIdList], - // eslint-disable-next-line camelcase - ) as Array<{wallet_id, device_id, enable_push, enable_show_amounts}>; + // eslint-disable-next-line camelcase + ) as Array<{ wallet_id, device_id, enable_push, enable_show_amounts }>; const pushDeviceSettignsList = pushDeviceSettingsResult.map((each) => ({ walletId: each.wallet_id, @@ -3121,7 +3121,7 @@ export const getTokenSymbols = async ( ); if (results.length === 0) return null; - return results.reduce((prev: Record, token: { id: string, symbol: string}) => { + return results.reduce((prev: Record, token: { id: string, symbol: string }) => { // eslint-disable-next-line no-param-reassign prev[token.id] = token.symbol; return prev; @@ -3176,7 +3176,7 @@ export const getAddressAtIndex = async ( WHERE \`index\` = ? AND \`wallet_id\` = ? LIMIT 1`, - [walletId, index], + [index, walletId], ); if (addresses.length <= 0) { From 4ab78718077acdf02b2dd9c57c602ceda8f9e8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 30 Apr 2025 12:35:51 -0300 Subject: [PATCH 7/8] refactor: stop JSON.stringifying and parsing for no reason --- packages/wallet-service/src/api/healthcheck.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/wallet-service/src/api/healthcheck.ts b/packages/wallet-service/src/api/healthcheck.ts index 379e4436..0d74d69d 100644 --- a/packages/wallet-service/src/api/healthcheck.ts +++ b/packages/wallet-service/src/api/healthcheck.ts @@ -1,11 +1,11 @@ import middy from '@middy/core'; import { - Healthcheck, - HealthcheckInternalComponent, - HealthcheckDatastoreComponent, - HealthcheckHTTPComponent, - HealthcheckCallbackResponse, - HealthcheckStatus, + Healthcheck, + HealthcheckInternalComponent, + HealthcheckDatastoreComponent, + HealthcheckHTTPComponent, + HealthcheckCallbackResponse, + HealthcheckStatus, } from '@hathor/healthcheck-lib'; import { getLatestHeight } from '@src/db'; import fullnode from '@src/fullnode'; @@ -140,6 +140,6 @@ export const getHealthcheck: APIGatewayProxyHandler = middy(async (event) => { return { statusCode: response.getHttpStatusCode(), - body: JSON.stringify(JSON.parse(response.toJson())), + body: response.toJson(), }; }).use(errorHandler()); From 3ec069858f212287dc9513c9d58331978f7400d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Wed, 30 Apr 2025 12:36:17 -0300 Subject: [PATCH 8/8] chore: stop updating snapshots --- packages/wallet-service/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wallet-service/package.json b/packages/wallet-service/package.json index e0e5eeaf..cae43da2 100644 --- a/packages/wallet-service/package.json +++ b/packages/wallet-service/package.json @@ -6,7 +6,7 @@ "lint": "eslint src/ tests/ --ext .js,.jsx,.ts,.tsx src tests", "lint-fix": "eslint src/ tests/ --fix --ext .js,.jsx,.ts,.tsx src tests", "check-types": "tsc --noemit --skipLibCheck", - "test": "jest -u" + "test": "jest" }, "author": "Hathor Labs", "license": "MIT",